مطالب
NOSQL قسمت سوم

در مطلب قبلی با نوع اول پایگاه‌های‌داده NoSQL یعنی Key/Value Store آشنا شدیم و در این مطلب به معرفی دسته دوم یعنی Document Database خواهیم پرداخت.

در این نوع پایگاه داده ، داده‌ها مانند نوع اول در قالب کلید/مقدار ذخیره می‌شوند و بازگردانی مقادیر نیز دقیقا مشابه نوع اول یعنی Key/Value Store بر اساس کلید می‌باشد. اما تفاوت این سیستم با نوع اول در دسته‌بندی داده‌های مرتبط با یکدیگر در قالب یک Document می‌باشد. سعی کردم در این مطلب با ذکر مثال مطالب را شفاف‌تر بیان کنم:

به عنوان مثال اگر بخواهیم جداول مربوط به پست‌های یک سیستم CMS را بصورت رابطه‌ای پیاده کنیم ، یکی از ساده‌ترین حالات پایه برای پست‌های این سیستم در حالت نرمال به صورت زیر می‌باشد.

 

جداول واضح بوده و نیازی به توضیح ندارد ، حال نحوه‌ی ذخیره‌سازی داده‌ها در سیستم Document Database برای چنین مثالی را بررسی می‌کنیم:

{
_id: ObjectID(‘4bf9e8e17cef4644108761bb’),
Title: ‘NoSQL Part3’,
url: ‘https://www.dntips.ir/yyy/xxxx’,
author: ‘hamid samani’,
tags: [‘databases’,’mongoDB’],
comments:[
{user: ‘unknown user’,
 text:’unknown test’
},
{user:unknown user2’,
 text:’unknown text2
}
]
}

همانگونه که مشاهد می‌کنید نحوه‌ی ذخیره‌سازی داده‌ها بسیار با سیستم رابطه‌ای متفاوت می‌باشد ، با جمع‌بندی تفاوت نحوه‌ی نگه‌داری داده‌ها در این سیستم و RDBMS و بررسی این سیستم نکات اصلی به شرح زیر می‌باشند:

۱-فرمت ذخیره سازی داده‌ها  مشابه فرمت JSON می‌باشد.

۲-به مجموعه داده‌های مرتبط به یکدیگر Document گفته می‌شود.

۳-در این سیستم JOIN ها وجود ندارند و داده‌های مرتبط کنار یکدیگر قرار می‌گیرند ، و یا به تعریف دقیق‌تر داده‌ها در یک داکیومنت اصلی Embed می‌شوند.

به عنوان مثال در اینجا مقدار commentها برابر با آرایه‌ای از Document‌ها می‌باشد.

۴-مقادیر می‌توانند بصورت آرایه نیز در نظر گرفته شوند.

۵-در سیستم‌های RDBMS در صورتی که بخواهیم از وجود JOIN‌ها صر‌فنظر کنیم. به عدم توانایی در نرمال‌سازی برخواهیم خورد که یکی از معایب عدم نرمال‌سازی وجود مقادیر Null در جداول می‌باشد؛ اما در این سیستم به دلیل Schema free بودن می‌توان ساختار‌های متفاوت برای Document‌ها در نظر گرفت.

به عنوان مثال برای یک پست می‌توان مقدار n   کامنت تعریف کرد و برای پست دیگر هیچ کامنتی تعریف نکرد.

۶-در این سیستم اصولا نیازی به تعریف ساختار از قبل موجود نمی‌باشد و به محض اعلان دستور قرار دادن داده‌ها در پایگاه‌داده ساختار متناسب ایجاد می‌شود.


با مقایسه دستورات CRUD در هر دو نوع پایگاه داده با نحوه‌ی کوئری گرفتن از Document Database آشنا می‌شویم:

در SQL برای ایجاد جدول خواهیم داشت:

CREATE TABLE posts (
    id INT NOT NULL
        AUTO_INCREMENT,
    author_id INT NOT NULL,
    url VARCHAR(50),
    PRIMARY KEY (id)
)

دستور فوق در Document Database معادل است با:

 
db.posts.insert({id: “256” , author_id:”546”,url:"http://example.com/xxx"}) // با قرار دادن مقدار نوع ساختار مشخص می‌شود 


در SQL  جهت خواندن خواهیم داشت:

 
SELECT * from posts
WHERE author_id > 100
و معادل آن برابر است با:
db.posts.find({author_id:{$gt:”1000”}})

در SQL جهت بروزرسانی داریم:
UPDATE posts
SET author_id= "123"
که معادل است با:
db.posts.update({ $set: { author_id: "123" }})

در SQL جهت حذف خواهیم داشت:
DELETE FROM posts
WHERE author_id= "654"

که معادل است با:
db.posts.remove( { author_id: "654" } )

همانگونه که مشاهده می‌فرمایید نوشتن کوئری برای این پایگاه داده ساده بوده و زبان آن نیز بر پایه جاوا اسکریپت می‌باشد که برای اکثر برنامه‌نویسان قابل درک است.
 

تاکنون توسط شرکت‌های مختلف پیاده‌سازی‌های مختلفی از این سیستم انجام شده است که از مهم‌ترین و پر استفاده‌ترین آنها می‌توان به موارد زیر اشاره کرد:

مطالب
آموزش MDX Query - قسمت سیزدهم – برخی توابع برای کار روی ساختار های سلسله مراتبی (prevmember و nextmember)

کار با توابع  prevmember  و  nextmember

قبل از اجرای کوئری‌های زیر در ابتدا به ساختار سلسله مراتبی Customer دقت نمایید و ترتیب کشور‌ها را در این ساختار بررسی نمایید.

کوئری زیر را اجرا نمایید :

Select
{
[Measures].[Internet Sales Amount],
[Measures].[Internet Tax Amount]
}on columns,
{
[Customer].[Customer Geography].[Country].[Germany],
[Customer].[Customer Geography].[Country].[Germany].prevmember
}on rows
From [Adventure Works]

این تابع برادر قبلی را بدست میآ ورد

حال کوئری زیر را اجرا نمایید :

Select
{
[Measures].[Internet Sales Amount],
[Measures].[Internet Tax Amount]
} on columns,
{
[Customer].[Customer Geography].[Country].[Germany],
[Customer].[Customer Geography].[Country].[Germany].lag(1)
} on rows
From [Adventure Works]

در اینجا میزان فروش اینترنتی و همچنین میزان مالیات اینترنتی برای مشتریان آلمان و کشور قبلی بدست می‌آید.

با این تابع می توان برادر قبلی را با اعلام یک فاصله بدست آورد. مثلا 4 برادر قبلی یا .... را توسط این تابع بدست آورد .

کوئری زیر را اجرا نمایید :

Select
{
[Measures].[Internet Sales Amount],
[Measures].[Internet Tax Amount]
} on columns,
{
[Customer].[Customer Geography].[Country].[Germany],
[Customer].[Customer Geography].[Country].[Germany].prevmember.prevmember
} on rows
From [Adventure Works]

در این حالت مشابه تابع (2)Lag عمل کرده ایم. حال با استفاده از تابع  Nextmember  می توانیم برادر بعدی را بدست بیاوریم.

کوئری زیر را اجرا کنید :

select
{
[Measures].[Internet Sales Amount],
[Measures].[Internet Tax Amount]
} on columns,
{
[Customer].[Customer Geography].[Country].[Germany],
[Customer].[Customer Geography].[Country].[Germany].nextmember
} on rows
From [Adventure Works]

 و همچنین در کوئری زیر برادر بعد از برادر بعدی را بدست آورده ایم

Select
{
[Measures].[Internet Sales Amount],
[Measures].[Internet Tax Amount]
} on columns,
{
[Customer].[Customer Geography].[Country].[Germany],
[Customer].[Customer Geography].[Country].[Germany].nextmember.nextmember
} on rows
From [Adventure Works]

یک ترکیب از عملگر Range , NextMember در کوئری زیر نوشته شده است.

Select
{
[Measures].[Internet Sales Amount],
[Measures].[Internet Tax Amount]
} on columns,
[Customer].[Customer Geography].[Country].[Germany]
:
[Customer].[Customer Geography].[Country].[Germany].nextmember.nextmember
on rows
From [Adventure Works]

کاربرد تابع Lead برای به دست آوردن برادر بعدی بر اساس عددی می باشد که به آن داده ایم .

در اینجا ترکیبی از رنج و به دست  آوردن برادر برادر بعدی را داریم .

Select
{
[Measures].[Internet Sales Amount],
[Measures].[Internet Tax Amount]
} on columns,
[Customer].[Customer Geography].[Country].[Germany]
:
[Customer].[Customer Geography].[Country].[Germany].lead(2)
on rows
From [Adventure Works]

در قسمت بعدی تابع Order را بررسی می‌کنیم.

مطالب
مرجعی در مورد نگارش‌های مختلف SQL Server

آیا می‌دانید که تا این تاریخ پس از ارائه سرویس پک یک اس کیوال سرور 2008، دقیقا 5 به روز رسانی دیگر نیز در مورد آن منتشر شده‌اند؟
آیا می‌دانید پس از ارائه سرویس پک سه مربوط به اس کیوال سرور 2005 ، دقیقا 10 مورد به روز رسانی دیگر آن نیز منتشر گردیده‌اند؟
آیا می‌دانید پس از سرویس پک 4 اس کیوال سرور 2000 دقیقا چند مورد به روز رسانی مرتبط با آن منتشر شده‌اند؟ (72 مورد!)
آیا می‌دانید دقیقا از چه نگارشی از SQL Server با کدام به روز رسانی‌ها استفاده می‌کنید؟

پاسخ دقیق به تمام این سؤالات را به صورت طبقه بندی شده و بسیار منظم، در وبلاگ زیر می‌توانید مشاهده نمائید:



مطالب
EF Code First #8

ادامه بحث بررسی جزئیات نحوه نگاشت کلاس‌ها به جداول، توسط EF Code first


استفاده از Viewهای SQL Server در EF Code first

از Viewها عموما همانند یک جدول فقط خواندنی استفاده می‌شود. بنابراین نحوه نگاشت اطلاعات یک کلاس به یک View دقیقا همانند نحوه نگاشت اطلاعات یک کلاس به یک جدول است و تمام نکاتی که تا کنون بررسی شدند، در اینجا نیز صادق است. اما ...
الف) بر اساس تنظیمات توکار EF Code first، نام مفرد کلاس‌ها، حین نگاشت به جداول، تبدیل به اسم جمع می‌شوند. بنابراین اگر View ما در سمت بانک اطلاعاتی چنین تعریفی دارد:
Create VIEW EmployeesView
AS
SELECT id,
FirstName
FROM Employees

در سمت کدهای برنامه نیاز است به این شکل تعریف شود:

using System.ComponentModel.DataAnnotations;

namespace EF_Sample04.Models
{
[Table("EmployeesView")]
public class EmployeesView
{
public int Id { set; get; }
public string FirstName { set; get; }
}
}

در اینجا به کمک ویژگی Table، نام دقیق این View را در بانک اطلاعاتی مشخص کرده‌ایم. به این ترتیب تنظیمات توکار EF بازنویسی خواهد شد و دیگر به دنبال EmployeesViews نخواهد گشت؛ یا جدول متناظر با آن‌را به صورت خودکار ایجاد نخواهد کرد.
ب) View شما نیاز است دارای یک فیلد Primary key نیز باشد.
ج) اگر از مهاجرت خودکار توسط MigrateDatabaseToLatestVersion استفاده کنیم، پیغام خطای زیر را دریافت خواهیم کرد:

There is already an object named 'EmployeesView' in the database.

علت این است که هنوز جدول dbo.__MigrationHistory از وجود آن مطلع نشده است، زیرا یک View، خارج از برنامه و در سمت بانک اطلاعاتی اضافه می‌شود.
برای حل این مشکل می‌توان همانطور که در قسمت‌های قبل نیز عنوان شد، EF را طوری تنظیم کرد تا کاری با بانک اطلاعاتی نداشته باشد:

Database.SetInitializer<Sample04Context>(null);

به این ترتیب EmployeesView در همین لحظه قابل استفاده است.
و یا به حالت امن مهاجرت دستی سوئیچ کنید:
Add-Migration Init -IgnoreChanges
Update-Database

پارامتر IgnoreChanges سبب می‌شود تا متدهای Up و Down کلاس مهاجرت تولید شده، خالی باشد. یعنی زمانیکه دستور Update-Database انجام می‌شود، نه Viewایی دراپ خواهد شد و نه جدول اضافه‌ای ایجاد می‌گردد. فقط جدول dbo.__MigrationHistory به روز می‌شود که هدف اصلی ما نیز همین است.
همچنین در این حالت کنترل کاملی بر روی کلاس‌های Up و Down وجود دارد. می‌توان CreateTable اضافی را به سادگی از این کلاس‌ها حذف کرد.

ضمن اینکه باید دقت داشت یکی از اهداف کار با ORMs، فراهم شدن امکان استفاده از بانک‌های اطلاعاتی مختلف، بدون اعمال تغییری در کدهای برنامه می‌باشد (فقط تغییر کانکشن استرینگ، به علاوه تعیین Provider جدید، باید جهت این مهاجرت کفایت کند). بنابراین اگر از View استفاده می‌کنید، این برنامه به SQL Server گره خواهد خورد و دیگر از سایر بانک‌های اطلاعاتی که از این مفهوم پشتیبانی نمی‌کنند، نمی‌توان به سادگی استفاده کرد.



استفاده از فیلدهای XML اس کیوال سرور

در حال حاضر پشتیبانی توکاری توسط EF Code first از فیلدهای ویژه XML اس کیوال سرور وجود ندارد؛ اما استفاده از آن‌ها با رعایت چند نکته ساده، به نحو زیر است:

using System.ComponentModel.DataAnnotations;
using System.Xml.Linq;

namespace EF_Sample04.Models
{
public class MyXMLTable
{
public int Id { get; set; }

[Column(TypeName = "xml")]
public string XmlValue { get; set; }

[NotMapped]
public XElement XmlValueWrapper
{
get { return XElement.Parse(XmlValue); }
set { XmlValue = value.ToString(); }
}
}
}


در اینجا توسط TypeName ویژگی Column، نوع توکار xml مشخص شده است. این فیلد در طرف کدهای کلاس‌های برنامه، به صورت string تعریف می‌شود. سپس اگر نیاز بود به این خاصیت توسط LINQ to XML دسترسی یافت، می‌توان یک فیلد محاسباتی را همانند خاصیت XmlValueWrapper فوق تعریف کرد. نکته‌ دیگری را که باید به آن دقت داشت، استفاده از ویژگی NotMapped می‌باشد، تا EF سعی نکند خاصیتی از نوع XElement را (یک CLR Property) به بانک اطلاعاتی نگاشت کند.

و همچنین اگر علاقمند هستید که این قابلیت به صورت توکار اضافه شود، می‌توانید اینجا رای دهید!



نحوه تعریف Composite keys در EF Code first

کلاس نوع فعالیت زیر را درنظر بگیرید:

namespace EF_Sample04.Models
{
public class ActivityType
{
public int UserId { set; get; }
public int ActivityID { get; set; }
}
}

در جدول متناظر با این کلاس، نباید دو رکورد تکراری حاوی شماره کاربری و شماره فعالیت یکسانی باهم وجود داشته باشند. بنابراین بهتر است بر روی این دو فیلد، یک کلید ترکیبی تعریف کرد:

using System.Data.Entity.ModelConfiguration;
using EF_Sample04.Models;

namespace EF_Sample04.Mappings
{
public class ActivityTypeConfig : EntityTypeConfiguration<ActivityType>
{
public ActivityTypeConfig()
{
this.HasKey(x => new { x.ActivityID, x.UserId });
}
}
}

در اینجا نحوه معرفی بیش از یک کلید را در متد HasKey ملاحظه می‌کنید.

یک نکته:
اینبار اگر سعی کنیم مثلا از متد db.ActivityTypes.Find با یک پارامتر استفاده کنیم، پیغام خطای «The number of primary key values passed must match number of primary key values defined on the entity» را دریافت خواهیم کرد. برای رفع آن باید هر دو کلید، در این متد قید شوند:

var activity1 = db.ActivityTypes.Find(4, 1);

ترتیب آن‌ها هم بر اساس ترتیبی که در کلاس ActivityTypeConfig، ذکر شده است، مشخص می‌گردد. بنابراین در این مثال، اولین پارامتر متد Find، به ActivityID اشاره می‌کند و دومین پارامتر به UserId.


بررسی نحوه تعریف نگاشت جداول خود ارجاع دهنده (Self Referencing Entity)

سناریوهای کاربردی بسیاری را جهت جداول خود ارجاع دهنده می‌توان متصور شد و عموما تمام آن‌ها برای مدل سازی اطلاعات چند سطحی کاربرد دارند. برای مثال یک کارمند را درنظر بگیرید. مدیر این شخص هم یک کارمند است. مسئول این مدیر هم یک کارمند است و الی آخر. نمونه دیگر آن، طراحی منوهای چند سطحی هستند و یا یک مشتری را درنظر بگیرید. مشتری دیگری که توسط این مشتری معرفی شده است نیز یک مشتری است. این مشتری نیز می‌تواند یک مشتری دیگر را به شما معرفی کند و این سلسله مراتب به همین ترتیب می‌تواند ادامه پیدا کند.
در طراحی بانک‌های اطلاعاتی، برای ایجاد یک چنین جداولی، یک کلید خارجی را که به کلید اصلی همان جدول اشاره می‌کند، ایجاد خواهند کرد؛ اما در EF Code first چطور؟

using System.Collections.Generic;

namespace EF_Sample04.Models
{
public class Employee
{
public int Id { set; get; }
public string FirstName { get; set; }
public string LastName { get; set; }

//public int? ManagerID { get; set; }
public virtual Employee Manager { get; set; }
}
}

