مطالب
آشنایی با SQL Server Common Table Expressions - CTE
مقدمه

تکنولوژی CTE از نسخه SQL Server 2005 رسمیت یافته است و شامل یک result set موقتی[1] است که دارای نام مشخص بوده و می­توان از آن در دستورات SELECT, INSERT, UPDATE, DELETEاستفاده کرد. همچنین از CTE می‌توان در دستور CREATE VIEW و دستور SELECT مربوط به آن استفاده کرد. در نسخه SQL Server 2008 نیز امکان استفاده از CTE در دستور MERGE فراهم شده است.
در SQL Serverاز دو نوع CTE بازگشتی[2] و غیر بازگشتی[3] پشتیبانی می­شود. در این مقاله سعی شده است نحوه تعریف و استفاده از هر دو نوع آن آموزش داده شود.

انواع روش­های ایجاد جداول موقت
برای استفاده از جداول موقتی در سرور اسکیوال، سه راه زیر وجود دارد.

روش اول: استفاده از دستوری مانند زیر است که سبب ایجاد جدول موقتی در بانک سیستمی tempdb می­شود. زمانی­که شما ارتباط خود را با سرور SQL قطع می­کنید به صورت اتوماتیک جداول موقت شما از بانک tempdb حذف می­شوند. این روش در برنامه نویسی پیشنهاد نمی­شود و فقط در کارهای موقتی و آزمایشی مناسب است.
 SELECT * INTO #temptable FROM [Northwind].[dbo].[Products]
UPDATE #temptable SET [UnitPrice] = [UnitPrice] + 10

روش دوم: استفاده از متغیر نوع Table است، که نمونه آن در مثال زیر دیده می­شود. زمانیکه از محدوده[4] جاری کد[5] خودتان خارج شوید آن متغیر نیز از حافظه پاک میشود. از این روش، عموما در کدهای Stored Procedureها و UserDefined Functionها استفاده می­شود.
DECLARE @tempTable TABLE
(
   [ProductID] [int] NOT NULL,
   [ProductName] [nvarchar](40) NOT NULL,
   [UnitPrice] [money] NULL
)
 
INSERT INTO @tempTable
SELECT
   [ProductID],
   [ProductName],
   [UnitPrice]
FROM [Northwind].[dbo].[Products]
 
UPDATE @temptable SET [UnitPrice] = [UnitPrice] + 10

روش سوم: استفاده از CTE است که مزیت­هایی نسبت به دو روش قبلی دارد و در بخش بعدی به نحوه تعریف و استفاده از آن خواهیم پرداخت.


کار با CTE
ساده ­ترین شکل تعریف یک CTE به صورت زیر است:
WITH yourName [(Column1, Column2, ...)]
AS
(
   your query
)
با کلمه WITH شروع شده و یک نام اختیاری به آن داده می­شود. سپس فهرست فیلدهای جدول موقت را درون زوج پرانتز، مشخص می­کنید. تعریف این فیلدها اختیاری است و اگر حذف شود، فیلدهای جدول موقت، مانند فیلدهای کوئری مربوطه خواهد بود.
your query شامل دستوری است که سبب تولید یک result set می­شود. قواعد تعریف این کوئری مشابه قواعد تعریف کوئری است که در دستور CREATE VIEW کاربرد دارد.


شکل کلی دستور

همانطور که از این تصویر مشخص است می­توان چندین بلوک از این ساختار را به دنبال هم تعریف نمود که با کاما از هم جدا می­شوند. در واقع یکی از کاربردهای CTE ایجاد قطعات کوچکی است که امکان استفاده مجدد را به شما داده و می­تواند سبب خواناتر شدن کدهای پیچیده شود.
یکی دیگر از کاربردهای CTE آنجایی است که شما نمی‌خواهید یک شی Viewی عمومی تعریف کنید و در عین حال می‌خواهید از مزایای Viewها بهرمند شوید.
و همچنین از کاربردهای دیگر CTE تعریف جدول موقت و استفاده از آن جدول به صورت همزمان در یک دستور است.

