در این قسمت میخواهیم بیشتر روی مفاهیم اعمال شرط بر روی خروجی عمل واکشی کار کنیم. برای شروع کوئری سادهی زیر را اجرا و خروجی آن را تفسیر میکنیم.
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 کار میکند.
- Lead Function:
LEAD ( scalar_expression [ ,offset ] , [ default ] ) OVER ( [ partition_by_clause ] order_by_clause )
- Scalar_expression: در Scalar_expression، نام یک فیلد یا ستون درج میشود، و مقدار برگشتی فیلد مورد نظر، به مقدار تعیین شده offset نیز بستگی دارد. خروجی Scalar_expression فقط یک مقدار است.
- offset: منظور از Offset در این Syntax همانند عملکرد Offset در Syntax مربوط به Over میباشد. یعنی هر عددی برای offset در نظر گرفته شود، بیانگر نقطه آغازین سطر بعدی یا قبلی نسبت به سطر جاری است. به بیان دیگر، عدد تعیین شده در Offset به Sql server میفهماند چه تعداد سطر را در محاسبه در نظر نگیرد.
- Default: زمانی که برای Offset مقداری را تعیین مینمایید، SQL Server به تعداد تعیین شده در Offset، سطرها را در نظر نمیگیرد، بنابراین مقدار خروجی Scalar_expression بطور پیش فرض Null در نظر گرفته میشود، چنانچه بخواهید، مقداری غیر از Null درج نمایید، میتوانید مقدار دلخواه را در قسمت Default وارد کنید.
- (OVER ( [ partition_by_clause ] order_by_clause : در بخش اول بطور کامل توضیح داده شده است.
Create Table TestLead_LAG (SalesOrderID int not null, SalesOrderDetailID int not null , OrderQty smallint not null); GO Insert Into TestLead_LAG Values (43662,49,1),(43662,50,3),(43662,51,1), (43663,52,1),(43664,53,1),(43664,54,1), (43667,77,3),(43667,78,1),(43667,79,1), (43667,80,1),(43668,81,3),(43669,110,1), (43670,111,1),(43670,112,2),(43670,113,2), (43670,114,1),(43671,115,1),(43671,116,2)
مثال:قصد داریم در هر سطر مقدار بعدی فیلد SalesOrderDetailID در فیلد دیگری به نام LeadValue نمایش دهیم، بنابراین Script زیر را ایجاد میکنیم:SELECT s.SalesOrderID,s.SalesOrderDetailID,s.OrderQty, LEAD(SalesOrderDetailID) OVER (ORDER BY SalesOrderDetailID) LeadValue FROM TestLead_LAG s WHERE SalesOrderID IN (43670, 43669, 43667, 43663) ORDER BY s.SalesOrderID,s.SalesOrderDetailID,s.OrderQtyخروجی بصورت زیر خواهد بود:
مطابق شکل، براحتی واضح است، که در هر سطر مقدار بعدی فیلد SalesOrderDetailID در فیلد LeadValue درج و نمایش داده میشود. فقط در سطر 10، چون مقدار بعدی برای فیلد SalesOrderDetailID وجود ندارد، SQL Server مقدار فیلد LeadValue را، Null در نظر میگیرد.در این مثال فقط از آرگومان Scalar_expression، استفاده کردیم، و Offset و Default را مقدار دهی ننمودیم، بنابراین SQL Server بطور پیش فرض هیچ سطری را حذف نمیکند و مقدار Default را Null در نظر میگیرد.مثال دوم: قصد داریم در هر سطر مقدار دو سطر بعدی فیلد SalesOrderDetailID را در فیلد LeadValue نمایش دهیم، و در صورت وجود نداشتن مقدار فیلد SalesOrderDetailID، مقدار پیش فرض صفر ،در فیلد LeadValue قرار دهیم،بنابراین Script آن بصورت زیر خواهد شد:SELECT s.SalesOrderID,s.SalesOrderDetailID,s.OrderQty, LEAD(SalesOrderDetailID,2,0) OVER (ORDER BY SalesOrderDetailID) LeadValue FROM TestLead_LAG s WHERE SalesOrderID IN (43670, 43669, 43667, 43663) ORDER BY s.SalesOrderID,s.SalesOrderDetailID,s.OrderQtyخروجی:
در صورت مسئله بیان کرده بودیم، در هر سطر،مقدار فیلد SalesOrderDetailID دو سطر بعدی، را نمایش دهیم، بنابراین مقداری که برای Offset در نظر میگیریم، برابر دو خواهد بود، سپس گفته بودیم، چنانچه در هر سطر مقدار فیلد SalesOrderDetailID وجود نداشت،بجای مقدار پیش فرض Null،از مقدار صفر استفاده شود، بنابراین به Default مقدار صفر را نسبت دادیم.LEAD(SalesOrderDetailID,2,0)در شکل، مطابق صورت مسئله، مقدار فیلد LeadValue سطر اول برابر است با 78،به بیان سادهتر برای بدست آوردن مقدار فیلد LaedValue هر سطر، میبایست هر سطر را به علاوه 2 (Offset) نماییم، تا سطر بعدی بدست آید، سپس مقدار SalesOrderDetailID را در فیلد LeadValue قرار میدهیم.به سطر 9 و 10 توجه نمایید، که مقدار فیلد LeadValue آنها برابر با صفر است، واضح است، سطر 10 + 2 برابر است با 12( 10+2=12 )، چنین سطری در خروجی نداریم، بنابراین بطور پیش فرض مقدار LeadVaule توسط Sql Server برابر Null در نظر گرفته میشود، اما نمیخواستیم، که این مقدار Null باشد، بنابراین به آرگومان Default مقدار صفر را نسبت دادیم، تا SQL Server ، به جای استفاده از Null، مقدار در نظر گرفته شده صفر را استفاده نماید.اگر چنین فانکشنی وجود نداشت، برای شبیه سازی آن میبایست از Join روی خود جدول استفاده مینمودیم، و یکسری محاسابت دیگر، که کار را سخت مینمود، مثال دوم را با Script زیر میتوان شبیه سازی نمود:WITH cteLead AS ( SELECT SalesOrderID,SalesOrderDetailID,OrderQty, ROW_NUMBER() OVER (ORDER BY SalesOrderDetailID) AS sn FROM TestLead_LAG WHERE SalesOrderID IN (43670, 43669, 43667, 43663) ) SELECT m.SalesOrderID, m.SalesOrderDetailID, m.OrderQty, case when sLead.SalesOrderDetailID is null Then 0 Else sLead.SalesOrderDetailID END as leadvalue FROM cteLead AS m LEFT OUTER JOIN cteLead AS sLead ON sLead.sn = m.sn+2 ORDER BY m.SalesOrderID, m.SalesOrderDetailID, m.OrderQtyجدول موقتی ایجاد نمودیم، که ROW_Number را در آن اضافه کردیم، سپس جدول ایجاد شده را با خود Join کردیم، و گفتیم، که مقدار فیلدLeadValue هر سطر برابر است با مقدار فیلد SalesOrderDetailID دو سطر بعد از آن. و با Case نیز مقدار پیش فرض را صفر در نظر گرفتیم.
- LAG Function:
این فانکشن نیز در SQL Server 2012 ارائه شده است، و امکان دسترسی، به Dataهای سطر قبلی نسبت به سطر جاری را در نتیجه یک پرس و جو (Query)، ارائه میدهد. بدون آنکه از Self-join استفاده نمایید،Syntax آن شبیه به فانکشن Lead میباشد و بصورت زیر است:LAG (scalar_expression [,offset] [,default]) OVER ( [ partition_by_clause ] order_by_clause )Syntax مربوط به فانکشن LAG را شرح نمیدهم، بدلیل آنکه شبیه به فانکشن Lead میباشد، فقط تفاوت آن در Offset است، Offset در فانکشن LAG روی سطرهای ماقبل سطر جاری اعمال میگردد.مثال دوم را برای حالت LAG Function شبیه سازی مینماییم:SELECT s.SalesOrderID,s.SalesOrderDetailID,s.OrderQty, LAG(SalesOrderDetailID,2,0) OVER (ORDER BY SalesOrderDetailID) LAGValue FROM TestLead_LAG s WHERE SalesOrderID IN (43670, 43669, 43667, 43663) ORDER BY s.SalesOrderID,s.SalesOrderDetailID,s.OrderQty goخروجی :
همانطور که گفتیم، LAG Function عکس LEAD Function میباشد. یعنی مقدار فیلد LAGValue سطر جاری برابر است با مقدار SalesOrderDetailID دو سطر ما قبل خود.مقدار فیلد LAGValue دو سطر اول و دوم نیز برابر صفر است، چون دو سطر ماقبل آنها وجود ندارد، و مقدار صفر نیز بدلیل این است که Default را برابر صفر در نظر گرفته بودیم.مثال: در این مثال از Laed Function و LAG Function بطور همزمان استفاده میکنیم، با این تفاوت، که از گروه بندی نیز استفاده شده است:Script زیر را اجرا نمایید:SELECT s.SalesOrderID,s.SalesOrderDetailID,s.OrderQty, Lead(SalesOrderDetailID) OVER (PARTITION BY SalesOrderID ORDER BY SalesOrderDetailID) LeadValue, LAG(SalesOrderDetailID) OVER (PARTITION BY SalesOrderID ORDER BY SalesOrderDetailID) LAGValue FROM TestLead_LAG s WHERE SalesOrderID IN (43670, 43669, 43667, 43663) ORDER BY s.SalesOrderID,s.SalesOrderDetailID,s.OrderQty goخروجی:با بررسی هایی که در مثالهای قبل نمودیم،خروجی زیر را میتوان براحتی تشخیص داد، و توضیح بیشتری نمیدهم.موفق باشید.
<input type="button" onclick="startTrace('Some Text')" value="startTrace" /> <input type="button" onclick="startError()" value="test Error" /> <script type="text/javascript"> function startTrace(str) { return method1(100, 200); } function method1(arg1, arg2) { return method2(arg1 + arg2 + 100); } function method2(arg1) { var var1 = arg1 / 100; return method3(var1); } function method3(arg1) { console.trace(); var total = arg1 * 100; return total; } function testCount() { // do something console.count("testCount() Calls Count ."); } function startError() { testError(); } function testError() { var errorObj = new Error(); errorObj.message = "this is a test error"; console.exception(errorObj); } function testFunc() { var t = 0; for (var i = 0; i < 100; i++) { t += i; } } </script>
- console.log(object[,object,...])
این دستور یک پیغام در کنسول چاپ میکند .
console.log("This is a log message!");
این دستور را میتوانیم به شکلهای مختلفی فراخوانی کنیم .
مثلا :
console.log(1 , "+" , 2 , "=", (1+2));
در این دستور میتوانیم از چند حرف جایگزین هم استفاده کنیم .
مثال :
console.log("Firebug 1.0 beta was %s in December %i.","released",2006);
اگر در رشتهی مورد نظر ، یک شیء ( تابع ، آرایه ، ... ) برای جایگزین %o ارسال کنیم ، در خروجی آن شیء بصورت لینک نمایش داده میشود که با کلیک بروی آن ، فایرباگ آن شیء را در تب مناسبش Inspect میکند .
مثال :
console.log("this is a test functin : %o",testFunc);
نتیجه :
و زمانی که بروی لینک testFunc کلیک کنیم :
یک ترفند : بوسیله جایگزین %o توانستیم به تابع مورد نظر لینک بدهیم . اگر بجای جایگزین %o از %s استفاده کنیم ، میتوانیم بدنهی تابع را ببینیم :
console.log("this is a test functin : %s",testFunc);
توسط جایگزین %c هم میتوانید خروجی را فرمت کنید .
console.log("%cThis is a Style Formatted Log","color:green;text-decoration:underline;");
نتیجه :
- console.debug(object[, object, ...])
- console.info(object[, object, ...])
- console.warn(object[, object, ...])
- console.error(object[, object, ...])
مشابه با دستور log عمل میکنند با این تفاوت که خروجی را با استایل متفاوتی نمایش میدهند .
همچنین هر یک از این دستورات ، توسط دکمههای همنام در کنسول قابل فیلتر شدن هستند .
- console.assert(expression[, object, ...])
چک میکند که عبارت ارسال شده true هست یا نه . اگر true نبود ، پیغام وارد شده را چاپ و یک استثناء ایجاد میکند .
console.assert(1==1,"this is a test error"); console.assert(1!=1,"this is a test error");
نتیجه :
- console.clear()
- console.dir(object)
- console.dirxml(node)
- console.profile([title])
- console.profileEnd()
- console.trace()
با این متد میتوانید پی ببرید که از کجا و توسط چه متدهایی برنامه به قسمت trace رسیده . برای درک بهتر مجددا اسکریپت صفحهی تست این مقاله را بررسی کنید ( جایی که متد trace قرار داده شده است ) .
اکنون صفحهی تست را باز کنید و بروی دکمهی startTrace کلیک کنید . خروجی ظاهر شده در کنسول را از پایین به بالا بررسی کنید .
حتما متوجه شدید که متد method3 چگونه در کدهایمان فراخوانی شده است !؟
ابتدا با کلیک بروی دکمهی startTrace ، متد startTrace اجرا شده و به همین ترتیب متد startTrace متد method1 ، متد method1 هم متد method2 و در نهایت method2 متد method3 را فراخوانی کرده است .
دستور trace زمانی که در حال بررسی کدهای برنامه نویسان دیگر هستید ، بسیار میتواند به شما کمک کند .
- console.group(object[, object, ...])
با این دستور میتوانید لاگهای کنسول را بصورت تو در تو گروه بندی کنید .
console.group("Group1"); console.log("Log in Group1"); console.group("Group2"); console.log("Log in Group2"); console.group("Group3"); console.log("Log in Group3");
- console.groupCollapsed(object[, object, ...])
این دستور معادل دستور قبلی است با این تفاوت که هنگام ایجاد ، گروه را جمع میکند .
- console.groupEnd()
به آخرین گروه بندی ایجاد شده خاتمه میدهد .
- console.time(name)
یک تایمر با نام داده شده ایجاد میکند . زمانی که نیاز دارید زمان طی شده بین 2 نقطه را اندازه گیری کنید ، این تابع مفید خواهد بود .
- console.timeEnd(name)
تایمر همنام را متوقف و زمان طی شده را چاپ میکند .
console.time("TestTime"); var t = 1; for (var i = 0; i < 100000; i++) { t *= (i + t) } console.timeEnd("TestTime");
- console.timeStamp()
توضیحات کامل را از اینجا دریافت کنید .
- console.count([title])
تعداد دفعات فراخوانی شدن کدی که این متد در آنجا قرار دارد را چاپ میکند .
البته ظاهرا در ورژن 10.0.1 که بنده با آن کار میکنم ، این دستور بی عیب کار نمیکند . زیرا بجای آنکه در هربار فراخوانی ، در همان خط تعداد فراخوانی را نمایش بدهد ، فقط اولین لاگ را آپدیت میکند .
- console.exception(error-object[, object, ...])
یک پیغام خطا را به همراه ردیابی کامل اجرای کدها تا زمان رویداد خطا ( مانند متد trace ) چاپ میکند .
در صفحهی تست این متد را اجرا کنید :
startError();
توجه کنید که ما برای مشاهدهی عملکرد صحیح این دستور ، آن را در تابع testError قرار دادیم و بوسیله تابع startError آن فراخوانی کردیم .
- console.table(data[, columns])
بوسیله این دستور میتوانید مجموعه ای از اطلاعات را بصورت جدول بندی نمایش بدهید .
این متد از متدهای جدیدی است که در فایرباگ قرار داده شده است .
برای اطلاعات بیشتر به اینجا مراجعه کنید .
این توابع معادل توابع همنامشان در خط فرمان هستند که در قسمت قبل با عملکردشان آشنا شدیم .
قبل از بررسی توابع، Script زیر را اجرا مینماییم، که شامل جدولی به نام Testو درج چند رکورد درون آن میباشد:
CREATE TABLE Test (ID INT, Product VARCHAR(100), Price INT, Color VARCHAR(100)) GO INSERT INTO Test SELECT 1, 'Toy', 100, 'Black' UNION ALL SELECT 2, 'Pen', 100, 'Black' UNION ALL SELECT 3, 'Pencil', 100, 'Blue' UNION ALL SELECT 4, 'Pencil', 100, 'Red' UNION ALL SELECT 5, 'Pencil', 200, 'Yellow' UNION ALL SELECT 6, 'Cup', 300, 'Orange' UNION ALL SELECT 7, 'Cup', 400, 'Brown' GO
ROW_NUMBER () OVER ([<partition_by_clause>] <order_by_clause>)
در ابتدا Query زیر را اجرا نمایید:
Select *, ROW_NUMBER() OVER ( ORDER BY Price DESC) AS RN from Test
- لازم به یادآوری است که استفاده از Order by در Syntax تابع Row_Number الزامی میباشد.
برای درک بیشتر Query زیر را اجرا نمایید:
Select *,ROW_NUMBER() OVER (PARTITION BY Product ORDER BY Price DESC) AS RN from Test
همانطور که در شکل مشاهده مینمایید، در ابتدا، جدول براساس فیلد Product، دسته بندی (Group by) شده است و سپس اعداد ترتیبی روی هر Group by بصورت جداگانه اعمال شده است.
تابع ()RANK
از تابع فوق در جهت رتبه بندی نمودن فیلدهای یک جدول استفاده میشود و Syntax آن بصورت زیر میباشد:
RANK () OVER ([<partition_by_clause>] <order_by_clause>)
ابتدا Query زیر را اجرا مینماییم:
Select *,RANK() over (ORDER BY Price ) AS RANK from Test
یادآوری: زمانی که دورن Order by ترتیب صعودی یا نزولی بودن را تعیین نکنیم، Order by بصورت پیش فرض صعودی میباشد.
همانطور که در شکل مشاهده مینمایید،رتبه بندی انجام شده به ترتیب نمیباشد، و برای مقادیر تکراری فیلد Price از Rank یکسانی استفاده شده است. نکته دیگر این که بین اعداد مشاهده شده در فیلد Rank نیز gap ایجاد میشود. به عبارت دیگر عمده تفاوت تابع Rank با تابع Row_Number همین مواردی است که بیان شده است.
در Syntax تابع Rank نیز کلمه Partition هم وجود دارد، که در جهت Group by فیلد یا فیلدهای خاصی استفاده میشود، و رتبه بندی نیز در این حالت روی Group by انجام میگردد.
برای درک بهتر Query زیر را اجرا نمایی:
Select *,RANK() over (Partition by Product ORDER BY Price Desc) AS RANK from Test
خروجی بصورت زیر خواهد بود:
همانطور که در شکل مشاهده مینمایید، رتبه بندی روی هر Group by بصورت جداگانه اعمال شده است.
تابع Dense_Rank
این تابع نیز همانند تابع Rank عمل میکند، با این تفاوت که هیچ gap ی بین اعداد آن رخ نمیدهد.
با جرای Query زیر خواهیم داشت:
Select *,dense_RANK() over (ORDER BY Price ) AS dense_RANK from Test
خروجی بصورت زیر خواهد بود:
همانطور که ملاحظه مینماییدهیچ gap ی بین اعداد Rank ایجاد نشده است.
و برای استفاده از Partition، درتابع Dense_Rank همانند تابعهای دیگر میباشد.
تابع NTILE:
این تابع نیز مانند توابع بالا در جهت رتبه بندی استفاده میشود، و بوسیله تابع فوق شما میتوانید رکوردهای جدول خود را به تعداد گروههای دلخواه تقسیم نمایید.و Syntaxآن بصورت زیر میباشد:
NTILE (integer_expression) OVER ([<partition_by_clause>] <order_by_clause>)
برای درک مطلب فوق مثالی میزنیم:
Select * ,NTILE(4) over ( ORDER BY Price desc) from Test
خروجی بصورت زیر خواهد بود:
در Syntax تابع فوق اشاره به Integer_Expressionشده است.که یک مقدار عددی دریافت میکند و بیانگر تعداد گروه بندی دلخواه میباشد.
حال سئوال اینجاست که رتبه بندی جدول به چه صورت انجام شده است:
همانطور که مشاهده مینمایید، جدول فوق شامل 7 رکورد میباشد،و ما در مثال خود،تمایل داشتیم که رکوردهای جدول به چهار گروه تقسیم و سپس رتبه بندی شوند، بنابراین 7 تقسیم بر 4 شده است و باقی مانده آن میشود 3
پس خواهیم داشت7=3+1*4
در ابتدا چهار گروه ایجاد میشودو در هر خانه یک رکورد قرار میگیرد
سپس 3 رکورد باقی میماند که از اولین گروه رو به پایین ، برای هر گروه فقط یک رکورد درج میشود، یعنی یک رکورد به گروه یک،یک رکورد به گروه 2 و هم چنین یک رکورد به گروه 3 بنابراین خواهیم داشت:
نکته مهم: اگر تعداد رکورد باقی مانده بعد از تقسیم بیش از یک عدد باشد، در زمان اختصاص دادن به گروه ها، به هر گروه از بالا به پایین فقط یک رکورد اختصاص داده میشود.
مثالی دیگر:
Select *,NTILE(3) over ( ORDER BY Price desc) AS NTILE from Test
خروجی:
در این حالت 7=1+2*3
امیدوارم مطلب فوق مفید واقع شده باشد.
تلاشهای بسیاری توسط توسعه گران صورت پذیرفته است تا فرایند ایجاد وب سرویس WCF در بستر HTTP آسان شود. امروزه وب سرویس هایی که از قالب REST استفاده میکنند مطرح هستند.
ASP.NET Web API از مفاهیم موجود در ASP.NET MVC مانند Controllerها استفاده میکند و بر مبنای آنها ساخته شده است. بدین شکل، توسعه گر میتواند با دانش موجود خود به سادگی وب سرویسهای مورد نظر را ایجاد کند. Web API، پروتوکل SOAP را به کتابهای تاریخی! سپرده است تا از آن به عنوان روشی برای تعامل بین سیستمها یاد شود. امروزه به دلیل فراگیری پروتوکل HTTP، بیشتر محیطهای برنامه نویسی و سیستم ها، از مبانی اولیهی پروتوکل HTTP مانند اَفعال آن پشتیبانی میکنند.
حال قصد داریم تا وب سرویسی را که در قسمت اول با WCF ایجاد کردیم، این بار با استفاده از Web API ایجاد کنیم. به تفاوت این دو دقت کنید.
using System.Web.Http; namespace MvcApplication1.Controllers { public class ValuesController : ApiController { // GET api/values/5 public string Get(int id) { return string.Format("You entered: {0}", id); } } }
نحوهی برگشت یک مقدار از متدها در Web API، مانند WCF است. میتوانید خروجی متد Get را با اجرای پروژهی قبل در Visual Studio و تست آن با یک مرورگر ملاحظه کنید. دقت داشته باشید که یکی از اصولی که Web API به آن معتقد است این است که وب سرویسها میتوانند ساده باشند. در Web API، تست و دیباگ وب سرویسها بسیار راحت است. با مرورگر Internet Explorer به آدرس http://localhost:{port}/api/values/3 بروید. پیش از آن، برنامهی Fiddler را اجرا کنید. شکل ذیل، نتیجه را نشان میدهد.
در اینجا نتیجه، عبارت "You entered: 3" است که به صورت یک متن ساده برگشت داده شده است.
ایجاد یک پروژهی Web API
در Visual Studio، مسیر ذیل را طی کنید.
File> New> Project> Installed Templates> Visual C#> Web> ASP.NET MVC 4 Web Application
نام پروژه را HelloWebAPI بگذارید و بر روی دکمهی OK کلیک کنید (شکل ذیل)در فرمی که باز میشود، گزینهی Web API را انتخاب و بر روی دکمهی OK کلیک کنید (شکل ذیل). البته دقت داشته باشید که ما همیشه مجبور به استفاده از قالب Web API برای ایجاد پروژههای خود نیستیم. میتوان در هر نوع پروژه ای از Web API استفاده کرد.
اضافه کردن مدل
مدل، شی ای است که نمایانگر دادهها در برنامه است. Web API میتواند به طور خودکار، مدل را به فرمت JSON، XML یا فرمت دلخواهی که خود میتوانید برای آن ایجاد کنید تبدیل و سپس دادههای تبدیل شده را در بدنهی پاسخ HTTP به Client ارسال کند. تا زمانی که Client بتواند فرمت دریافتی را بخواند، میتواند از آن استفاده کند. بیشتر Clientها میتوانند فرمت JSON یا XML را پردازش کنند. به علاوه، Client میتواند نوع فرمت درخواستی از Server را با تنظیم مقدار هدر Accept در درخواست ارسالی تعیین کند. اجازه بدهید کار خود را با ایجاد یک مدل ساده که نمایانگر یک محصول است آغاز کنیم.
بر روی پوشهی Models کلیک راست کرده و از منوی Add، گزینهی Class را انتخاب کنید.
نام کلاس را Product گذاشته و کدهای ذیل را در آن بنویسید.
namespace HelloWebAPI.Models { public class Product { public int Id { get; set; } public string Name { get; set; } public string Category { get; set; } public decimal Price { get; set; } } }
مدل ما، چهار Property دارد که در کدهای قبل ملاحظه میکنید.
اضافه کردن Controller
در پروژه ای که با استفاده از قالب پیش فرض Web API ایجاد میشود، دو Controller نیز به طور خودکار در پروژهی Controller قرار میگیرند:
- HomeController: یک Controller معمول ASP.NET MVC است که ارتباطی با Web API ندارد.
- ValuesController: یک Controller مختص Web API است که به عنوان یک مثال در پروژه قرار داده میشود.
توجه: Controllerها در Web API بسیار شبیه به Controllerها در ASP.NET MVC هستند، با این تفاوت که به جای کلاس Controller، از کلاس ApiController ارث میبرند و بزرگترین تفاوتی که در نگاه اول در متدهای این نوع کلاسها به چشم میخورد این است که به جای برگشت Viewها، داده برگشت میدهند.
کلاس ValuesController را حذف و یک Controller به پروژه اضافه کنید. بدین منظور، بر روی پوشهی Controllers، کلیک راست کرده و از منوی Add، گزینهی Controller را انتخاب کنید.
توجه: در ASP.NET MVC 4 میتوانید بر روی هر پوشهی دلخواه در پروژه کلیک راست کرده و از منوی Add، گزینهی Controller را انتخاب کنید. پیشتر فقط با کلیک راست بر روی پوشهی Controller، این گزینه در دسترس بود. حال میتوان کلاسهای مرتبط با Controllerهای معمول را در یک پوشه و Controllerهای مربوط به قابلیت Web API را در پوشهی دیگری قرار داد.
نام Controller را ProductsController بگذارید، از قسمت Template، گزینهی Empty API Controller را انتخاب و بر روی دکمهی OK کلیک کنید (شکل ذیل).
فایلی با نام ProductsController.cs در پوشهی Controllers قرار میگیرد. آن را باز کنید و کدهای ذیل را در آن قرار دهید.
namespace HelloWebAPI.Controllers { using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Net.Http; using System.Web.Http; using HelloWebAPI.Models; public class ProductsController : ApiController { Product[] products = new Product[] { new Product { Id = 1, Name = "Tomato Soup", Category = "Groceries", Price = 1.39M }, new Product { Id = 2, Name = "Yo-yo", Category = "Toys", Price = 3.75M }, new Product { Id = 3, Name = "Hammer", Category = "Hardware", Price = 16.99M } }; public IEnumerable<Product> GetAllProducts() { return products; } public Product GetProductById(int id) { var product = products.FirstOrDefault((p) => p.Id == id); if (product == null) { var resp = new HttpResponseMessage(HttpStatusCode.NotFound); throw new HttpResponseException(resp); } return product; } public IEnumerable<Product> GetProductsByCategory(string category) { return products.Where( (p) => string.Equals(p.Category, category, StringComparison.OrdinalIgnoreCase)); } } }
برای ساده نگهداشتن مثال، لیستی از محصولات را در یک آرایه قرار داده ایم اما واضح است که در یک پروژهی واقعی، این لیست از پایگاه داده بازیابی میشود. در مورد کلاسهای HttpResponseMessage و HttpResponseException بعداً توضیح میدهیم.
در کدهای Controller قبل، سه متد تعریف شده اند:
- متد GetAllProducts که کل محصولات را در قالب نوع <IEnumerable<Product برگشت میدهد.
- متد GetProductById که یک محصول را با استفاده از مشخصهی آن (خصیصهی Id) برگشت میدهد.
- متد GetProductsByCategory که تمامی محصولات موجود در یک دستهی خاص را برگشت میدهد.
تمام شد! حال شما یک وب سرویس با استفاده از Web API ایجاد کرده اید. هر یک از متدهای قبل در Controller، به یک آدرس به شرح ذیل تناظر دارند.
GetAllProducts به api/products/
GetProductById به api/products/id/
GetProductsByCategory به api/products/?category=category/
در آدرسهای قبل، id و category، مقادیری هستند که همراه با آدرس وارد میشوند و در پارامترهای متناظر خود در متدهای مربوطه قرار میگیرند. یک Client میتواند هر یک از متدها را با ارسال یک درخواست از نوع GET اجرا کند.
در قسمت بعد، کار خود را با تست پروژه و نحوهی تعامل jQuery با آن ادامه میدهیم.
خطا ها و مدیریت خطا (Exception Handling)
exception myError of int
ساختار کلی try catch finally در #F به صورت زیر است.(تنها تفاوت در کلمه with به جای catch است)
try // try code here with //catch statement here
try // try code here finally //finally statement here
یک مثال از try with:
exception WrongSecond of int//یک exception تعریف میکنیم let primes = [ 2; 3; 5; 7; 11; 13; 17; 19; 23; 29; 31; 37; 41; 43; 47; 53; 59 ] // یک تابع برای تست اینکه آیا ثانیه الان در لیست prime وجود دارد یا نه let testSecond() = try let currentSecond = System.DateTime.Now.Second in // شرط برای اینکه مشخص شود که ثانیه در لیست است یا خیر if List.exists (fun x -> x = currentSecond) primes then // اگر بود یک خطا تولید میشود failwith "A prime second" else // اگر نیود یک استثنا از نوع wrongSecond پرتاب میشود raise (WrongSecond currentSecond) with // catch کردن استثناها WrongSecond x -> printf "The current was %i, which is not prime" x
در کد با در هر خط توضیحات لازم داده شده است. نکته قابل ذکر این است که در #C زمانی که قصد داشته باشیم یک استثنا جدید ایجاد کنیم باید کلاسی جدیدی که از کلاس System.Exception ارث برده باشد(یا هر کلاس دیگری که خود از این System.Exception ارث برده است) ایجاد کنیم و کدهای مورد نظر رو در اون قرار بدیم. ولی در اینجا (در قسمتی که رنگ آن متفاوت است) به راحتی توانستیم یک استثنا جدید بر اساس نیاز بسازیم.
یک مثال از try finally :
// تابعی برای نوشتن فایل let writeToFile() = //ابتدا فایل به صورت متنی ساخته میشود let file = System.IO.File.CreateText("test.txt") try // متن مورد نظر در فایل نوشته میشود file.WriteLine("Hello F# users") finally //فایل مورد نظر بسته میشود.این دستور حتی اگر در هنگام نوشتن فایل استثنا هم رخ بدهد اجرا خواهد شد file.Dispose()
*توجه : برنامه نویسانی که قبلا با OCaml کدنویسی کرده اند هنگام برنامه نویسی #F از raise کردنهای زیاد و بی مورد استثناها خودداری کنند. به دلیل نوع معماری CLR پرتاب کردن استثنا و مدیریت آن کمی هزینه بر است (بیشتر از زبان Ocaml). البته این مسئله در زبانهای تحت دات نت نیز مطرح است کما اینکه در #C نیز مدیریت استثناها رو در بالاترین لایه انجام میدهیم و از catch کردن بی مورد استثنائات در لایههای زیرین خودداری میکنیم.
یک مثال از الگوی Matching در try with
let getNumber msg = printf msg; try int32(System.Console.ReadLine()) with | :? System.FormatException -> -1 | :? System.OverflowException -> System.Int32.MinValue | :? System.ArgumentNullException -> 0
پیاده سازی Option یا Maybe در #C
public struct Maybe<T> : IEquatable<Maybe<T>> where T : class { private readonly T _value; private Maybe(T value) { _value = value; } public bool HasValue => _value != null; public T Value => _value ?? throw new InvalidOperationException(); public static Maybe<T> None => new Maybe<T>(); public static implicit operator Maybe<T>(T value) { return new Maybe<T>(value); } public static bool operator ==(Maybe<T> maybe, T value) { return maybe.HasValue && maybe.Value.Equals(value); } public static bool operator !=(Maybe<T> maybe, T value) { return !(maybe == value); } public static bool operator ==(Maybe<T> left, Maybe<T> right) { return left.Equals(right); } public static bool operator !=(Maybe<T> left, Maybe<T> right) { return !(left == right); } /// <inheritdoc /> /// <summary> /// Avoid boxing and Give type safety /// </summary> /// <param name="other"></param> /// <returns></returns> public bool Equals(Maybe<T> other) { if (!HasValue && !other.HasValue) return true; if (!HasValue || !other.HasValue) return false; return _value.Equals(other.Value); } /// <summary> /// Avoid reflection /// </summary> /// <param name="obj"></param> /// <returns></returns> public override bool Equals(object obj) { if (obj is T typed) { obj = new Maybe<T>(typed); } if (!(obj is Maybe<T> other)) return false; return Equals(other); } /// <summary> /// Good practice when overriding Equals method. /// If x.Equals(y) then we must have x.GetHashCode()==y.GetHashCode() /// </summary> /// <returns></returns> public override int GetHashCode() { return HasValue ? _value.GetHashCode() : 0; } public override string ToString() { return HasValue ? _value.ToString() : "NO VALUE"; } }
public virtual async Task<Maybe<TModel>> GetByIdAsync(long id) { Guard.ArgumentInRange(id, 1, long.MaxValue, nameof(id)); var entity = await UnTrackedEntitySet.Where(a => a.Id == id) .ProjectTo<TModel>(_mapper.ConfigurationProvider).SingleOrDefaultAsync(); return entity; }
Maybe<T> = Nullable<T>
کامپوننتهای جدید SectionOutlet و SectionContent در Blazor 8x
پیاده سازی Sections در Blazor 8x به کمک دو کامپوننت جدید SectionOutlet و SectionContent میسر شدهاست و برای دسترسی به آنها نیاز است ابتدا به فایل Imports.razor_ پروژه، مراجعه کرد و using زیر را به آن اضافه نمود تا این اشیاء، در کامپوننتهای برنامه قابل شناسایی و استفاده شوند:
@using Microsoft.AspNetCore.Components.Sections
SectionOutlet کامپوننتی است که محتوای ارائه شدهی توسط کامپوننت SectionContent را رندر میکند (این محتوا در اصل یک RenderFragment است). ارتباط بین این دو هم توسط خواص SectionName و یا SectionIdهای متناظر، برقرار میشود. اگر چندین SectionContent دارای نام و یا Id یکسانی باشند، محتوای آخرین آنها در SectionOutlet متناظر، رندر میشود.
برای مثال در فایل MainLayout.razor، تغییر زیر را اعمال میکنیم:
<div class="top-row px-4"> <SectionOutlet SectionName="before-top-row"/> <a href="https://learn.microsoft.com/aspnet/core/" target="_blank">About</a> </div>
<SectionContent SectionName="before-top-row"> <a href="/show-help" target="_blank">Help</a> </SectionContent>
روش تعریف یک محتوای پیشفرض
این محتوا، فقط زمانی تامین خواهد شد که کامپوننت در حال نمایش SectionContent، قابل مشاهده و فعال شده باشد. یعنی اگر از کامپوننت نمایش دهندهی آن به صفحهی دیگری رجوع کنیم، محتوای SectionOutlet مجددا خالی خواهد شد، تا زمانیکه به آدرس نمایش دهندهی کامپوننت دربرگیرندهی SectionContent متناظر با آن رجوع کنیم. به همین جهت اگر علاقمند به نمایش یک «محتوای پیشفرض» هستید، میتوان به صورت زیر عمل کرد:
<div class="top-row px-4"> <SectionOutlet SectionName="before-top-row" /> <SectionContent SectionName="before-top-row"> <a href="https://learn.microsoft.com/aspnet/core/" target="_blank">About</a> </SectionContent> </div>
تفاوت SectionId با SectionName
کامپوننت SectionOutlet، هم میتواند یک SectionName را دریافت کند و هم یک SectionId را. SectionNameها، رشتهای هستند و تغییرات آتی آنها تحت نظارت کامپایلر نیست. به همین جهت اگر نیاز به Refactoring آنها است، شاید بهتر باشد از خاصیت SectionId که یک Id از نوع strongly typed را مشخص میکند، استفاده کنیم.
برای نمونه میتوان مثال قبلی را به صورت زیر با استفاده از یک SectionId، بازنویسی کرد:
<div class="top-row px-4"> <SectionOutlet SectionId="BeforeTopRow" /> <a href="https://learn.microsoft.com/aspnet/core/" target="_blank">About</a> </div> @code{ public static SectionOutlet BeforeTopRow = new(); }
سپس هر کامپوننت دیگری که بخواهد از این Id استاتیک استفاده کند، روش کار آن به صورت زیر است:
<SectionContent SectionId="MainLayout.BeforeTopRow"> <a href="/show-help" target="_blank">Help</a> </SectionContent>
PS /> git branch --format='{"name":"%(refname:lstrip=2)"}' --list
%(refname:lstrip=2)
{"name":"main"} {"name":"feature-branch-a"} {"name":"feature-branch-b"} ,...
PS /> git branch --format='{"name":"%(refname:lstrip=2)","isMainBranch":'"$(if [[ $(git symbolic-ref --short HEAD) == "main" ]]; then echo true; else echo false; fi)"' }' --list
ParserError: Line | 1 | … --format='{"name":"%(refname:lstrip=2)","isMainBranch":'"$(if [[ $(gi … | ~ | Missing '(' after 'if' in if statement.
'"`$(if [[ `$
{"name":"main","isMainBranch":$(if [[ $(git symbolic-ref --short HEAD) == main ]]; then echo true; else echo false; fi) } {"name":"feature-branch-a","isMainBranch":$(if [[ $(git symbolic-ref --short HEAD) == main ]]; then echo true; else echo false; fi) } {"name":"feature-branch-b","isMainBranch":$(if [[ $(git symbolic-ref --short HEAD) == main ]]; then echo true; else echo false; fi) }
$branches = git branch | ForEach-Object { $default = $false $activeBranch = git symbolic-ref --short HEAD $currentBranch = ($_.Replace("* ", " ")).Trim() if ($currentBranch -eq $activeBranch) { $default = $true } @{ name = $currentBranch isMainBranch = $default } | ConvertTo-Json } | ConvertFrom-Json