در این کلاس، خاصیت Manager دارای ارجاعی است به همان کلاس؛ یعنی یک کارمند می‌تواند مسئول کارمند دیگری باشد. برای تعریف نگاشت‌ این کلاس به بانک اطلاعاتی می‌توان از روش زیر استفاده کرد:

using System.Data.Entity.ModelConfiguration;
using EF_Sample04.Models;

namespace EF_Sample04.Mappings
{
public class EmployeeConfig : EntityTypeConfiguration<Employee>
{
public EmployeeConfig()
{
this.HasOptional(x => x.Manager)
.WithMany()
//.HasForeignKey(x => x.ManagerID)
.WillCascadeOnDelete(false);
}
}
}

با توجه به اینکه یک کارمند می‌تواند مسئولی نداشته باشد (خودش مدیر ارشد است)، به کمک متد HasOptional مشخص کرده‌ایم که فیلد Manager_Id را که می‌خواهی به این کلاس اضافه کنی باید نال پذیر باشد. توسط متد WithMany طرف دیگر رابطه مشخص شده است.
اگر نیاز بود فیلد Manager_Id اضافه شده نام دیگری داشته باشد، یک خاصیت nullable مانند ManagerID را که در کلاس Employee مشاهده می‌کنید،‌ اضافه نمائید. سپس در طرف تعاریف نگاشت‌ها به کمک متد HasForeignKey، باید صریحا عنوان کرد که این خاصیت، همان کلید خارجی است. از این نکته در سایر حالات تعاریف نگاشت‌ها نیز می‌توان استفاده کرد، خصوصا اگر از یک بانک اطلاعاتی موجود قرار است استفاده شود و از نام‌های دیگری بجز نام‌های پیش فرض EF استفاده کرده است.




مثال‌های این سری رو از این آدرس هم می‌تونید دریافت کنید: (^)

مطالب
از کجا به وب سرور شما حمله DOS شده است؟

اگر پیش فرض‌های IIS را تغییر نداده باشید، تمامی اعمال رخ داده در طی یک روز را در یک سری فایل‌های متنی در یکی از آدرس‌های زیر ذخیره می‌کند:

IIS 6.0: %windir%\System32\LogFiles\W3SVC<SiteID>
IIS 7.0: %systemDrive%\Inetpub\logfiles

اطلاعات فوق العاده ارزشمندی را می‌توان از این لاگ فایل‌های خام بدست آورد. اعم از تعداد بار دقیق مراجعه به صفحات، چه فایل‌هایی مفقود هستند (خطای 404)، کدام صفحات کندترین‌های سایت شما را تشکیل می‌دهند و الی آخر.
مایکروسافت برای آنالیز این لاگ فایل‌ها (که محدود به IIS‌ هم نیست) ابزاری را ارائه داده به نام LogParser که این امکان را به شما می‌دهد تا از فایل‌های CSV مانند با استفاده از عبارات SQL کوئری بگیرید (چیزی شبیه به پروایدرهای LINQ البته در سال‌های 2005 و قبل از آن).
یکی از کاربردهای این ابزار، بررسی‌های امنیتی است.

سؤال؟ چگونه متوجه شوم کدام کامپیوتر در شبکه اقدام به حمله DOS کرده و سرور را دارد از پا در می‌آورد؟
از آنجائیکه در لاگ‌های IIS دقیقا IP تمامی درخواست‌ها ثبت می‌شود، با آنالیز این فایل ساده متنی می‌توان اطلاعات لازم را بدست آورد.
logparser.exe -i:iisw3c "select top 25 count(*) as HitCount, c-ip from C:\WINDOWS\system32\LogFiles\W3SVC1\*.log group by c-ip order by HitCount DESC" -rtp:-1 > top25-ip.txt
دستور خط فرمان فوق، یک کوئری SQL را بر روی تمامی لاگ فایل‌های قرار گرفته در مسیر یاد شده اجرا کرده و نتیجه را در یک فایل متنی ذخیره می‌کند.
به این صورت می‌توان دقیقا متوجه شد که از کدام IP‌ مشغول به زانو درآوردن سرور هستند.

اگر به این ابزار علاقمند شدید مطالعه مقاله زیر توصیه می‌شود:


مطالب
آموزش MDX Query - قسمت چهاردهم– Order

در زیر در ابتدا یک کوئری می‌نویسیم و سپس بررسی می‌کنیم که چگونه می‌توان خروجی را مرتب کنیم.

Select
[Measures].[Internet Sales Amount]on columns,
[Product].[Product Categories].[Subcategory] on rows
From [Adventure Works]

کوئری بالا میزان فروش اینترنتی را بر اساس دسته بندی محصولات واکشی می‌کند

در کوئری زیر لیست فروش، براساس میزان فروش اینترنتی مرتب شده است :

Select
[Measures].[Internet Sales Amount] on columns,
order(
[Product].[Subcategory].[Subcategory],
[Measures].[Internet Sales Amount]
)on rows
From [Adventure Works]

تابع Order،  ستون یا ردیف خاصی را بر اساس ستون یا ردیف دیگری مرتب می کند .این تابع بر روی کل یک Axis  اعمال میگردد و دو پارامتر دارد .

پارامتر دوم مشخص می کند مرتب سازی پارامتر اول بر اساس چه شاخصی باشد .

همچنین در کوئری زیر نحوه‌ی مرتب کردن به صورت نزولی را خواهیم دید.

Select
[Measures].[Internet Sales Amount] on columns,
order(
[Product].[Subcategory].[Subcategory],
[Measures].[Internet Sales Amount],
desc
) on rows
From [Adventure Works]

مرتب سازی به صورت صعودی به شکل زیر می‌باشد

Select
[Measures].[Internet Sales Amount] on columns,
order(
[Product].[Subcategory].[Subcategory],
[Measures].[Internet Sales Amount],
asc
) on rows
From [Adventure Works]

از  آنجایی که مرتب سازی به صورت پیش فرض صعودی می باشد، ب کار بردن این کلید واژه ، خیلی لازم نمی باشد .


