۱۴۰۴/۰۷/۰۴
✅ روش ۱ – با
✅ روش ۲ – با
CROSS APPLY و OUTER APPLY
📌 CROSS APPLY چیست؟
CROSS APPLYیک Join خاص است که بین یک جدول اصلی (Left Table) و یک Table-Valued Function یا Subquery که برای هر ردیف جدول اصلی محاسبه میشود، ایجاد میکنه.- به زبان ساده: برای هر ردیف جدول اصلی، جدول سمت راست (یا تابع) اجرا میشود و نتیجه با همان ردیف ترکیب میشود.
- مشابه INNER JOIN هست، ولی دینامیک است و میتواند بر اساس مقدار هر ردیف جدول اصلی، محاسبه متفاوت داشته باشد.
📌 نکات کلیدی
- مثل INNER JOIN است → فقط ردیفهایی که نتیجه دارند نمایش داده میشوند.
- Table-Valued Function یا Subquery میتواند به ستونهای جدول اصلی دسترسی داشته باشد.
- اگر بخواهیم مشابه 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
| CustomerID | Name |
|---|---|
| 1 | Ali |
| 2 | Sara |
| 3 | Reza |
Orders
| OrderID | CustomerID | Amount |
|---|---|---|
| 101 | 1 | 100 |
| 102 | 1 | 200 |
| 103 | 2 | 300 |
| 104 | 2 | 400 |
| 105 | 3 | 500 |
✅ روش ۱ – با 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;
🔍 گام به گام توضیح:
- سابکوئری داخلی (
M) برای هرCustomerIDبیشترین مبلغ (MAX(Amount)) را پیدا میکند.
خروجی آن مثلاً این میشود: CustomerIDMaxAmount120024003500 - بعد با جدول اصلی
OrdersJOIN میکنیم تا رکورد واقعی آن سفارش را پیدا کنیم (چون فقط با MAX مقدار داریم، نه OrderID). - در نهایت با جدول
CustomersJOIN میکنیم تا نام مشتری را هم بگیریم.
📊 نتیجه نهایی:
| Name | OrderID | Amount |
|---|---|---|
| Ali | 102 | 200 |
| Sara | 104 | 400 |
| Reza | 105 | 500 |
✅ روش ۲ – با 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 + JOIN | CROSS APPLY |
|---|---|---|
| نوع پردازش | set-based (همهباهم) | row-by-row (برای هر مشتری جداگانه) |
| Performance در دیتای زیاد | معمولاً سریعتر، مخصوصاً با index خوب | کندتر روی داده زیاد |
| خوانایی برای “Top-N per group” | کمی پیچیدهتر | سادهتر و قابلخواندنتر |
| نیاز به تابع تجمیعی (MAX, MIN) | دارد | ندارد |
| پشتیبانی از ORDER BY مستقیم | ❌ فقط در زیرکوئری | ✅ بله، درون APPLY |
📌 CROSS APPLY vs INNER JOIN
| ویژگی | CROSS APPLY | INNER 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;
توضیح:
- برای هر ردیف از جدول
Customers، Subquery سمت راست اجرا میشود. - اگر مشتری سفارش داشته باشد → آخرین سفارش برگردانده میشود.
- اگر مشتری سفارش نداشته باشد → ردیف مشتری نمایش داده میشود ولی ستونهای
OrderIDوOrderDateNULL خواهند بود.
📌 CROSS APPLY vs OUTER APPLY
| ویژگی | CROSS APPLY | OUTER 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
[your-shortcode]