我需要通過OrderDate找到每個客戶的第一個和最后一個訂單,以及每個訂單中業務量最大的專案的名稱和SKU。作為參考,客戶表有>15萬條記錄,而訂單和OrderDetails(這些是專案)則要多得多。
注意:訂單和它們各自的專案都應該和客戶在同一行
。定單
OrderID OrderDate CustomerID BusinessVolumeTotal Subtotal
13212 '2021-09-06' 512312 500.00 25.60
OrderDetails
OrderID ItemCode ItemDescription BusinessVolume
13212 'SKW-BS' 'Some item' 450.00
在我的第一個查詢中,我試圖堅持使用連接而不是子查詢,結果是這樣的
select distinct(c.CustomerID), c. FirstName ' ' c.LastName as Name,
cs.CustomerStatusDescription as Status。
ct.CustomerTypeDescription as Type, pv.Volume80 as G3,
fo.OrderID,fo.OrderDate,fo.SubTotal,fo.Country, fod.ItemCode, fod.ItemDescription, fopt.PriceTypeID,
lo.OrderID,lo.OrderDate,lo.SubTotal,lo.Country, lod.ItemCode, lod.ItemDescription, lopt.PriceTypeID
from Customers c
left join CustomerTypes ct on ct.CustomerTypeID= c.CustomerTypeID
left join CustomerStatuses cs on cs.CustomerStatusID= c.CustomerStatusID
left join PeriodVolumes pv on pv.CustomerID= c.CustomerID
left join Orders fo on fo.CustomerID = c.CustomerID -- First Order
left join Orders lo on lo.CustomerID = c.CustomerID -- last Order
left join OrderDetails fod on fod.OrderID= fo.OrderID
left join OrderDetails lod on lod.OrderID= lo.OrderID
left join PriceTypes fopt on fo.PriceTypeID = fopt.PriceTypeID
left join PriceTypes lopt on lo.PriceTypeID= lopt.PriceTypeID
where c.CustomerStatusID in (1,2)
and c.CustomerTypeID in (2, 3)
and pv.PeriodTypeID = 2
/* 一階 */
and fo. OrderID = (select top 1(OrderID) from Orders where CustomerID = c. CustomerID and OrderStatusID>=7 order by OrderDate )
and fod. ItemID = (select top 1(ItemID) from OrderDetails where OrderID = fo. OrderID order by BusinessVolume)
/* Last Order */
and lo. OrderID = (select top 1(OrderID) from Orders where CustomerID = c. CustomerID and OrderStatusID> =7 order by OrderDate desc)
and lod. ItemID = (select top 1(ItemID) from OrderDetails where OrderID = lo. OrderID order by BusinessVolume desc)
and pv. PeriodID = (select PeriodID from Periods where PeriodTypeID=2 and StartDate < = @now and EndDate >= @now)
但是,這最終執行了大約6-7分鐘。從解釋計劃來看,大部分時間被基于OrderStatusID >=7的訂單關鍵查詢所占用。
所以我嘗試使用視窗函式來實作同樣的目的:
select distinct(c.CustomerID), c. FirstName ' ' c.LastName as Name, cs.CustomerStatusDescription as Status,
ct.CustomerTypeDescription as Type, pv.Volume80 as G3,
fal.*
from Customers c
left join CustomerTypes ct on ct.CustomerTypeID = c.CustomerTypeID
left join CustomerStatuses cs on cs.CustomerStatusID= c.CustomerStatusID
left join PeriodVolumes pv on pv.CustomerID= c.CustomerID
left join(
select select
客戶ID。
max(case when MinDate = 1 then OrderID end) FirstOrderID,
max(case when MinDate = 1 then OrderDate end) FirstOrderDate。
max(case when MinDate = 1 then BusinessVolumeTotal end) FirstBVTotal,
max(case when MinDate = 1 then PriceTypeDescription end) FirstPriceType。
max(case when MinDate = 1 then ItemCode end) FirstItemCode。
max(case when MinDate = 1 then ItemDescription end) FirstItemDescription,
max(case when MaxDate= 1 then OrderID end) LastOrderID,
max(case when MaxDate = 1 then OrderDate end) 最后的訂單日期。
max(case when MaxDate = 1 then BusinessVolumeTotal end) LastBVTotal,
max(case when MaxDate= 1 then PriceTypeDescription end) LastPriceType。
max(case when MaxDate = 1 then ItemCode end) LastItemCode。
max(case when MaxDate= 1 then ItemDescription end) 最后的專案描述(LastItemDescription
從起
(
select distinct o.CustomerID。
o.OrderID,
o.OrderDate,
o.BusinessVolumeTotal,
PT.PriceTypeDescription。
RANK() over (partition byo. CustomerID order by OrderDate) as MinDate,
RANK() over (partition by o. CustomerID order by OrderDate desc) as MaxDate,
FIRST_VALUE(ItemCode) over (partition by od. OrderID order by BusinessVolume desc) as ItemCode,
FIRST_VALUE(ItemDescription) over (partition by od. OrderID order by BusinessVolume desc) as ItemDescription
from Orders o
left join OrderDetails od on od.OrderID= o.OrderID
left join PriceTypes PT on o.PriceTypeID= PT.PriceTypeID
where o.OrderStatusID >= 7
)fal
group by CustomerID
) fal on c.CustomerId = fal.CustomerID
where c.CustomerStatusID in (1,2)
and c.CustomerTypeID in (2, 3)
and pv.PeriodTypeID = 2
/* CurrentG3 */
and pv. PeriodID = (select PeriodID from Periods where PeriodTypeID=2 and StartDate < = @now and EndDate >= @now)
唉,這下子執行的時間更長了。如果可能的話,我需要一種方法來優化它。
次要的查詢
我還需要一個過去3個月、6個月和12個月內每個訂單的數量和總和。目前,我在原查詢結果出來后,以程式化的方式進行了二次查詢,并轉發了客戶ID,就像這樣:select count(OrderID) asCnt, sum(BusinessVolumeTotal) as Bv, CustomerID
from Orders where OrderStatusID > 6 and OrderTypeID in (1, 4,8,11)
and OrderDate > = @timeAgo and CustomerID in @ids group by CustomerID
次數為3,因為3、6和12個月。理想情況下,我也想讓這部分內容成為原始內容,但我對如何做到這一點并沒有一個好的想法,尤其是在加入訂單的程序中是多么的曲折。
因此,在理想情況下,我最終會得到一個像這樣的結果表
。CustomerID Name CustomerStatus CustomerType FirstOrderID FirstOrderDate FirstBVTotal FirstItemCode FirstItemDesc FirstPriceType LastOrderID LastOrderDate LastBVTotal LastItemCode LastItemDesc LastPriceType ThreeMonthCount ThreeMonthTotal SixMonthCount SixtyMonthTotal TwelveMonthCount TwelveMonthTotal
512312 'Jane Doe'/span> 'Active' 'Retail' 13212 '2020-06-06' 50. 00 'Item1' 'Item 1 desc' 'Retail' 14321 '2021-09-01' 200. 00 'Item2' 'Item 2 desc' 'Retail' 45 4305。 00 76 8545.60 183 21542.95
如果您對如何優化或減少查詢有任何幫助和建議,以及您認為我做錯了什么,我們將非常感激。
P.S. 我不知道這個標題是否合適,也不知道我是否能夠在以后改變它,我已經有一段時間沒有用SO來問題了。
更新
實際執行計劃
查詢1的實際執行計劃:
https://www.brentozar.com/pastetheplan/?id=SJd56RSmK
查詢2的實際執行計劃:
https://www.brentozar.com/pastetheplan/?id=BJ7QHk87Y
uj5u.com熱心網友回復:
我認為,對于這種型別的查詢,你需要記住兩個要點:
。
ROW_NUMBER來獲得任一方向的第一順序,但你不應該使用另一個對立的ROW_NUMBER來獲得最后一個。而是使用LEAD來檢查下一行是否存在,從而告訴你這是否是最后一行。然后你可以使用條件聚合。APPLY,它可以準確地挑選出你需要的行。
我認為對于OrderDetails,我們應該使用apply,因為每個客戶只有兩個訂單,我們需要找到。這確實需要很好的索引,所以如果OrderDetails沒有很好的索引,那么你可能也想換成一個行編號的解決方案。
select
c.CustomerID,
c.FirstName ' ' c.LastName as Name,
cs.CustomerStatusDescription as Status。
ct.CustomerTypeDescription as Type,
pv.Volume80 as G3。
o.FirstOrderID,
o.FirstOrderDate,
o.FirstSubTotal,
o.FirstCountry,
fod.ItemCode as FirstItemCode,
fod.ItemDescription as FirstItemDescription,
fopt.PriceTypeDescription as FirstPriceTypeDescription,
o.LastOrderID,
o.LastOrderDate,
o.LastSubTotal,
o.LastCountry,
lod.ItemCode as LastItemCode,
lod.ItemDescription as LastItemDescription,
lopt.PriceTypeDescription as LastPriceTypeDescription
from Customers c
left join CustomerTypes ct on ct.CustomerTypeID= c.CustomerTypeID
left join CustomerStatuses cs on cs.CustomerStatusID= c.CustomerStatusID
left join PeriodVolumes pv on pv.CustomerID= c.CustomerID
and pv.PeriodTypeID = 2
and pv.PeriodID = (
select top 1 PeriodID
from Periods p
where p.PeriodTypeID= 2
and p.StartDate<= @now
and p.EndDate>= @now
)
left join (
select select o.CustomerID.
o.CustomerID。
min(case when rn = 1 then OrderID end) as FirstOrderId,
min(case when rn = 1 then OrderDate end) as FirstOrderDate,
min(case when rn = 1 then SubTotal end) as FirstSubTotal,
min(case when rn = 1 then Country end) as FirstCountry,
min(case when nx 是 null then OrderID end) as LastOrderId,
min(case when nx 是 null then OrderDate end) as LastOrderDate,
min(case when nx 是 null then SubTotal end) as LastSubTotal,
min(case when nx 是 null then Country end) as LastCountry,
count(case when o. OrderDate >= DATEADD(month, 3, GETDATE() then 1 end) as ThreeMonthCount,
sum(case when o. OrderDate >= DATEADD(month, 3, GETDATE() then BusinessVolumeTotal end) as ThreeMonthTotal,
count(case when o. OrderDate >= DATEADD(month, 6, GETDATE() then 1 end) as SixMonthCount,
sum(case when o. OrderDate >= DATEADD(month, 6, GETDATE() then BusinessVolumeTotal end) as SixMonthTotal,
count(case when o. OrderDate >= DATEADD(month, 12, GETDATE() then 1 end) as TwelveMonthCount,
sum(case when o. OrderDate >= DATEADD(month, 12, GETDATE() then BusinessVolumeTotal end) as TwelveMonthTotal
from (
select *,
ROW_NUMBER() over (partition by o. CustomerID order by OrderDate) as rn,
LEAD(OrderID) over (partition by o. CustomerID order by OrderDate) as nx
from Orders o
where o.OrderStatusID >= 7
and o. OrderTypeID in (1,4, 8, 11)
and o.OrderDate >=@timeAgo
) o
group by o.CustomerID
) o on o.CustomerID = c.CustomerID
outer apply (
select top 1
od.ItemCode。
od.ItemDescription
from OrderDetails od
訂單 by od.BusinessVolume desc
where od.OrderID = o.FirstOrderId
) fod
outer apply (
select top 1
od.ItemCode。
od.ItemDescription
from OrderDetails od
訂單 by od.BusinessVolume desc
where od.OrderID = o.LastOrderId
) 住宿
left join PriceTypes fopt on fopt.PriceTypeID= o.FirstPriceTypeID
left join PriceTypes lopt on lopt.PriceTypeID= o.LastPriceTypeID
where c.CustomerStatusID in (1, 2)
and c.CustomerTypeID in (2,3) 。
我也要給你一個行編號的版本,因為從你的執行計劃來看,它實際上可能更好。你需要同時嘗試這兩種方法
select
c.CustomerID,
c.FirstName ' ' c.LastName as Name,
cs.CustomerStatusDescription as Status。
ct.CustomerTypeDescription as Type,
pv.Volume80 as G3。
o.FirstOrderID,
o.FirstOrderDate,
o.FirstSubTotal,
o.FirstCountry,
o.FirstItemCode,
o.FirstItemDescription,
o.FirstPriceTypeDescription,
o.LastOrderID,
o.LastOrderDate,
o.LastSubTotal,
o.LastCountry,
o.LastItemCode,
o.LastItemDescription,
o.LastPriceTypeDescription
from Customers c
left join CustomerTypes ct on ct.CustomerTypeID= c.CustomerTypeID
left join CustomerStatuses cs on cs.CustomerStatusID= c.CustomerStatusID
left join PeriodVolumes pv on pv.CustomerID= c.CustomerID
and pv.PeriodTypeID = 2
and pv.PeriodID = (
select top 1 PeriodID
from Periods p
where p.PeriodTypeID= 2
and p.StartDate<= @now
and p.EndDate>= @now
)
left join (
select select o.CustomerID.
o.CustomerID。
min(case when rn = 1 then o. OrderID end) as FirstOrderId,
min(case when rn = 1 then o。 OrderDate end) as FirstOrderDate,
min(case when rn = 1 then o。 SubTotal end) as FirstSubTotal,
min(case when rn = 1 then o。 國家結束) as FirstCountry,
min(case when rn = 1 then od。 專案代碼 end) as FirstItemCode,
min(case when rn = 1 then od。 專案描述 end) as FirstItemDescription,
min(case when rn = 1 then opt。 PriceTypeDescription end) as FirstPriceTypeDescription,
min(case when nx 是 null then o. OrderID end) as LastOrderId,
min(case when nx 是 null then o. OrderDate end) as LastOrderDate,
min(case when nx 是 null then o. SubTotal end) as LastSubTotal,
min(case when nx 是 null then o. 國家 結束) as LastCountry,
min(case when nx 是 null then od. 專案代碼 end) as LastItemCode,
min(case when nx 是 null then od. 專案描述 end) as LastItemDescription,
min(case when nx 是 null then opt.com PriceTypeDescription end) as LastPriceTypeDescription,
count(case when o. OrderDate >= DATEADD(month, 3, GETDATE() then 1 end) as ThreeMonthCount,
sum(case when o. OrderDate >= DATEADD(month, 3, GETDATE() then BusinessVolumeTotal end) as ThreeMonthTotal,
count(case when o. OrderDate >= DATEADD(month, 6, GETDATE() then 1 end) as SixMonthCount,
sum(case when o. OrderDate >= DATEADD(month, 6, GETDATE() then BusinessVolumeTotal end) as SixMonthTotal,
count(case when o. OrderDate >= DATEADD(month, 12, GETDATE() then 1 end) as TwelveMonthCount,
sum(case when o. OrderDate >= DATEADD(month, 12, GETDATE() then BusinessVolumeTotal end) as TwelveMonthTotal
from (
select *,
ROW_NUMBER() over (partition by o. CustomerID order by OrderDate) as rn,
LEAD(OrderID) over (partition by o. CustomerID order by OrderDate) as nx
from Orders o
where o.OrderStatusID >= 7
and o. OrderTypeID in (1,4, 8, 11)
and o.OrderDate >=@timeAgo
) o
left join PriceTypes opt on opt.PriceTypeID = o.PriceTypeID
join (
select *,
ROW_NUMBER() over (partition by od. OrderID order by od.BusinessVolume desc) as rn
from OrderDetails od
) od on od.OrderID = o.OrderId
where rn = 1 or nx is null >。
) o on o.CustomerID = c.CustomerID
where c.CustomerStatusID in (1,2)
and c.CustomerTypeID in (2,3) 。
良好的索引對于良好的性能是必不可少的。我希望在你的表上有大致如下的索引,無論是聚類的還是非聚類的(聚類的索引INCLUDE每一個其他的列都是自動的),如果需要,你顯然可以添加其他INCLUDE列:
Customers (CustomerID) INCLUDE (FirstName, LastName)
CustomerTypes (CustomerTypeID) INCLUDE (CustomerTypeDescription)
CustomerStatuses (CustomerStatusID) INCLUDE (CustomerTypeDescription)
PeriodVolumes (CustomerID) INCLUDE (Volume80)
Periods (PeriodTypeID, StartDate, PeriodID) INCLUDE (EndDate) --可以互換Start和End。
訂單 (CustomerID, OrderDate) 包括 (OrderStatusID, SubTotal, Country, BusinessVolumeTotal)
OrderDetails (OrderID, BusinessVolume) INCLUDE (ItemCode ItemDescription)
PriceTypes (PriceTypeID) INCLUDE (PriceTypeDescription)
你應該仔細考慮INNER與LEFT連接,因為優化器可以更容易地在INNER連接中移動。
還要注意的是,DISTINCT不是一個函式,它是在整個列的集合上計算的。一般來說,我們可以認為,如果查詢中出現了DISTINCT,那么這個連接就沒有經過正確的思考。
轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/323137.html
標籤:
