اصول پایگاه داده - اندیس ها (indices)
اجرای یک Script حاوی دستورات Go در سی شارپ
EF Code First #12
در مورد add or update
در مورد نحوه صحیح به روز رسانی اطلاعات و اشتباهات متداول مرتبط
در گزارشات Crosstab، ردیفهای یک گزارش، تبدیل به ستونهای آن میشوند؛ به همین جهت به آنها Pivot tables هم میگویند.
برای مثال فرض کنید که قصد دارید گزارش تعداد ساعت کارکرد را به ازای هر پروژه در طول چند ماه تعیین کنید. گزارش متداول از این نوع اطلاعات، یک لیست بلند بالای بیمفهوم است. این گزارش تشکیل شده از صدها رکورد به ازای کارکنان مختلف در پروژههای مختلف و ... هیچ ارزش آماری خاصی ندارد. یک گزارش بدوی است. زمانیکه این گزارش را تبدیل به حالت crosstab میکنیم، اولین ستون فقط یک شماره پروژه خواهد بود و ستونهای بعدی، مثلا نام ماهها و مقادیر آنها هم جمع کارکرد افراد بر روی یک پروژه مشخص.
مثال اول) تهیه گزارش Crosstab جمع هزینههای واحدهای مختلف به تفکیک ماه
کلاس هزینههای زیر را در نظر بگیرید که به کمک آن میتوان به ازای هر واحد یا دپارتمان در تاریخهای متفاوت، هزینهای را مشخص ساخت:
using System;
namespace Pivot.Sample1
{
public class Expense
{
public DateTime Date { set; get; }
public string Department { set; get; }
public decimal Expenses { set; get; }
}
}
با توجه به این کلاس، یک منبع داده آزمایشی جهت تهیه گزارشات، میتواند به صورت زیر باشد:
using System;
using System.Collections.Generic;
namespace Pivot.Sample1
{
public class ExpenseDataSource
{
public static IList<Expense> ExpensesDataSource()
{
return new List<Expense>
{
new Expense { Date = new DateTime(2011,11,1), Department = "Computer", Expenses = 100 },
new Expense { Date = new DateTime(2011,11,1), Department = "Math", Expenses = 200 },
new Expense { Date = new DateTime(2011,11,1), Department = "Physics", Expenses = 150 },
new Expense { Date = new DateTime(2011,10,1), Department = "Computer", Expenses = 75 },
new Expense { Date = new DateTime(2011,10,1), Department = "Math", Expenses = 150 },
new Expense { Date = new DateTime(2011,10,1), Department = "Physics", Expenses = 130 },
new Expense { Date = new DateTime(2011,9,1), Department = "Computer", Expenses = 90 },
new Expense { Date = new DateTime(2011,9,1), Department = "Math", Expenses = 95 },
new Expense { Date = new DateTime(2011,9,1), Department = "Physics", Expenses = 100 }
};
}
}
}
و اگر این لیست را به همین شکلی که هست نمایش دهیم، خروجی زیر را خواهیم داشت:
که ... خروجی مطلوبی نیست. در اینجا ما فقط 9 رکورد داریم؛ اما در عمل به ازای هر روز، یک رکورد میتواند وجود داشته باشد و این لیست طولانی، هیچ ارزش آماری خاصی ندارد. میخواهیم سرستونهای گزارش ما مطابق جدول زیر باشند:
یعنی اگر سه ماه را در نظر بگیریم با هر تعداد رکورد، فقط سه ردیف به ازای هر ماه باید حاصل شود و ستونهای دیگر هم نام بخشها یا واحدهای موجود باشند.
برای رسیدن به این خروجی Crosstab، میتوان کوئری LINQ زیر را به کمک امکانات گروه بندی اطلاعات آن تهیه کرد:
using System.Collections;
using System.Linq;
namespace Pivot.Sample1
{
public class PivotTable
{
public static IList ExpensesCrossTab()
{
return ExpenseDataSource
.ExpensesDataSource()
.GroupBy(t =>
new
{
Year = t.Date.Year,
Month = t.Date.Month
})
.Select(myGroup =>
new
{
//Year = myGroup.Key.Year,
Month = myGroup.Key.Month,
ComputerDepartment = myGroup.Where(x => x.Department == "Computer").Sum(x => x.Expenses),
MathDepartment = myGroup.Where(x => x.Department == "Math").Sum(x => x.Expenses),
PhysicsDepartment = myGroup.Where(x => x.Department == "Physics").Sum(x => x.Expenses)
})
.ToList();
}
}
}
که اینبار خروجی زیر را تولید میکند.
اگر علاقمند باشید که مثال فوق را در برنامهی LINQPad آزمایش کنید، این فایل را دریافت نموده و در آن برنامه باز نمائید.
مثال دوم) تهیه لیست Crosstab حضور و غیاب افراد در طول یک هفته
کلاس StudentStat را جهت ثبت اطلاعات حضور یک دانشجو، میتوان به شکل زیر تعریف کرد:
using System;
namespace Pivot.Sample2
{
public class StudentStat
{
public int Id { set; get; }
public string Name { set; get; }
public DateTime Date { set; get; }
public bool IsPresent { set; get; }
}
}
و بر همین اساس یک منبع داده فرضی جهت انجام گزارشات میتواند به نحو زیر تهیه شود:
using System;
using System.Collections.Generic;
namespace Pivot.Sample2
{
public class StudentsStatDataSource
{
public static IList<StudentStat> CreateMonthlyReportDataSource()
{
var result = new List<StudentStat>();
var rnd = new Random();
for (int day = 1; day < 6; day++)
{
for (int student = 1; student < 6; student++)
{
result.Add(new StudentStat
{
Id = student,
Date = new DateTime(2011, 11, day),
IsPresent = rnd.Next(-1, 1) == 0 ? true : false,
Name = "student " + student
});
}
}
return result;
}
}
}
خروجی این گزارش هم در این حالت ساده با 5 دانشجو و فقط 5 روز، 25 رکورد خواهد بود:
که ... این هم آنچنان از لحاظ آماری مطلوب و مفهوم نیست. میخواهیم سطرهای این گزارش همانند لیست واقعی حضورغیاب، فقط از نام افراد تشکیل شود و همچنین ستونها مثلا شماره یا نام روزهای یک هفته یا ماه باشند. مثلا به شکل زیر:
برای رسیدن به این خروجی Crosstab، مثلا میتوان از کوئری LINQ زیر کمک گرفت که بر اساس شماره دانشجویی اطلاعات را گروه بندی کرده است:
using System.Collections;
using System.Linq;
namespace Pivot.Sample2
{
public class PivotTable
{
public static IList StudentsStatCrossTab()
{
return StudentsStatDataSource
.CreateWeeklyReportDataSource()
.GroupBy(x =>
new
{
x.Id
})
.Select(myGroup =>
new
{
myGroup.Key.Id,
Name = myGroup.First().Name,
Day1IsPresent = myGroup.Where(x => x.Date.Day == 1).First().IsPresent,
Day2IsPresent = myGroup.Where(x => x.Date.Day == 2).First().IsPresent,
Day3IsPresent = myGroup.Where(x => x.Date.Day == 3).First().IsPresent,
Day4IsPresent = myGroup.Where(x => x.Date.Day == 4).First().IsPresent,
Day5IsPresent = myGroup.Where(x => x.Date.Day == 5).First().IsPresent,
PresentsCount = myGroup.Where(x => x.IsPresent).Count(),
AbsentsCount = myGroup.Where(x => !x.IsPresent).Count()
})
.ToList();
}
}
}
و این کوئری خروجی زیر را تولید میکند که از هر لحاظ نسبت به لیست قبلی مفهومتر است:
فایل LINQPad این مثال را میتوانید از اینجا دریافت کنید.
فعال سازی Subgrid
در اینجا مواردی را که باید جهت فعال سازی subgrid به تعاریف اولیهی jqGrid اضافه کرد، مشاهده میکنید:
$('#list').jqGrid({ caption: "آزمایش یازدهم", // ... jsonReader: { // ... subgrid: { root: "Rows", repeatitems: true, cell: "RowCells" } }, // ... subGrid: true, subGridModel: [{ name: ['شرکت', 'آدرس', 'کد پستی', 'شهر', 'کشور', 'تلفن', 'وب سایت'], width: [100, 100, 100, 100, 100, 100, 100], align: ['center', 'center', 'center', 'center', 'center', 'center', 'center'], params: ['@(StronglyTyped.PropertyName<Product>(x=>x.Name))'] }], subGridOptions:{ reloadOnExpand : false //load only once }, subGridUrl: '@Url.Action("GetGetSupplierData", "Home")' });
با تنظیم subGrid: true نمایش ستون + داری که در تصویر فوق مشخص است، انجام میشود.
subGridModel بیانگر ساختار اطلاعاتی است که قرار است نمایش داده شوند.
آرایه name، نام سر ستونها را مشخص میکند.
آرایه width، عرض ستونهای زیرگرید را مقدار دهی خواهد کرد.
آرایه align محل و سمت قرارگیری هر یک از مقادیر سلولها را تعیین میکند.
آرایه params اختیاری است. زمانیکه کاربر بر روی یک + ستون subgrid، برای باز شدن این زیرگرید کلیک میکند، صرفا Id ردیف به سرور ارسال میشود. اگر در این بین میخواهید، خاصیت خاصی از گرید اصلی نیز به سرور ارسال شود، آرایه params را مقدار دهی کنید. برای نمونه در اینجا Name ردیف انتخاب شده نیز به ارسال ارسال خواهد شد (برگه شبکه شکل فوق).
subGridOptions یک سری تنظیمات اضافه را به همراه دارد. اگر میخواهید اطلاعات زیرگرید فقط یکبار بارگذاری شود و با هربار کلیک کاربر از سرور دریافت نگردد، خاصیت reloadOnExpand آنرا false کنید.
subGridUrlآدرسی که تامین کننده اطلاعات JSON زیرگرید میباشد.
در این حالت، کدهای سمت سرور بازگشت اطلاعات زیر گرید به شکل زیر میباشد:
public ActionResult GetGetSupplierData(int id, string name) { var list = ProductDataSource.LatestProducts; var products = list.Where(x => x.Id == id).ToList(); if (!products.Any()) return Json(null, JsonRequestBehavior.AllowGet); var productsData = new JqGridData { Rows = (products.Select(product => new JqGridRowData { Id = product.Id, RowCells = new List<string> { product.Supplier.CompanyName, product.Supplier.Address, product.Supplier.PostalCode, product.Supplier.City, product.Supplier.Country, product.Supplier.Phone, product.Supplier.HomePage } })).ToList() }; return Json(productsData, JsonRequestBehavior.AllowGet); }
کدهای کامل این مثال را از اینجا میتوانید دریافت کنید
jqGrid11.zip
امکان انجام محاسبات سمت کلاینت در EF Core
var test1 = context.Blogs .Where(blog => String.Compare(blog.Url, "A", StringComparison.Ordinal) > 0) .ToList(); // SELECT [blog].[BlogId], [blog].[Url] // FROM [Blogs] AS [blog]
var test2 = context.Blogs .Where(blog => String.Compare(blog.Url, "B") > 0) .ToList(); // SELECT [blog].[BlogId], [blog].[Url] // FROM [Blogs] AS [blog] // WHERE [blog].[Url] > N'B'
var test3 = context.Blogs .Where(blog => blog.Url.Equals("C", StringComparison.OrdinalIgnoreCase)) .ToList(); // SELECT [blog].[BlogId], [blog].[Url] // FROM [Blogs] AS [blog]
var test3_1 = context.Blogs .Where(blog => blog.Url.Equals("C_1")) .ToList(); // SELECT [blog].[BlogId], [blog].[Url] // FROM [Blogs] AS [blog] // WHERE [blog].[Url] = N'C_1'
var test4 = context.Blogs .Where(blog => blog.Url.StartsWith("D")) .ToList(); // SELECT [blog].[BlogId], [blog].[Url] // FROM [Blogs] AS [blog] // WHERE [blog].[Url] LIKE N'D' + N'%' AND (LEFT([blog].[Url], LEN(N'D')) = N'D')
var test5 = context.Blogs .Where(blog => EF.Functions.Like(blog.Url, "S_i%")) .ToList(); // SELECT [blog].[BlogId], [blog].[Url] // FROM [Blogs] AS [blog] // WHERE [blog].[Url] LIKE N'S_i%'
var test6 = context.Blogs .Where(blog => blog.Url.ToUpper() == "E") .ToList(); // SELECT [blog].[BlogId], [blog].[Url] // FROM [Blogs] AS [blog] // WHERE UPPER([blog].[Url]) = N'E'
var test7 = context.Blogs .Where(blog => blog.Url.ToUpperInvariant() == "F") .ToList(); // SELECT [blog].[BlogId], [blog].[Url] // FROM [Blogs] AS [blog]
public class OrderItem { public int Quantity { get; set; } public decimal UnitPrice { get; set; } public decimal Discount { get; set; } } public class InvoiceItemGenerator { private readonly OrderItem _orderItem; public InvoiceItemGenerator(OrderItem orderItem) { _orderItem = orderItem; } public dynamic Generate() { dynamic invoiceItem = new ExpandoObject(); invoiceItem.Amount = _orderItem.Quantity * _orderItem.UnitPrice - _orderItem.Discount; return invoiceItem; } }
public class OrderItem { public int Quantity { get; set; } public decimal UnitPrice { get; set; } public decimal Discount { get; set; } public decimal GetFinalAmount() { return Quantity * UnitPrice - Discount; } } public class InvoiceItemGenerator { private readonly OrderItem _orderItem; public InvoiceItemGenerator(OrderItem orderItem) { _orderItem = orderItem; } public dynamic Generate() { dynamic invoiceItem = new ExpandoObject(); invoiceItem.Amount = _orderItem.GetFinalAmount(); return invoiceItem; } }
جمع بندی
- CUME_DIST:
CUME_DIST( ) OVER ( [ partition_by_clause ] order_by_clause )
Create Table TestCUME_DIST (SalesOrderID int not null, OrderQty smallint not null, ProductID int not null ); GO Insert Into TestCUME_DIST Values (43663,1,760),(43667,3,710),(43667,1,773), (43667,1,775),(43667,1,778),(43669,1,747), (43670,1,709),(43670,2,710),(43670,2,773),(43670,1,776)
SELECT SalesOrderID, OrderQty, CUME_DIST() OVER(ORDER BY SalesOrderID) AS [CUME_DIST] FROM TestCUME_DIST ORDER BY [CUME_DIST] DESC
*** برای بدست آوردن CUME_DIST سطر پنجم نیز خواهیم داشت:
Rows=(c1+c2)/c3 بنابراین خواهیم داشت: 0/6=10/(5+1)=Rows
مثال دوم : ابتدا Script زیر را اجرا نمایید:
SELECT SalesOrderID, OrderQty, ProductID, CUME_DIST() OVER(PARTITION BY SalesOrderID ORDER BY ProductID ) AS [CUME_DIST] FROM TestCUME_DIST WHERE SalesOrderID IN (43670, 43669, 43667, 43663) ORDER BY SalesOrderID DESC, [CUME_DIST] DESC
همانگونه که ملاحظه میکنید، در این مثال، خروجی، براساس SalesOrderID به چهار گروه تقسیم میشود و عملیات مرتب سازی روی فیلد ProductID انجام می گیرد، بنابراین CUME_DIST، روی هر گروه بر روی فیلد ProductID محاسبه میشود.
گروه اول : نحوه محاسبه Cume_DIST سطر اول:
سوال:چه تعداد از مقادیر ProductID آن برابر 776 میباشد؟
جواب: فقط مقدار سطر اول، بنابراین خواهیم داشت C1=1
سوال: چه تعداد از مقادیر کوچکتر از ProductID=776 میباشد؟
جواب: مقدار سه سطر، در واقع مقادیر سطر دوم،سوم و چهارم کوچکتر از مقدار سطر اول میباشند، c2=3
سوال: تعداد کل سطرهای گروه اول چه مقدار میباشد؟
جواب: 4سطر
بنابراین برای بدست آوردن CUME_DIST سطر اول خواهیم داشت:
1=4/(1+3)=Rows
محاسبه سطر دوم از گروه اول بدون شرح:
0/75=4/(1+2)=Rows
امیدوارم مفید واقع شده باشد.
در این قسمت میخواهیم بیشتر روی مفاهیم اعمال شرط بر روی خروجی عمل واکشی کار کنیم. برای شروع کوئری سادهی زیر را اجرا و خروجی آن را تفسیر میکنیم.
Select From [Adventure Works]
همان طور که مشاهده میکنید، خروجی یک عدد میباشد. بدون نام ستون یا ردیف؟!
بهخاطر بیاورید که هر Cube در SSAS دارای یک Measure پیش فرض بود که در صورت عدم اعلام نام یک Measure در کوئری، SSAS به صورت پیش فرض مقدار این Measure را بر میگرداند. خوب؛ نام ستون و سطر چرا ذکر نشده است؟
به دلیل عدم اعلام صریح نام سطر و ستون در کوئری بالا، SSAS نام ستون و سطر خاصی را نمیتواند نمایش دهد.
با بررسی کوئری زیر به درک بیشتری از شاخص (Measure) پیش فرض Cube دست پیدا خواهید کرد.
Select From [Adventure Works] Where ( [Measures].[Reseller Sales Amount] )
خروجی همچنان مانند بالا میباشد اما در این حالت اعلام شده است که از کدام شاخص باید واکشی انجام شود. دلیل خروجی مشابه، یکسان بودن شاخص پیش فرض و شرط اعلام شده میباشد. به بیان دیگر [Measures].[Reseller Sales Amount] در [Adventure Works] به عنوان شاخص پیش فرض معرفی شده است و با اجرای کوئری زیر عملا شرط واکشی برای یک شاخص متفاوت اعمال شده است.
Select From [Adventure Works] Where [Measures].[Internet Sales Amount]
کوئری زیر را اجرا کنید:
Select [Measures].[Internet Sales Amount] on columns From [Adventure Works]
تنها تفاوت دو کوئری بالا ، در آوردن نام ستون می باشد . زیرا در هر دو سرجمع ، یک شاخص واکشی می گردد .
کوئری زیر را اجرا کنید:
select [Measures].[Internet Sales Amount] on columns From [Adventure Works] Where [Measures].[Internet Sales Amount]
این کوئری با خطا مواجه می شود . زیرا در آن کوئری در یک Axis و در شرط ، اعمال انتخاب شاخص شده است که این مورد فقط می بایستی در یکی از این دو قسمت رخ دهد .
و همچنین در صورت انتخاب دو شاخص متفاوت نیز با خطا برخورد خواهیم کرد.
Select [Measures].[Internet Sales Amount] on columns From [Adventure Works] Where [Measures].[Reseller Sales Amount]
به عبارت دیگر نمیتوان در خواست فیلتر کردن کوئری را برروی شاخص 1 داد؛ در صورتیکه میخواهیم شاخص 2 را واکشی کنیم. اعمال شرط برای واکشی اطلاعات از شاخص، پیش فرض نوشتن این شرط لازم نمیباشد؛ زیرا این شاخص به صورت پیش فرض انتخاب شدهاست.
select { [Product].[Product Categories].[Category], [Product].[Product Categories] }on columns From [Adventure Works] Where [Measures].[Reseller Sales Amount]
بنابراین کوئری بالا و کوئری زیر یکسان عمل خواهند کرد:
select { [Product].[Product Categories].[Category], [Product].[Product Categories] }on columns From [Adventure Works]
حال میخواهیم سرجمع فروش نمایندگان فروش محصولات در کشور کانادا را بر اساس دسته بندی محصولات داشته باشیم . برای این منظور کوئری زیر را مینویسیم:
Select { [Product].[Product Categories].[Category], [Product].[Product Categories] } on columns From [Adventure Works] Where [Customer].[Customer Geography].[Country].[Canada]
با اعمال شرط کشور کانادا، عملا خروجی فروش نمایندگان فروش در کانادا بر اساس دسته بندی محصولات واکشی میگردد. کمی به خروجی دقت نمایید. مبلغ سرجمع برابر مبلغ کل فروش اینترنتی میباشد که در کوئریهای قبلی بدست آوردیم؟!
خروجی این کوئری مشکوک به نظر می رسد . زیرا سرجمع مبالغ فروش نمایندگان فروش برای کانادایی ها برابر کل فروش نمایندگان فروش می باشد .آیا کانادایی ها تمام خرید را انجام داده اند؟ خیر .
دلیل این اشکال در این است که هیچ گونه ارتباطی بین بعد مشتری و شاخص پیش فرض در سیستم وجود ندارد .
مشکل کوئری بالا در این کوئری با تغییر بعد در قسمت اعمال شرط برطرف شده؛ اکنون خروجی حقیقی مشاهده می شود .
Select { [Product].[Product Categories].[Category], [Product].[Product Categories] }on columns From [Adventure Works] Where [Sales Territory].[Sales Territory].[Country].[Canada]
حال اگر بخواهیم دو شرط را به صورت همزمان داشته باشیم به صورت زیر عمل خواهیم کرد :
Select { [Product].[Product Categories].[Category], [Product].[Product Categories] } on columns From [Adventure Works] Where ( [Customer].[Customer Geography].[Country].[Canada], [Measures].[Internet Sales Amount] )
در کوئری بالا سرجمع فروش اینترنتی توسط مشتریان کانادایی بدست آمده است.
البته میتوان کوئری فوق را به صورت زیر هم نوشت و در این حالت نام ردیف هم در خروجی قابل مشاهده میباشد و البته دیگر نیازی به اعمال شرط، روی نام شاخص نمیباشد. زیرا اعمال شرط در ردیف انجام شده است.
Select { [Product].[Product Categories].[Category], [Product].[Product Categories] } on columns, [Measures].[Internet Sales Amount] On rows From [Adventure Works] Where ( [Customer].[Customer Geography].[Country].[Canada] )
حال اگر بخواهیم فروش اینترنتی را برای استرالیا و کانادا داشته باشیم به صورت زیر عمل میکنیم .
Select { [Product].[Product Categories].[Category],[Product].[Product Categories] } on columns From [Adventure Works] Where ( [Customer].[Customer Geography].[Country].[Canada], [Customer].[Customer Geography].[Country].[Australia], [Measures].[Internet Sales Amount] )
در اینجا ما نیاز داریم میزان فروش اینترنتی کانادا و استرالیا را برای انواع محصولات بدست آوریم ، اما نحوه استفاده از دو ساختار سلسله مراتبی مرتبط با یک دایمنشن را درست رعایت نکردهایم .بنابر این کوئری زیر را اجرا خواهیم کرد :
Select { [Product].[Product Categories].[Category], [Product].[Product Categories] } on columns From [Adventure Works] Where ( { [Customer].[Customer Geography].[Country].[Canada], [Customer].[Customer Geography].[Country].[Australia] }, [Measures].[Internet Sales Amount] )
که همان کوئری بالا می باشد با این تفاوت که از {} استفاده شده است .
درابتدا میزان فروش نمایندگان فروش در انگلستان را بدست میآوریم:
Select { [Product].[Product Categories].[Category], [Product].[Product Categories] } on columns From [Adventure Works] Where [Sales Territory].[Sales Territory].[Country].[United Kingdom]
و برای بدست آوردن فروش اینترنتی تمام کشور ها به جز انگلستان بر اساس دسته بندی محصولات کوئری زیر را خواهیم نوشت :
Select { [Product].[Product Categories].[Category], [Product].[Product Categories] } on columns From [Adventure Works] Where [Sales Territory].[Sales Territory].[Country] - [Sales Territory].[Sales Territory].[Country].[United Kingdom]
البته از تابع Except هم میتوان به صورت زیر استفاده کرد
Select { [Product].[Product Categories].[Category], [Product].[Product Categories] } on columns From [Adventure Works] Where except( [Sales Territory].[Sales Territory].[Country], [Sales Territory].[Sales Territory].[Country].[United Kingdom] )
عملگر منها مشابه except کار میکند.