بعد از آنکه CTE یا CTEهای خودتان را تعریف کردید آنگاه می­توانید مانند جداول معمولی از آنها استفاده کنید. استفاده از این جداول توسط دستوری خواهد بود که دقیقا بعد از تعریف CTE نوشته می­شود.


ایجاد یک CTE غیر بازگشتی[6]

مثال اول، یک CTE غیر بازگشتی ساده را نشان میدهد.
WITH temp
AS
(
    SELECT
         [ProductName],
         [UnitPrice]
    FROM [Northwind].[dbo].[Products]
)
SELECT * FROM temp
ORDER BY [UnitPrice] DESC

مثال دوم نمونه‌­ای دیگر از یک CTE غیر بازگشتی است.
WITH orderSales (OrderID, Total)
AS
(
   SELECT
      [OrderID],
     SUM([UnitPrice]*[Quantity]) AS Total
   FROM [Northwind].[dbo].[Order Details]
   GROUP BY [OrderID]
)
SELECT
   O.[ShipCountry],
   SUM(OS.[Total]) AS TotalSales
FROM [Northwind].[dbo].[Orders] AS O INNER JOIN [orderSales] AS OS
ON O.[OrderID] = OS.[OrderID]
GROUP BY O.[ShipCountry]
ORDER BY TotalSales DESC
هدف این کوئری، محاسبه کل میزان فروش کالاها، به ازای هر کشور می­باشد. ابتدا از جدول Order Details مجموع فروش هر سفارش محاسبه شده و نتیجه آن در یک CTE به نام orderSales قرار می­گیرد و از JOIN این جدول موقت با جدول Orders محاسبه نهایی انجام شده و نتیجه­‌ای مانند این تصویر حاصل می‌شود.

نتیجه خروجی

 مثال سوم استفاده از دو CTE را به صورت همزمان نشان می­دهد:
WITH customerList
AS
(
   SELECT
      [CustomerID],
      [ContactName]
   FROM [Northwind].[dbo].[Customers]
   WHERE [Country] ='UK'
)
,orderList
AS
(
   SELECT
      [CustomerID],
      [OrderDate]
   FROM [Northwind].[dbo].[Orders]
   WHERE YEAR([OrderDate])< 2000
)
SELECT
   cl.[ContactName],
   YEAR(ol.[OrderDate]) AS SalesYear
FROM customerList AS cl JOIN orderList AS ol
ON cl.[CustomerID] = ol.[CustomerID]

مثال چهارم استفاده مجدد از یک CTE را نشان می­دهد. فرض کنید جدولی به نام digits داریم که فقط یک فیلد digit دارد و دارای 10 رکورد با مقادیر 0 تا 9 است. مانند تصویر زیر

نتیجه خروجی

حال می­خواهیم از طریق CROSS JOIN اعداد 1 تا 100 را با استفاده از مقادیر این جدول تولید کنیم. کد زیر آنرا نشان می­دهد:
WITH digitList
AS
(
   SELECT [digit] from [digits]
)
SELECT
   a.[digit] * 10 + b.[digit] + 1 AS [Digit]
FROM [digitList] AS a CROSSJOIN [digitList] AS b
در این کد یک CTE تعریف شده و دو بار مورد استفاده قرار گرفته است. مثلا اگر بخواهید اعداد 1 تا 1000 را تولید کنید می­توانید سه بار از آن استفاده کنید. حاصل این دستور result setی مانند زیر است.

نتیجه

نتیجه

حتی می­توان از یک CTE در کوئری CTE بعدی مانند کد زیر استفاده کرد.
WITH CTE_1 AS
(
   ....
),
CTE_2 AS
(
   SELECT  ... FROM CTE_1 JOIN ...
)
SELECT   *
FROM FOO
LEFTJOIN  CTE_1
LEFTJOIN  CTE_2


