۱۴۰۴/۰۷/۰۴ Nebular

CROSS APPLY و OUTER APPLY

📌 CROSS APPLY چیست؟

  • CROSS APPLY یک Join خاص است که بین یک جدول اصلی (Left Table) و یک Table-Valued Function یا Subquery که برای هر ردیف جدول اصلی محاسبه می‌شود، ایجاد می‌کنه.
  • به زبان ساده: برای هر ردیف جدول اصلی، جدول سمت راست (یا تابع) اجرا می‌شود و نتیجه با همان ردیف ترکیب می‌شود.
  • مشابه INNER JOIN هست، ولی دینامیک است و می‌تواند بر اساس مقدار هر ردیف جدول اصلی، محاسبه متفاوت داشته باشد.

📌 نکات کلیدی

  1. مثل INNER JOIN است → فقط ردیف‌هایی که نتیجه دارند نمایش داده می‌شوند.
  2. Table-Valued Function یا Subquery می‌تواند به ستون‌های جدول اصلی دسترسی داشته باشد.
  3. اگر بخواهیم مشابه LEFT JOIN باشه → از OUTER APPLY استفاده می‌کنیم.

📌 مثال کاربردی 1 – با Table-Valued Function

فرض کن یک تابع داریم که ۵ بالاترین فروش یک کارمند را برمی‌گرداند:

CREATE FUNCTION TopSales(@EmpID INT)
RETURNS TABLE
AS
RETURN
(
    SELECT TOP 5 *
    FROM Sales
    WHERE EmployeeID = @EmpID
    ORDER BY SaleDate DESC
);

حالا می‌خوایم برای همه کارمندان، ۵ فروش آخرشون را ببینیم:

SELECT E.EmpID, E.Name, S.SaleID, S.Amount, S.SaleDate
FROM Employees E
CROSS APPLY dbo.TopSales(E.EmpID) S;

✅ اینجا برای هر ردیف Employee، تابع TopSales اجرا می‌شود و فقط ۵ ردیف آخر فروش مربوط به همان کارمند برمی‌گردد.


📌 مثال کاربردی 2 – با Subquery

فرض کن می‌خوایم آخرین سفارش هر مشتری را پیدا کنیم:

SELECT C.CustomerID, C.Name, O.OrderID, O.OrderDate
FROM Customers C
CROSS APPLY
(
    SELECT TOP 1 *
    FROM Orders O
    WHERE O.CustomerID = C.CustomerID
    ORDER BY O.OrderDate DESC
) AS O;
  • هر ردیف Customer → Subquery اجرا می‌شود → آخرین Order برای همان مشتری برمی‌گردد.
  • مزیت: نیازی به Join پیچیده + Group By + MAX نیست.

برای هر مشتری (Customer)، فقط گران‌ترین سفارش (Order با بیشترین Amount) رو بیاریم.

جدول‌ها:

Customers

CustomerIDName
1Ali
2Sara
3Reza

Orders

OrderIDCustomerIDAmount
1011100
1021200
1032300
1042400
1053500

✅ روش ۱ – با GROUP BY و JOIN (روش استاندارد set-based)

SELECT
    C.Name,
    O.OrderID,
    O.Amount
FROM Customers AS C
JOIN (
    SELECT
        CustomerID,
        MAX(Amount) AS MaxAmount
    FROM Orders
    GROUP BY CustomerID
) AS M
    ON C.CustomerID = M.CustomerID
JOIN Orders AS O
    ON O.CustomerID = M.CustomerID
    AND O.Amount = M.MaxAmount;

🔍 گام به گام توضیح:

  1. ساب‌کوئری داخلی (M) برای هر CustomerID بیشترین مبلغ (MAX(Amount)) را پیدا می‌کند.
    خروجی آن مثلاً این می‌شود: CustomerIDMaxAmount120024003500
  2. بعد با جدول اصلی Orders JOIN می‌کنیم تا رکورد واقعی آن سفارش را پیدا کنیم (چون فقط با MAX مقدار داریم، نه OrderID).
  3. در نهایت با جدول Customers JOIN می‌کنیم تا نام مشتری را هم بگیریم.

📊 نتیجه نهایی:

NameOrderIDAmount
Ali102200
Sara104400
Reza105500

✅ روش ۲ – با CROSS APPLY (برای مقایسه)

SELECT
    C.Name,
    O.OrderID,
    O.Amount
FROM Customers AS C
CROSS APPLY (
    SELECT TOP 1 *
    FROM Orders AS O
    WHERE O.CustomerID = C.CustomerID
    ORDER BY O.Amount DESC
) AS O;

📊 خروجی دقیقاً همان است
ولی CROSS APPLY در واقع برای هر ردیف از Customers یک ساب‌کوئری مجزا اجرا می‌کند.


🔍 تفاوت فنی و عملکردی