رویکرد تابع Order  با ساختار سلسله مراتبی 

برای شروع کوئری زیر را اجرا می‌کنیم.

Select
[Measures].[Internet Sales Amount] on columns,
order(
[Product].[Product Categories].[Subcategory],
[Measures].[Internet Sales Amount],
asc
) on rows
From [Adventure Works]

البته در این حالت با نگاه کردن به خروجی متوجه میشوید که عملیات مرتب سازی به درستی انجام نشده است و دلیل این اتفاق رویکرد تابع مرتب سازی به ساختار سلسله مراتبی میباشد . عملا تابع مرتب سازی احترام ویژه ای به ساختار سلسله می گزارد .

حال همان کوئری را به صورت زیر اجرا می‌کنیم :

Select
[Measures].[Internet Sales Amount] on columns,
order(
[Product].[Product Categories].[Subcategory],
[Measures].[Internet Sales Amount],
basc
) on rows
From [Adventure Works]

حرف  در کلمه ی کلیدی  basc  به معنی شکستن ساختار سلسله مراتبی می باشد . در این حالت عملیات مرتب سازی بدون درنظر گرفتن ساختار سلسله مراتبی انجام می گردد .

کوئری‌های زیر تاثیر مرتب سازی نزولی و ساختار سلسله مراتبی را بررسی می‌کنند

Select
[Measures].[Internet Sales Amount] on columns,
order(
[Product].[Product Categories].[Subcategory],
[Measures].[Internet Sales Amount],
desc
) on rows
From [Adventure Works]

در این حالت مرتب سازی نزولی انجام می گردد اما با درنظر گرفتن ساختار سلسله مراتبی

Select
[Measures].[Internet Sales Amount] on columns,
order(
[Product].[Product Categories].[Subcategory],
[Measures].[Internet Sales Amount],
bdesc
) on rows
From [Adventure Works]

در این حالت عمل مرتب سازی نزولی بدون درنظر گرفتن ساختار سلسله مراتبی انجام میگردد .

می توان مرتب سازی را بر اساس یک ستون که در کوئری ذکر نشده است انجام داد . در این حالت نتیجه در ظاهر مرتب نمی باشد .

Select
[Measures].[Internet Sales Amount] on columns,
order(
[Product].[Product Categories].[Subcategory],
[Measures].[Reseller Sales Amount],
bdesc
) on rows
From [Adventure Works]

در این کوئری دو ستون وجود دارد اما مرتب سازی براساس یکی از ستون ها انجام می شود

Select
{
[Measures].[Internet Sales Amount],
[Measures].[Reseller Sales Amount]
} on columns,
order(
[Product].[Product Categories].[Subcategory],
[Measures].[Reseller Sales Amount],
bdesc
) on rows
From [Adventure Works]

عملیات مرتب سازی می تواند بر روی هرکدام از Axis  ها جداگانه انجام گردد .

Select
order(
[Product].[Product Categories].[Subcategory],
[Measures].[Reseller Sales Amount],
bdesc
 ) on columns,
{
[Measures].[Internet Sales Amount],
[Measures].[Reseller Sales Amount]
} on rows
From [Adventure Works]

از تابع hierarchize  هم می‌توان برای عملیات مرتب سازی استفاده کرد

Select
[Measures].[Internet Sales Amount] on columns,
hierarchize(
{
[Product].[Subcategory].[Touring Bikes],
[Product].[Subcategory],
[Product].[Subcategory].[Mountain Bikes]
}
)on rows
From [Adventure Works]

برای مرتب سازی ردیف هایی که عملا ارتباطی با هم ندارند از این تابع استفاده می شود . پیش فرض مرتب سازی نزولی می باشد . البته می توانستیم همچنان از تابع Order  استفاده کنیم:

Select
[Measures].[Internet Sales Amount] on columns,
Order(
{
[Product].[Subcategory].[Touring Bikes],
[Product].[Subcategory],
[Product].[Subcategory].[Mountain Bikes]
} , [Measures].[Internet Sales Amount] , basc
)on rows
From [Adventure Works]

حال می‌خواهیم ببینیم چگونه می‌توان با استفاده از تابع hierarchize  مرتب سازی صعودی را انجام دهیم :

Select
[Measures].[Internet Sales Amount] on columns,
hierarchize(
{
[Product].[Subcategory].[Touring Bikes],
[Product].[Subcategory],
[Product].[Subcategory].[Mountain Bikes]
 },
 post
) on rows
From [Adventure Works]

همان طور که مشخص می‌باشد مرتب سازی به درستی صورت نگرفته است؟! دلیل این اتفاق اولویت دادن به ساختار سلسله مراتبی می‌باشد.

برای بررسی بیشتر ابتدا کوئری زیر را فراخوانی نمایید :

Select
[Measures].[Internet Sales Amount] on columns,
non empty order(
crossjoin(
[Product].[Category].[Category],
[Product].[Subcategory].[Subcategory]
 ),
[Measures].[Internet Sales Amount],
desc
) on rows
From [Adventure Works]

همانطور که مشخص می‌باشد، مرتب سازی براساس فروش اینترنتی و با درنظر گرفتن ساختار سلسله مراتبی صورت گرفته است. برای مرتب سازی بدون در نظر گرفتن ساختار سلسله مراتبی کوئری زیر را بنویسید :

Select
[Measures].[Internet Sales Amount] on columns,
non empty order(
crossjoin(
[Product].[Category].[Category],
[Product].[Subcategory].[Subcategory]
),
[Measures].[Internet Sales Amount],
bdesc
) on rows
From [Adventure Works]

در این حالت مرتب سازی بدون در نظر گرفتن ساختار سلسله مراتب انجام می گردد .

در دو کوئری فوق از تابع Non empty  برای حذف ردیف‌های  Null  استفاده شده است.

مطالب
کوئری نویسی در EF Core - قسمت هفتم - کار با رشته‌‌ها
هدف از این سری مثال‌ها، آشنایی با متدها و توابعی است که در حین کار با خواص رشته‌ای در LINQ to Entities، مجاز به استفاده‌ی از آن‌ها هستیم و همچنین اگر تابعی در EF-Core هنوز تعریف نشده بود، راه حل چیست.