ایجاد یک CTE بازگشتی[7]

از CTE بازگشتی برای پیمایش جداولی استفاده می­شود که رکوردهای آن دارای رابطه سلسله مراتبی یا درختی است. نمونه این جداول، جدول کارمندان است که مدیر هر کارمند نیز مشخص شده است یا جدولی که ساختار سازمانی را نشان می­دهد یا جدولی که موضوعات درختی را در خود ذخیره کرده است. یکی از مزایای استفاده از CTE بازگشتی، سرعت کار آن در مقایسه با روش­های پردازشی دیگر است.

ساختار کلی یک دستور CTE بازگشتی به صورت زیر است.
WITH cteName AS
(
   query1
   UNION ALL
   query2
)
در بدنه CTE حداقل دو عضو[8] (کوئری) وجود دارد که بایستی با یکی از عبارت­های زیر به هم متصل شوند.
UNION
UNION ALL
INTERSECT
EXCEPT

query1 شامل دستوری است که اولین سری از رکوردهای result set نهایی را تولید می­کند. اصطلاحا به این کوئری anchor memberمی­گویند.
بعد از دستور query1، حتما بایستی از UNION ALL و امثال آنها استفاده شود.
سپس query2 ذکر می­شود. اصطلاحا به این کوئری recursive member گفته می­شود. این کوئری شامل دستوری است که سطوح بعدی درخت را تولید خواهد کرد. این کوئری دارای شرایط زیر است.
  •   حتما بایستی به CTE که همان cteName است اشاره کرده و در جایی از آن استفاده شده باشد. به عبارت دیگر از رکوردهای موجود در جدول موقت استفاده کند تا بتواند رکوردهای بعدی را تشخیص دهد.
  • حتما بایستی مطمئن شوید که شرایط کافی برای پایان حلقه پیمایش رکوردها را داشته باشد در غیر این صورت سبب تولید حلقه بی پایان[9] خواهد شد.

بدنه CTE می­تواند حاوی چندین anchor member و چندین recursive member باشد ولی فقط recursive memberها هستند که به CTE اشاره می­کنند.

برای آنکه نکات فوق روشن شود به مثال­های زیر توجه کنید.
فرض کنید جدولی از کارمندان و مدیران آنها داریم که به صورت زیر تعریف و مقداردهی اولیه شده است.
IFOBJECT_ID('Employees','U')ISNOTNULL
DROPTABLE dbo.Employees
GO 

CREATETABLE dbo.Employees
(
  EmployeeID intNOTNULLPRIMARYKEY,
  FirstName varchar(50)NOTNULL,
  LastName varchar(50)NOTNULL,
  ManagerID intNULL
)

GO


INSERTINTO Employees VALUES (101,'Alireza','Nematollahi',NULL)
INSERTINTO Employees VALUES (102,'Ahmad','Mofarrahzadeh', 101)
INSERTINTO Employees VALUES (103,'Mohammad','BozorgGhommi', 102)
INSERTINTO Employees VALUES (104,'Masoud','Narimani', 103)
INSERTINTO Employees VALUES (105,'Mohsen','Hashemi', 103)
INSERTINTO Employees VALUES (106,'Aref','Partovi', 102)
INSERTINTO Employees VALUES (107,'Hosain','Mahmoudi', 106)
INSERTINTO Employees VALUES (108,'Naser','Pourali', 106)
INSERTINTO Employees VALUES (109,'Reza','Bagheri', 102)
INSERTINTO Employees VALUES (110,'Abbas','Najafian', 102)

مثال اول: می­خواهیم فهرست کارمندان را به همراه نام مدیر آنها و شماره سطح درخت نمایش دهیم. کوئری زیر نمونه‌ای از یک کوئری بر اساس CTE بازگشتی می­باشد.
WITHcteReports(EmpID, FirstName, LastName, MgrID, EmpLevel)
AS
(
SELECT EmployeeID, FirstName, LastName, ManagerID, 1
FROM Employees
WHERE ManagerID ISNULL
UNIONALL
SELECT e.EmployeeID, e.FirstName, e.LastName, e.ManagerID,r.EmpLevel + 1
FROM Employees e INNERJOINcteReports r
ON e.ManagerID = r.EmpID
)
SELECT
FirstName +' '+ LastName AS FullName,
EmpLevel,
(SELECT FirstName +' '+ LastName FROM Employees
WHERE EmployeeID = cteReports.MgrID)AS Manager
FROMcteReports
ORDERBY EmpLevel, MgrID
کوئری اول در بدنه CTE رکورد مدیری را می­دهد که ریشه درخت بوده و بالاسری ندارد و شماره سطح این رکورد را 1 در نظر می­گیرد.
کوئری دوم در بدنه CTE از یک JOIN بین Employees و cteReports استفاده کرده و کارمندان زیر دست هر کارمند قبلی (فرزندان) را بدست آورده و مقدار شماره سطح آنرا به صورت Level+1 تنظیم می­کند.
در نهایت با استفاده از CTE و یک subquery جهت بدست آوردن نام مدیر هر کارمند، نتیجه نهایی تولید می­شود.

مثال دوم: می­خواهیم شناسه یک کارمند را بدهیم و نام او و نام مدیران وی را به عنوان جواب در خروجی بگیریم.
WITHcteReports(EmpID, FirstName, LastName, MgrID, EmpLevel)
AS
(
SELECT EmployeeID, FirstName, LastName, ManagerID, 1
FROM Employees
WHERE EmployeeID = 110
UNIONALL
SELECTe.EmployeeID, e.FirstName, e.LastName, e.ManagerID,r.EmpLevel + 1
FROM Employees e INNERJOINcteReports r
ON e.EmployeeID = r.MgrID
)
SELECT
FirstName +' '+ LastName AS FullName,
EmpLevel
FROMcteReports
ORDERBY EmpLevel
اگر دقت کنید اولین تفاوت در خط اول مشاهده می­شود. در اینجا مشخص می­کند که اولین سری از رکوردها چگونه انتخاب شود. مثلا کارمندی را می­خواهیم که شناسه آن 110 باشد.
دومین تفاوت اصلی این کوئری با مثال قبلی، در قسمت دوم دیده می­شود. شما می‌خواهید مدیر (پدر) کارمندی که در آخرین پردازش در جدول موقت قرار گرفته است را استخراج کنید.

    


[1] a temporary named result set
[2] recursive
[3] nonrecursive
[4] Scope
[5]مثلا محدوده کدهای یک روال یا یک تابع
[6] nonrecursive
[7] recursive
[8] member
[9] Infinite loop
  
اشتراک‌ها
چگونه سبب کرش کامپایلر #C شویم؟!
Level Compile Time (secs) Working set (KB) Binary Size (Bytes)
5 1.15 54,288 135,680
6 1.22 59,500 788,992
7 2.00 70,728 4,707,840
8 6.43 121,852 28,222,464
9 33.23 405,472 169,310,208
10 202.10 2,141,272 CRASH
چگونه سبب کرش کامپایلر #C شویم؟!
مطالب
PersianDateTime جایگزینی برای System.DateTime
همانطور که در توضیح پروژه PersianDateTime آمده است، کلاس PersianDateTime جایگزینی است برای System.DateTime برای استفاده در پروژه‌هایی که احتیاج به تاریخ شمسی و ساعت رسمی ایران یا سایر کشورهای فارسی‌زبان، مستقل از Time Zone سیستم و در نظر گرفتن Daylight Saving Time، دارند. این کلاس شامل اکثر متدها، پراپرتی‌ها و عملگرهای متداول  System.DateTime است.

دسترسی به تاریخ و ساعت فعلی :
PersianDateTime now = PersianDateTime.Now;

string persianDateTime = now.ToString(); // 1392/03/09 23:37:57
string persianDate = now.ToString(PersianDateTimeFormat.Date); // 1392/03/09
string persianFullDateTime = now.ToString("dddd d MMMM yyyy ساعت hh:mm:ss tt"); // پنج شنبه 9 خرداد 1392 ساعت 11:37:57 ب.ظ

TimeSpan persianTime = now.TimeOfDay; // 23:37:57.4641984


نحوه محاسبه PersianDateTime.Now بر اساس فیلد استاتیک PersianDateTime.Mode است که مقدار پیش‌فرض آن PersianDateTimeMode.UtcOffset است.
PersianDateTime.Mode را می‌توان یکی از سه مقدار زیر قرار داد :
  • System : بر اساس تاریخ و زمان سیستم (Time Zone فعلی سیستم)
  • PersianTimeZoneInfo : بر اساس Time Zone تعیین شده در فیلد استاتیک PersianDateTime.PersianTimeZoneInfo (مستقل از Time Zone سیستم)
  • UtcOffset : بر اساس اختلاف ساعت با UTC، مشخص شده در فیلد استاتیک PersianDateTime.OffsetFromUtc (مستقل از Time Zone سیستم)

مقدار پیش‌فرض PersianDateTime.PersianTimeZoneInfo برابر Iran Standard Time Zone است. توجه داشته باشید که در این حالت از DaylightSavingTime تعیین شده در Time Zone استفاده خواهد شد که مثلا برای ایران با زمان واقعی آن اختلاف دارد و باید آنرا اصلاح کرد .

مقدار پیش‌فرض PersianDateTime.OffsetFromUtc برابر 3 ساعت و نیم است. در این حالت DaylightSavingTime با توجه به مقادیر سه فیلد استاتیک DaylightSavingTimeStart ،DaylightSavingTimeEnd و DaylightSavingTime تعیین می‌شود که به صورت پیش‌فرض برابر ساعت 24 اول فروردین، ساعت 24 سی‌ام شهریور و یک ساعت است.

تمام فیلدهای استاتیک ذکر شده به صورت public تعریف شده‌اند تا برنامه‌نویسان سایر کشورهای فارسی‌زبان بتوانند به دلخواه خود آنرا تغییر دهند.

نحوه کار با مقادیر رشته‌ای تاریخ شمسی هم اینگونه است :
PersianDateTime persianDate1 = PersianDateTime.Parse("1392/03/02");
PersianDateTime persianDate2 = PersianDateTime.Parse("1392/03/02", "23:32:56");

چند سازنده هم وجود دارد برای کسانی که تاریخ را به صورت int و ساعت را int یا short (بدون ثانیه) ذخیره می‌کنند :
PersianDateTime persianDate1 = new PersianDateTime(13920310);
PersianDateTime persianDate2 = new PersianDateTime(13920310, 233256);
PersianDateTime persianDate3 = new PersianDateTime(13920310, (short)2332);

تبدیل تاریخ میلادی به شمسی :
DateTime miladiDate = new DateTime(2013, 5, 31);
PersianDateTime persianDate = new PersianDateTime(miladiDate);

تبدیل تاریخ شمسی به میلادی :
PersianDateTime persianDate = PersianDateTime.Parse("1392/03/02");
DateTime miladiDate = persianDate.ToDateTime();


علاوه بر متد ToString معمولی، دو overload دیگر از این متد برای نمایش فرمت‌های مختلف PersianDateTime وجود دارد :

public string ToString(PersianDateTimeFormat format);

public string ToString(string format);
که اولی برای فرمت‌های معمول و دومی برای هر نوع فرمت دلخواه قابل استفاده است که چند نمونه آنرا در قسمت تعیین تاریخ و ساعت فعلی دیدید.