ویژگیGROUP BY + JOINCROSS APPLY
نوع پردازشset-based (همه‌با‌هم)row-by-row (برای هر مشتری جداگانه)
Performance در دیتای زیادمعمولاً سریع‌تر، مخصوصاً با index خوبکندتر روی داده زیاد
خوانایی برای “Top-N per group”کمی پیچیده‌ترساده‌تر و قابل‌خواندن‌تر
نیاز به تابع تجمیعی (MAX, MIN)داردندارد
پشتیبانی از ORDER BY مستقیم❌ فقط در زیرکوئری✅ بله، درون APPLY


📌 CROSS APPLY vs INNER JOIN

ویژگیCROSS APPLYINNER JOIN
محاسبه سمت راست بر اساس ردیف چپ✅ بله❌ نه، JOIN ثابت است
می‌تواند Table-Valued Function باشد✅ بله❌ نه
فیلتر پویا بر اساس ستون‌های چپ✅ بله❌ نه
نتیجه مشابه INNER JOIN✅ بله✅ بله

📌 OUTER APPLY

  • وقتی جدول سمت راست ممکنه نتیجه نداشته باشد و می‌خوای ردیف چپ همچنان نمایش داده شود:
SELECT C.CustomerID, C.Name, O.OrderID
FROM Customers C
OUTER APPLY
(
    SELECT TOP 1 *
    FROM Orders O
    WHERE O.CustomerID = C.CustomerID
    ORDER BY O.OrderDate DESC
) AS O;
  • مشتری بدون سفارش هم در نتیجه نمایش داده می‌شود و ستون‌های OrderNULL می‌شوند.

💡 جمع‌بندی:

  • CROSS APPLY → برای محاسبه دینامیک و ردیف به ردیف، مثل INNER JOIN پویا.
  • OUTER APPLY → مشابه LEFT JOIN، وقتی ممکنه سمت راست داده نداشته باشه.
  • کاربرد اصلی: آخرین رکورد، TOP N رکورد، Table-Valued Function روی هر ردیف

OUTER APPLY چیست؟

  • همانند LEFT JOIN عمل می‌کند.
  • یعنی اگر سمت راست هیچ داده‌ای نداشته باشد، باز هم ردیف سمت چپ در نتیجه نمایش داده می‌شود و ستون‌های سمت راست NULL می‌شوند.
  • کاربردش وقتی است که جدول یا Subquery سمت راست ممکن است برای بعضی ردیف‌ها خالی باشد.

📌 مثال کاربردی

فرض کن می‌خوایم آخرین سفارش هر مشتری را نمایش بدهیم، حتی اگر بعضی مشتری‌ها هیچ سفارشی نداشته باشند:

SELECT C.CustomerID, C.Name, O.OrderID, O.OrderDate
FROM Customers C
OUTER APPLY
(
    SELECT TOP 1 *
    FROM Orders O
    WHERE O.CustomerID = C.CustomerID
    ORDER BY O.OrderDate DESC
) AS O;

توضیح:

  1. برای هر ردیف از جدول Customers، Subquery سمت راست اجرا می‌شود.
  2. اگر مشتری سفارش داشته باشد → آخرین سفارش برگردانده می‌شود.
  3. اگر مشتری سفارش نداشته باشد → ردیف مشتری نمایش داده می‌شود ولی ستون‌های OrderID و OrderDate NULL خواهند بود.

📌 CROSS APPLY vs OUTER APPLY

ویژگیCROSS APPLYOUTER APPLY
مانند INNER JOIN✅ فقط ردیف‌هایی که سمت راست داده دارند
مانند LEFT JOIN✅ حتی اگر سمت راست NULL باشد، ردیف چپ نمایش داده می‌شود
استفاده رایجTop N رکوردها، Table-Valued Functionآخرین رکورد، Table-Valued Function با امکان NULL

💡 مثال عملی دیگر:

  • جدول Employees و جدول Sales
  • می‌خوایم آخرین فروش هر کارمند را ببینیم. بعضی کارمندها هنوز فروشی ندارند.
SELECT E.EmpID, E.Name, S.SaleID, S.Amount
FROM Employees E
OUTER APPLY
(
    SELECT TOP 1 *
    FROM Sales S
    WHERE S.EmployeeID = E.EmpID
    ORDER BY SaleDate DESC
) AS S;
  • کارمندی بدون فروش → ردیفش در نتیجه هست ولی ستون‌های SaleID و Amount NULL هستند.

✅ جمع‌بندی:

  • CROSS APPLY → Inner Apply → فقط ردیف‌هایی که سمت راست داده دارند.
  • OUTER APPLY → Left Apply → همه ردیف‌های سمت چپ، حتی اگر سمت راست NULL باشد.
Accept Cookies
Accept Cookies
[your-shortcode]