مثال 1: نام تمام کاربران را با قالب 'Surname, Firstname'  نمایش دهید.

var members = context.Members
                                    .Select(member => new { Name = member.Surname + ", " + member.FirstName })
                                    .ToList();
متد Select می‌تواند به همراه اعمال محاسباتی ساده‌ای نیز باشد که نمونه‌ای از آن‌را در اینجا مشاهده می‌کنید.
با این خروجی:



مثال 2: تمام امکاناتی را که با Tennis شروع می‌شوند، لیست کنید.
این گزارش به همراه تمام ستون‌های جدول است.

var facilities = context.Facilities
                                        .Where(facility => facility.Name.StartsWith("Tennis"))
                                        .ToList();
متدهای استانداردی مانند StartsWith، EndsWith و Contains را می‌توان بر روی خواص رشته‌ای بکار برد.
با این خروجی:



مثال 3: تمام امکاناتی را که با tennis شروع می‌شوند، لیست کنید. این جستجو باید غیرحساس به بزرگی و کوچکی حروف باشد.
این گزارش به همراه تمام ستون‌های جدول است.

نیازی به انجام مجزای این تمرین نیست؛ چون پاسخ آن همان پاسخ مثال 2 است. Collation پیش‌فرض در SQL Server، غیرحساس به بزرگی و کوچکی حروف است. بنابراین چه tennis را جستجو کنیم و یا TeNnis را، تفاوتی نمی‌کند.


مثال 4: شماره تلفن‌های دارای پرانتز را لیست کنید.
این گزارش باید به همراه ستون‌های memid, telephone باشد.

روش اول: در اینجا دوبار از متد Contains استفاده شده‌است:
var members = context.Members
                                    .Select(member => new { member.MemId, member.Telephone })
                                    .Where(member => member.Telephone.Contains("(")
                                                    && member.Telephone.Contains(")"))
                                    .ToList();
با این خروجی:


روش دوم: اگر می‌خواهیم کنترل بیشتری را بر روی خروجی نهایی LIKE تولیدی داشته باشیم، می‌توان از متد سفارشی استاندارد EF.Functions.Like استفاده کرد که از حروف wild cards نیز پشتیبانی می‌کند:
members = context.Members
                                    .Select(member => new { member.MemId, member.Telephone })
                                    .Where(member => EF.Functions.Like(member.Telephone, "%[()]%"))
                                    .ToList();
با این خروجی:



مثال 5: کد پستی‌ها 5 رقمی هستند. گزارشی را تهیه کنید که در آن اگر کدپستی کمتر از 5 رقم بود، ابتدای آن با صفر شروع شود.
هدف اصلی از این مثال، اعمال متد PadLeft(5, '0') به خاصیت member.ZipCode است.

روش اول: EF-Core فعلا قابلیت ترجمه‌ی PadLeft(5, '0') را به معادل SQL آن‌را ندارد. به همین جهت مجبور هستیم ابتدا ZipCode‌ها را به صورت رشته‌ای بازگشت دهیم که در اینجا استفاده‌ی از Convert.ToString مجاز است.
با این خروجی:
SELECT   CONVERT (NVARCHAR (MAX), [m].[ZipCode]) AS [Zip]
FROM     [Members] AS [m]
ORDER BY CONVERT (NVARCHAR (MAX), [m].[ZipCode]);
 سپس می‌توان بر روی لیست آماده‌ی موجود در حافظه، از LINQ to Objects استفاده کرد و در این حالت دسترسی کاملی به تمام امکانات زبان #C وجود دارد:
var members = context.Members
                                    .Select(member => new { ZipCode = Convert.ToString(member.ZipCode) })
                                    .OrderBy(m => m.ZipCode)
                                    .ToList();
// Now using LINQ to Objects
members = members.Select(member => new { ZipCode = member.ZipCode.PadLeft(5, '0') })
                                                    .OrderBy(m => m.ZipCode)
                                                    .ToList();

روش دوم: SQL Server به همراه تابع استانداردی به نام Replicate است که از آن می‌توان برای شبیه سازی PadLeft، بدون متوسل شدن به LINQ to Objects، استفاده کرد. اما چون این تابع هنوز به EF-Core معرفی نشده‌است، نیاز است خودمان اینکار را انجام دهیم. در این روش، از متد SqlDbFunctionsExtensions.SqlReplicate استفاده می‌شود. روش تعریف این نوع متدها را در مطلب «امکان تعریف توابع خاص بانک‌های اطلاعاتی در EF Core» پیشتر بررسی کرده‌ایم که برای مثال در اینجا چنین شکلی را پیدا می‌کند:
namespace EFCorePgExercises.Utils
{
    public static class SqlDbFunctionsExtensions
    {
        public static string SqlReplicate(string expression, int count)
            => throw new InvalidOperationException($"{nameof(SqlReplicate)} method cannot be called from the client side.");

        private static readonly MethodInfo _sqlReplicateMethodInfo = typeof(SqlDbFunctionsExtensions)
            .GetRuntimeMethod(
                nameof(SqlDbFunctionsExtensions.SqlReplicate),
                new[] { typeof(string), typeof(int) }
            );


        public static void AddCustomSqlFunctions(this ModelBuilder modelBuilder)
        {
            modelBuilder.HasDbFunction(_sqlReplicateMethodInfo)
                .HasTranslation(args =>
                {
                    return SqlFunctionExpression.Create("REPLICATE",
                        args,
                        _sqlReplicateMethodInfo.ReturnType,
                        typeMapping: null);
                });
        }
    }
}
پس از آن فقط کافی است متد AddCustomSqlFunctions را به Context برنامه معرفی کنیم:
namespace EFCorePgExercises.DataLayer
{
    public class ApplicationDbContext : DbContext
    {
         // ...

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
         // ...
            modelBuilder.AddCustomSqlFunctions();
         // ...
        }
    }
}
اکنون می‌توان از تابع SqlDbFunctionsExtensions.SqlReplicate جهت شبیه سازی PadLeft به صورت زیر استفاده کرد:
var newMembers = context.Members
                                        .Select(member => new
                                        {
                                            ZipCode =
                                                SqlDbFunctionsExtensions.SqlReplicate(
                                                    "0", 5 - Convert.ToString(member.ZipCode).Length)
                                                + member.ZipCode
                                        })
                        .OrderBy(m => m.ZipCode)
                        .ToList();
با این خروجی:



مثال 6: اولین حرف نام خانوادگی کاربران در کل ردیف‌های جدول چندبار تکرار شده‌است؟
این گزارش باید به همراه ستون‌های letter,  count باشد.

var members = context.Members
                                    .Select(member => new { Letter = member.Surname.Substring(0, 1) })
                                    .GroupBy(m => m.Letter)
                                    .Select(g => new
                                    {
                                        Letter = g.Key,
                                        Count = g.Count()
                                    })
                                    .OrderBy(r => r.Letter)
                                    .ToList();
هدف از این مثال بیان مجاز بودن استفاده‌ی از متد Substring بر روی خواص رشته‌ای است که EF-Core امکان ترجمه‌ی آن‌ها را به کدهای SQL دارد.
با این خروجی:



مثال 7: حروف '-','(',')', ' ' را از شماره تلفن‌ها حذف کنید.
این گزارش باید به همراه ستون‌های memid, telephone باشد.

بانک اطلاعاتی PostgreSQL به همراه تابع استاندارد regexp_replace است و می‌توان از آن برای حل یک چنین مسایلی استفاده کرد:
select memid, regexp_replace(telephone, '[^0-9]', '', 'g') as telephone
from members
order by memid;
اما SQL Server هنوز هم به همراه یک چنین تابعی نیست. بنابراین از روش زیر نیز می‌توان مثال جاری را حل کرد:
var members = context.Members
                                .Select(member => new
                                {
                                    member.MemId,
                                    Telephone = member.Telephone.Replace("-", "")
                                                        .Replace("(", "")
                                                        .Replace(")", "")
                                                        .Replace(" ", "")
                                })
                                .OrderBy(r => r.MemId)
                                .ToList();
با این خروجی:



کدهای کامل این قسمت را در اینجا می‌توانید مشاهده کنید.
مطالب
تجزیه یک رشته به کلمات تشکیل دهنده آن توسط Recursive CTE
برای پردازش یک عبارت در بسیاری از موارد نیاز هست که عبارت به کلمات تشکیل دهنده اش تجزیه شود. روش‌های متنوعی برای انجام این عمل وجود دارد که یکی از شناخته شده‌ترین آنها استفاده از جدول اعداد می‌باشد (البته از بین روش‌های مجموعه گرا/set -based).
روشهایی که قرار هست در ادامه توضیح داده شوند بر اساس کوئری بازگشتی می‌باشند. الگوریتم‌های متنوعی بر اساس recursive CTE برای حل این مساله خلق شده اند. که من تنها به دو روش آن اکتفا می‌کنم.

Recursive CTE در نسخه‌ی 2005 به SQL Server اضافه شده است. توسط این تکنیک مسائل پیچیده و گوناگونی را میتوان بسادگی حل نمود. مخصوصا مسائلی که ماهیت بازگشتی دارند مثل پیمایش یک درخت یا پیمایش یک گراف وزن دار.

روش اول:

یک کوئری بازگشتی دارای دو بخش هست به نام‌های Anchor و recursive. در بخش دوم کوئری باز خودش را فراخوانی می‌کند تا به داده هایی که در مرحله قبل تولید شده اند دسترسی پیدا کند در اولین فراخوانی توسط عضو recursive، داده‌های تولید شده در قسمت Anchor قابل دسترسی هستند. در قسمت دوم، کوئری آنقدر خود را فراخوانی می‌کند تا دیگر سطری از مرحله قبل وجود نداشته باشد که به آن مراجعه کند.

توضیح تکنیک:
در گام اول اندیس شروع و پایان کلمه اول را بدست می‌آوریم.
سپس در گام بعدی از اندیس پایان کلمه قبلی به عنوان اندیس شروع کلمه جدید استفاده می‌کنیم.
و اندیس پایان کلمه توسط تابع charindex بدست می‌آید.
کوئری تا زمانی ادامه پیدا میکند که کلمه برای تجزیه کردن در رشته باقی مانده باشد. فقط فراموش نکنید که حتما باید آخر عبارت یک کارکتر space داشته باشید.
DECLARE @S VARCHAR(50)='I am a student I go to school ';
WITH CTE AS 
(
     SELECT 1 rnk,
            1 start,
            CHARINDEX(' ', @s) - 1 ed

     UNION ALL
 
     SELECT rnk + 1,
            ed + 2,
            CHARINDEX(' ', @s, ed + 2) - 1
       FROM CTE
      WHERE CHARINDEX(' ', @s, ed + 2) > 0
)
SELECT rnk, SUBSTRING(@s, start, ed - start + 1) AS word
FROM CTE
  
/* Result
rnk         word
----------- -------
1           I
2           am
3           a
4           student
5           I
6           go
7           to
8           school
*/



روش دوم:
در این روش در همان CTE عبارت تجزیه می‌شود و عمل تفکیک به مرحله بعدی واگذار نمی‌شود،
در گام اول، اولین کلمه انتخاب می‌شود. و سپس آن کلمه از رشته حذف می‌شود. با این روش همیشه اندیس شروع کلمه برابر با 1 خواهد بود و اندیس پایان کلمه توسط تابع charindex بدست خواهد آمد.
در گام بعدی اولین کلمه موجود در رشته ای که قبلا اولین کلمه از آن جدا شده است بدست می‌آید و باز مثل قبلی کلمه انتخاب شده از رشته جدا شده و رشته برش یافته به مرحله بعد منتقل می‌شود.
در این روش مثل روش قبلی آخر عبارتی که قرار هست تجزیه شود باید یک کارکتر خالی وجود داشته باشد.
DECLARE @a VARCHAR(50)='I am a student I go to school ';
 
WITH MyWords(ranking, word, string) AS(
 
    SELECT 1,
           CAST(SUBSTRING(@a, 1, CHARINDEX(' ', @a) - 1) AS VARCHAR(25)),
           STUFF(@a, 1, CHARINDEX(' ', @a), '')
  
    UNION ALL
  
    SELECT ranking + 1,
           CAST(SUBSTRING(string, 1, CHARINDEX(' ', string) - 1) AS VARCHAR(25)),
           STUFF(string, 1, CHARINDEX(' ', string), '')
      FROM MyWords
     WHERE CHARINDEX(' ', string) > 0
)
SELECT ranking, word FROM MyWords;
و خروجی:
ranking     word
----------- -------------------------
1           I
2           am
3           a
4           student
5           I
6           go
7           to
8           school

پاسخ به بازخورد‌های پروژه‌ها
راهنمایی در مورد ایجاد columnها
در مورد مثال خاص شما، خیر. به select آخری که انجام دادید دقت کنید. این select است که خروجی نهایی را مشخص می‌کند.
select new
       {
           CostName = ...,
           CostAmount = ...
       }
مثل کوئری زیر
select f1 as CostName, f2 as CostAmount  from table2
مهم نیست table2 چند فیلد دارد. نتیجه این کوئری همیشه حاوی دو فیلد است.


مطالب
مونیتور کردن میزان فضای خالی باقیمانده در سرور توسط اس کیوال سرور

با توجه به در حال اجرا بودن 24 ساعته‌ی سرویس SQL server agent، استفاده‌های ارزنده‌ای از آن می‌توان کرد. برای مثال هر از گاهی بررسی کند که آیا هارد سرور پر شده یا نه؟ و اگر بله (کمبود میزان فضای خالی به حد خطرناکی رسیده)، یک ایمیل خودکار به مسؤول مربوطه ارسال کند.
عمده‌ی مطالبی که در این مقاله بررسی خواهند شد همانند مطلب مونیتور کردن میزان مصرف CPU توسط اس کیوال سرور است و از تکرار آن‌ها در این‌جا صرفنظر خواهد شد (راه اندازی دیتابیس میل و همچنین تعریف یک job جدید که در مورد آن‌ها صحبت شد، همانند قبل است). تنها مطلب جدیدی که به آن اشاره خواهد شد، اسکریپت بررسی میزان فضای خالی و سپس ارسال ایمیل است که در یک job جدید همانند مقاله‌ی قبل باید به سرور اضافه شود. این اسکریپت به شرح زیر است:

DECLARE @DriveBenchmark INT
DECLARE @MachineName NVARCHAR(1000)
DECLARE @DiskFreeSpace INT
DECLARE @DriveLetter CHAR(1)
DECLARE @AlertMessage NVARCHAR(MAX)
DECLARE @MailSubject NVARCHAR(MAX)
DECLARE @NewLine CHAR(2)

SET @NewLine = CHAR(13) + CHAR(10)

SET @DriveBenchmark = 2048 -- 2GB
SET @MailSubject = 'Free space is low on ' + @@SERVERNAME
SET @AlertMessage = ''

IF EXISTS (
SELECT *
FROM tempdb..sysobjects
WHERE id = OBJECT_ID(N'[tempdb]..[#disk_free_space]')
)
DROP TABLE #disk_free_space

CREATE TABLE #disk_free_space
(
DriveLetter CHAR(1) NOT NULL,
FreeMB INTEGER NOT NULL
)

/* Populate #disk_free_space with data */
INSERT INTO #disk_free_space
EXEC MASTER..xp_fixeddrives


DECLARE DriveSpace CURSOR FAST_FORWARD
FOR
SELECT DriveLetter,
FreeMB
FROM #disk_free_space

OPEN DriveSpace
FETCH NEXT FROM DriveSpace INTO @DriveLetter, @DiskFreeSpace

WHILE (@@FETCH_STATUS = 0)
BEGIN
IF @DiskFreeSpace < @DriveBenchmark
BEGIN
SET @AlertMessage = @AlertMessage + 'Drive ' + @DriveLetter + ' on ' + @@SERVERNAME
+ ' has only ' + CAST(@DiskFreeSpace AS VARCHAR) + ' MB left.' + @NewLine
END

FETCH NEXT FROM DriveSpace INTO @DriveLetter, @DiskFreeSpace
END
CLOSE DriveSpace
DEALLOCATE DriveSpace

DROP TABLE #disk_free_space

IF @AlertMessage <> ''
BEGIN
EXECUTE msdb.dbo.sp_send_dbmail
@recipients = 'nasiri@site.net', -- Change This
@copy_recipients = 'Administrator@site.net', -- Change This
@Subject = @MailSubject,
@Body = @AlertMessage
,@importance = 'High'
END

بررسی اسکریپت فوق:

همه چیز از رویه‌ی سیستمی xp_fixeddrives شروع می‌شود. حاصل اجرای این رویه، دریافت میزان فضای خالی هر درایو موجود در سرور خواهد بود. همانطور که در اسکریپت نیز مشخص است، برای ذخیره سازی خروجی این رویه، یک جدول موقتی (disk_free_space) ایجاد شده و خروجی آن به درون این جدول اضافه خواهد شد. سپس یک cursor ایجاد شده و تک تک رکوردهای حاصل با مقدار متغیر DriveBenchmark که در اینجا 2 گیگابایت در نظر گرفته شده است، مقایسه می‌گردند. سپس هر کدام از رکوردها که کمتر از 2 گیگابایت بود، متغیر AlertMessage ما را مقدار دهی خواهد کرد. در پایان اگر این متغیر مقدار دهی شده بود، یعنی مشکل حاصل شده و نتیجه‌ی بررسی به صورت یک ایمیل ارسال می‌گردد. بدیهی است که در صورت نیاز مقدار متغیر DriveBenchmark و آرگومان‌های recipients و copy_recipients کد فوق باید اصلاح شوند.

برای استفاده از آن یک job جدید تعریف کنید که مثلا هر سه ساعت یکبار اجرا شده و این اسکریپت را فراخوانی نماید.