برخی از خصوصیات کلاس PersianDateTime :
  • Year : سال
  • Month : ماه
  • Day : روز
  • TimeOfDay : زمان سپری شده از ابتدای روز
  • TimeOfWeek :زمان سپری شده از ابتدای هفته
  • TimeOfMonth : زمان سپری شده از ابتدای ماه
  • TimeOfYear : زمان سپری شده از ابتدای سال
  • DaysInYear : تعداد روز سال
  • DaysInMonth : تعداد روز ماه
  • DayOfWeek : چندمین روز هفته
  • DayOfYear : چندمین روز سال
  • DayName : نام روز
  • MonthName : نام ماه
  • Date : تاریخ بدون زمان
  • FirstDayOfWeek : اولین روز هفته
  • LastDayOfWeek : آخرین روز هفته
  • FirstDayOfMonth : اولین روز ماه
  • LastDayOfMonth : آخرین روز ماه
  • FirstDayOfYear : اولین روز سال
  • LastDayOfYear : آخرین روز سال

برخی دیگر از متدهای کلاس PersianDateTime :

public PersianDateTime Add(TimeSpan value);
public PersianDateTime AddDays(double value);
public PersianDateTime AddMonths(int months);
public PersianDateTime AddYears(int value);
public PersianDateTime AddHours(double value);
public PersianDateTime AddMinutes(double value);
public PersianDateTime AddSeconds(double value);
همچنین تمام عملگرهای +، -، >، <، =>، =<، ==، و =! قابل استفاده هستند.
مطالب
آموزش LINQ بخش چهارم
 بررسی عبارت Join
عبارت Join برای ایجاد ارتباط بین عناصر دو توالی استفاده می‌شود. پیش نیاز این عملیات وجود یک مقدار در عناصر توالی ورودی است که بتوان از طریق آن برابری دو عنصر را اهراز کرد.
عبارت join، دو توالی را به عنوان ورودی دریافت می‌کند. هر عنصر در توالی اول باید یک خصوصیت داشته باشد که بتوان آن را با یک خصوصیت از عناصر توالی دوم مقایسه کرد. عبارت Join، مقادیر مشخص شده را از طریق کلمه‌ی کلیدی equals مقایسه می‌کند. باید توجه داشت که عملیات join  برای بررسی برابری دو مقدار، در دو توالی مختلف است. از این رو به آن equal-joins می‌گویند.
فرم خروجی عبارت join  بر اساس نوع عملیات join ای است که ما انجام می‌دهیم.

انواع JOIN :
• Inner JOIN
• Group JOIN
• Left JOIN

کلاس‌های زیر را در نظر بگیرید:
/// <summary>
/// دستور العمل
/// </summary>
class Recipe
{
  public int Id { get; set; }
  public string Name { get; set; }
}

/// <summary>
/// بازخورد
/// </summary>
class Review
{
  public int RecipeId { get; set; }
  public string ReviewText { get; set; }
}
بر اساس مدل‌های تعریف شده، هر Recipe ممکن است 0، 1 یا تعداد نامحدودی Review داشته باشد. همانطور که می‌بینید هیچ رابطه‌ی مستقیمی این دو مدل با هم ندارند. در کلاس Review یک خصوصیت وجود دارد که ID مدل Recipe را در خود نگه می‌دارد.

Inner Join
 این دستور عنصری از  توالی اول را که متناظر با آن عنصری در توالی دوم وجود داشته باشد، به خروجی می‌برد.
مثال:

Recipe[] recipes =
{
   new Recipe {Id = 1, Name = "Mashed Potato"},
   new Recipe {Id = 2, Name = "Crispy Duck"},
   new Recipe {Id = 3, Name = "Sachertorte"}
};

Review[] reviews =
{
   new Review {RecipeId = 1, ReviewText = "Tasty!"},
   new Review {RecipeId = 1, ReviewText = "Not nice :("},
   new Review {RecipeId = 1, ReviewText = "Pretty good"},
   new Review {RecipeId = 2, ReviewText = "Too hard"},
   new Review {RecipeId = 2, ReviewText = "Loved it"}
};

var query = from recipe in recipes
join review in reviews on recipe.Id equals review.RecipeId
select new //anonymous type
{
   RecipeName = recipe.Name,
   RecipeReview = review.ReviewText
};

foreach (var item in query)
{
   Console.WriteLine($"{item.RecipeName}-{item.RecipeReview}");
}
در مثال فوق، در ابتدا دو توالی تعریف شد. recipes که تعدادی دستور العمل و reviews که مجموعه‌ای از بازخوردها را در خود نگه می‌دارند.
پرس و جو با عبارت from که اشاره‌گری به توالی اول است آغاز و با معرفی یک متغیر Range به نام Recipe ادامه پیدا می‌کند. سپس دستور Join نوشته شده است. مجددا متغیر range  جدیدی به نام review معرفی شده که به عناصر توالی دوم اشاره خواهد کرد. در ادامه کلمه‌ی کلیدی on اجازه می‌دهد که عناصر یکسانی در توالی اول را با عناصر یکسانی در توالی دوم مشخص کنیم. کلمه‌ی کلیدی equal هم برای مقایسه‌ی برابری دو مقدار استفاده شده است. عبارت join  بر اساس مقدار id  در مجموعه recipes و همچنین Recipeid در مجموعه‌ی reviews خروجی را مهیا خواهد کرد.
خروج مثال فوق به شکل زیر است :
Mashed Potato-Tasty!
Mashed Potato-Not nice :(
Mashed Potato-Pretty good
Crispy Duck-Too hard
Crispy Duck-Loved it
همانطور که مشاهده می‌کنید کلیه دستورالعمل‌هایی که بازخوردی برای آنها وجود دارد، در خروجی وجود دارند.

Group Join
بکارگیری into به همراه join، دستور Group Join را می‌سازد.
این دستور  خروجی حاصل از join  را گروه بندی خواهد کرد. Group join یک توالی سلسله مراتبی را تولید می‌کند که در آن یک عنصر از توالی اول با یک یا چند عنصر در توالی دوم در ارتباط است.
مثال:
var query = from recipe in recipes
join review in reviews on recipe.Id equals review.RecipeId
into reviewGroup
select new //anonymous type
{
   RecipeName = recipe.Name,
   Reviews = reviewGroup//collection of related reviews
};

foreach (var item in query)
{
   Console.WriteLine($"Review for {item.RecipeName}");
   foreach (var review in item.Reviews)
   {
       Console.WriteLine($"-{review.ReviewText}");
   }
}
در این مثال از 2 توالی تعریف شده‌ی در مثال قبل استفاده کرده‌ایم.
متغیر reviewGroup توالی حاصل از اجرای join  را نمایش می‌دهد. برای ایجاد توالی خروجی، نتیجه به یک نوع بی نام، بازتاب شده است. هر عنصر در نوع بی نام یک گروه را نشان می‌دهد. نوع بی نام شامل دو خصوصیت RecipeName که مقدار آن از توالی اول می‌آید و Reviews که حاصل خروجی Join است می‌باشد.
خروجی مثال بالا به شکل زیر است:
Review for Mashed Potato
-Tasty!
-Not nice :(
-Pretty good
Review for Crispy Duck
-Too hard
-Loved it
Review for Sachertorte
همانطور که می‌بینید اینبار عنصر Sachertorte در خروجی ظاهر شده است. اگر عنصری در توالی سمت چپ وجود داشته باشد که عنصر و یا عناصری در توالی سمت راست با آن متناظر نباشند، آنگاه دستور join،  یک لیست خالی را برای این عنصر تخصیص می‌دهد.

Left outer join
برای آنکه بتوان یک خروجی غیر سلسله مراتبی و اصطلاحا flat را ایجاد کرد که مانند خروجی مثال group join  باشد (نمایش عنصر Sachertorte) می‌توان از عملگر  ()DefaultIfEmpty به‌همراه یک عبارت from اضافی و معرفی یک متغیر Range استفاده کرد. در صورت نبودن عنصر متناظر در توالی دوم این متغیر range بصورت null تنظیم می‌شود.
مثال :
در این مثال از مجموعه‌های تعریف شده‌ی در بخش اول مطلب استفاده کرده‌ایم:
var query = from recipe in recipes
join review in reviews on recipe.Id equals review.RecipeId
into reviewGroup
from rg in reviewGroup.DefaultIfEmpty()
select new //anonymous type
{
   RecipeName = recipe.Name,
   //RecipeReview = rg.ReviewText SystemNullException
   RecipeReview = (rg == null ? "n/a" : rg.ReviewText)
};

foreach (var item in query)
{
   Console.WriteLine($"{item.RecipeName}-{item.RecipeReview}");
}
در بخشی از کد بالا به خاطر وجود حالت null، در خروجی استثنائی رخ خواهد داد. به همین خاطر قبل از تخصیص، مقدار را کنترل کرده‌ایم.
خروجی مثال فوق به شکل زیر است:
Mashed Potato-Tasty!
Mashed Potato-Not nice :(
Mashed Potato-Pretty good
Crispy Duck-Too hard
Crispy Duck-Loved it
Sachertorte-n/a
همانطور که مشاهده می‌کنید، خروجی به صورت غیر سلسله مراتبی و flat می‌باشد. از طریق دستور select و جایگزین کردن مقدار N/A به جای null، عنصر Sachertorte را در خروجی مهیا کرده‌ایم .
یک راه جایگزین برای کنترل مقدار null در دستور select بازسازی متد DefaultIfEmpty به‌صورتی است که به جای ایجاد مقدار null، یک نمونه‌ی جدید از مدل review را با مقدار پیش فرض N/A ایجاد کند.
مثال:
var query = from recipe in recipes
join review in reviews on recipe.Id equals review.RecipeId
into reviewGroup
from rg in reviewGroup.DefaultIfEmpty(new Review { ReviewText = "N/A" })
select new //anonymous type
{
   RecipeName = recipe.Name,
   RecipeReview = rg.ReviewText
};

foreach (var item in query)
{
   Console.WriteLine($"{item.RecipeName}-{item.RecipeReview}");
}
خروجی مثال بالا :
 Mashed Potato-Tasty!
Mashed Potato-Not nice :(
Mashed Potato-Pretty good
Crispy Duck-Too hard
Crispy Duck-Loved it
Sachertorte-N/A 
مطالب
تهیه گزارش از منسوخ شده‌های مورد استفاده در SQL Server 2008

مطلب "منسوخ شده‌ها در نگارش‌های جدید SQL server" را احتمالا به خاطر دارید. جهت تکمیل آن، کوئری زیر را هم می‌توان ذکر کرد:

SELECT instance_name,
cntr_value
FROM sys.dm_os_performance_counters
WHERE OBJECT_NAME = 'SQLServer:Deprecated Features'
AND cntr_value > 0
ORDER BY
cntr_value DESC

توسط این کوئری گزارشی از منسوخ شده‌های مورد استفاده‌ در دیتابیس‌های شما ارائه می‌شود. برای مثال چندبار از text و ntext استفاده کرده‌اید، آیا هنوز compatibility level دیتابیس‌های خود را تغییر نداده‌اید و مثال‌هایی از این دست.

برای مثال جهت یافتن سریع فیلدهای منسوخ شده text و image دیتابیس جاری از کوئری زیر می‌توان کمک گرفت:
SELECT O.Name,
col.name AS ColName,
systypes.name
FROM syscolumns col
INNER JOIN sysobjects O
ON col.id = O.id
INNER JOIN systypes
ON col.xtype = systypes.xtype
WHERE O.Type = 'U'
AND OBJECTPROPERTY(o.ID, N'IsMSShipped') = 0
AND systypes.name IN ('text', 'ntext', 'image')
ORDER BY
O.Name,
Col.Name