Top 20 Count of SKUs per Store - sql

I am trying to create a query that pulls down the top 20 out of stock SKUs per store. Here is what I have so far:
;with top20OOS as
(select wi.SKU
, count(wi.SKU) OOSCount
, wi.ProductId
, wi.PseudoStoreId
from WaveItem wi (nolock)
where wi.isOutOfStock = 1
and DateAdded between '2018-01-01' and '2018-05-31'
group by wi.SKU, p.ProductName, wi.ProductId, bo.brandname, p.size, mo.name,
wi.PseudoStoreId
)
select oos.SKU
, oos.ProductName
, oos.OOSCount
, s.StoreName
, s.StoreID from top20OOS oos
join WaveItem wi (nolock) on wi.ProductId = oos.ProductId
and wi.PseudoStoreId = oos.PseudoStoreId
join tblStore s (nolock) on s.PseudoStoreID = wi.PseudoStoreId
where s.StoreID in (1094,1088)
and DateAdded between '2018-01-01' and '2018-05-31'
group by oos.SKU, oos.ProductName, s.StoreName, s.StoreID, oos.OOSCount
order by s.StoreName asc, OOSCount desc
This just pulls all of the out of stock SKUs for stores 1094 and 1088, and I need to limit it to the top 20 per store. I am using SQL Server 2017

It's a bit verbose because I can't remember if ROW_NUMBER() plays well with Group By in the same query so I erred on the side of caution and broke it apart into separate subqueries.
;with top20OOS as
(select wi.SKU
, count(wi.SKU) OOSCount
, wi.ProductId
, wi.PseudoStoreId
from WaveItem wi (nolock)
where wi.isOutOfStock = 1
and DateAdded between '2018-01-01' and '2018-05-31'
group by wi.SKU, p.ProductName, wi.ProductId, bo.brandname, p.size, mo.name,
wi.PseudoStoreId
)
SELECT sku,
productName,
OOSCount,
StoreName,
StoreID
FROM (SELECT sku,
productName,
OOSCount,
StoreName,
StoreID,
ROW_NUMBER() OVER
( PARTITION BY StoreID
ORDER BY OOSCount
) AS rn
FROM (select oos.SKU
, oos.ProductName
, oos.OOSCount
, s.StoreName
, s.StoreID from top20OOS oos
join WaveItem wi (nolock) on wi.ProductId = oos.ProductId
and wi.PseudoStoreId = oos.PseudoStoreId
join tblStore s (nolock) on s.PseudoStoreID = wi.PseudoStoreId
where s.StoreID in (1094,1088)
and DateAdded between '2018-01-01' and '2018-05-31'
group by oos.SKU, oos.ProductName, s.StoreName, s.StoreID, oos.OOSCount
) TMP
) TMP2
WHERE rn <= 20;

Related

How can I rotate a table that requires a group by without using pivot and case when in sql?

This is the code I'm trying to run
select min(sod.ModifiedDate) as [ModifiedDate]
,Bikes = (select sum(LineTotal) from SalesOrderDetail where max(ProductCategoryName) = 'Bikes' and ModifiedDate = sod.ModifiedDatee)
,Components = (select sum(LineTotal) from SalesOrderDetail where max(ProductCategoryName) = 'Components' and ModifiedDate = sod.ModifiedDate)
,Clothing = (select sum(LineTotal) from SalesOrderDetail where max(ProductCategoryName) = 'Clothing' and ModifiedDate = sod.ModifiedDate )
,Accessories = (select sum(LineTotal) from SalesOrderDetail where max(ProductCategoryName) = 'Accessories' and ModifiedDate = sod.ModifiedDate )
from SalesOrderDetail sod
inner join product p on p.ProductID = sod.ProductID
inner join ProductSubcategory ps on ps.ProductSubcategoryID = p.ProductSubcategoryID
inner join ProductCategory pc on pc.ProductCategoryID = ps.ProductCategoryID
group by ProductCategoryName
,sod.ModifiedDate
,datepart(year, sod.ModifiedDate)
,datepart(month, sod.ModifiedDate)
,datepart(day, sod.ModifiedDate)
order by datepart(year, sod.ModifiedDate)
,datepart(month, sod.ModifiedDate)
,datepart(day, sod.ModifiedDate)
I can't figure out how to make it so it splits the LineTotal into the four ProductNameCategory like this: (expected result)
ModifiedDate
Bikes
Components
Clothing
Accessories
2005-07-01 00:00:00.000
467709.136900
31525.960400
2875.153600
1695.666000
2005-07-02 00:00:00.000
13931.520000
NULL
NULL
NULL
2005-07-03 00:00:00.000
15012.178200
NULL
NULL
NULL
2005-07-04 00:00:00.000
7156.540000
NULL
NULL
NULL
2005-07-05 00:00:00.000
15012.178200
NULL
NULL
NULL
All I get is this, it adds all the lineTotal for a given date regardless of ProductCategoryName and then puts the sum in Components, except when the only thing there is that day is Bikes, then he puts it in Bikes.
ModifiedDate
Bikes
Components
Clothing
Accessories
2005-07-01 00:00:00.000
NULL
503805.916900
NULL
NULL
2005-07-02 00:00:00.000
13931.520000
NULL
NULL
NULL
2005-07-03 00:00:00.000
15012.178200
NULL
NULL
NULL
2005-07-04 00:00:00.000
7156.540000
NULL
NULL
NULL
2005-07-05 00:00:00.000
15012.178200
NULL
NULL
NULL
How can I make it look like the expected result without Pivot and case when? I need to get the results showed here using four different methods to then test performance and I already used pivot and case when. I'm trying to use this method I found https://learn.microsoft.com/en-us/troubleshoot/sql/database-design/rotate-table for this specific query
Here's one way to do it without PIVOT or CASE, but it's really ugly as a result, even after pulling some of the joins and aggregations out of the main query:
WITH ProductMeta AS
(
SELECT p.ProductID, CatName = pc.Name
FROM Production.Product AS p
INNER JOIN Production.ProductSubcategory ps
on ps.ProductSubcategoryID = p.ProductSubcategoryID
INNER JOIN Production.ProductCategory pc
on pc.ProductCategoryID = ps.ProductCategoryID
WHERE pc.Name IN (N'Bikes',N'Components',N'Clothing',N'Accessories')
), Agg AS
(
SELECT date = CONVERT(date, sod.ModifiedDate),
pm.CatName,
LineTotal = SUM(LineTotal)
FROM Sales.SalesOrderDetail sod
INNER JOIN ProductMeta AS pm ON sod.ProductID = pm.ProductID
GROUP BY CONVERT(date, sod.ModifiedDate), pm.CatName
)
SELECT a.date
,Bikes = (SELECT SUM(LineTotal) FROM Agg
WHERE date = a.date AND CatName = 'Bikes')
,Components = (SELECT SUM(LineTotal) FROM Agg
WHERE date = a.date AND CatName = 'Components')
,Clothing = (SELECT SUM(LineTotal) FROM Agg
WHERE date = a.date AND CatName = 'Clothing')
,Accessories = (SELECT SUM(LineTotal) FROM Agg
WHERE date = a.date AND CatName = 'Accessories')
FROM Agg AS a
GROUP BY a.date;
This is what I came up with just modifying your code. After downloading AdventureWorks, I validated this runs and the output matches your table above (minus the superfluous 00:00:00 times);
select DISTINCT CAST(sod.ModifiedDate as date) as [ModifiedDate]
, Bikes = (
select sum(LineTotal)
from Sales.SalesOrderDetail as sod2
inner join production.product p on p.ProductID = sod2.ProductID
inner join production.ProductSubcategory ps on ps.ProductSubcategoryID = p.ProductSubcategoryID
inner join production.ProductCategory pc on pc.ProductCategoryID = ps.ProductCategoryID
where pc.[Name] = 'Bikes'
and CAST(sod2.ModifiedDate as date) = CAST(sod.ModifiedDate as date)
)
, Components = (
select sum(LineTotal)
from Sales.SalesOrderDetail as sod2
inner join production.product p on p.ProductID = sod2.ProductID
inner join production.ProductSubcategory ps on ps.ProductSubcategoryID = p.ProductSubcategoryID
inner join production.ProductCategory pc on pc.ProductCategoryID = ps.ProductCategoryID
where pc.[Name] = 'Components'
and CAST(sod2.ModifiedDate as date) = CAST(sod.ModifiedDate as date)
)
, Clothing = (
select sum(LineTotal)
from Sales.SalesOrderDetail as sod2
inner join production.product p on p.ProductID = sod2.ProductID
inner join production.ProductSubcategory ps on ps.ProductSubcategoryID = p.ProductSubcategoryID
inner join production.ProductCategory pc on pc.ProductCategoryID = ps.ProductCategoryID
where pc.[Name] = 'Clothing'
and CAST(sod2.ModifiedDate as date) = CAST(sod.ModifiedDate as date)
)
, Accessories = (
select sum(LineTotal)
from Sales.SalesOrderDetail as sod2
inner join production.product p on p.ProductID = sod2.ProductID
inner join production.ProductSubcategory ps on ps.ProductSubcategoryID = p.ProductSubcategoryID
inner join production.ProductCategory pc on pc.ProductCategoryID = ps.ProductCategoryID
where pc.[Name] = 'Accessories'
and CAST(sod2.ModifiedDate as date) = CAST(sod.ModifiedDate as date)
)
from Sales.SalesOrderDetail sod
order by CAST(sod.modifiedDate as date)
Sample results:
EDIT:
This query completes in less than 0.359 seconds.
WITH prelim as (
select sod2.LineTotal, sod2.ModifiedDate, pc.Name as ProductCategoryName
from Sales.SalesOrderDetail as sod2
inner join production.product p on p.ProductID = sod2.ProductID
inner join production.ProductSubcategory ps on ps.ProductSubcategoryID = p.ProductSubcategoryID
inner join production.ProductCategory pc on pc.ProductCategoryID = ps.ProductCategoryID
)
select DISTINCT CAST(sod.ModifiedDate as date) as [ModifiedDate]
, Bikes = (
SELECT SUM(LineTotal)
FROM prelim as p
WHERE p.ModifiedDate = sod.ModifiedDate
AND p.ProductCategoryName = 'Bikes'
)
, Components = (
SELECT SUM(LineTotal)
FROM prelim as p
WHERE p.ModifiedDate = sod.ModifiedDate
AND p.ProductCategoryName = 'Components'
)
, Clothing = (
SELECT SUM(LineTotal)
FROM prelim as p
WHERE p.ModifiedDate = sod.ModifiedDate
AND p.ProductCategoryName = 'Clothing'
)
, Accessories = (
SELECT SUM(LineTotal)
FROM prelim as p
WHERE p.ModifiedDate = sod.ModifiedDate
AND p.ProductCategoryName = 'Accessories'
)
from Sales.SalesOrderDetail sod
order by CAST(sod.modifiedDate as date)

Get first and last Order and the highest value Item in each order for each Customer, all of which are separate tables

I need to find the first and last Order for each Customer by OrderDate, and the name and SKU of the item with the highest business volume in each of those orders. For reference, the Customer table has >150k records, and Orders and OrderDetails (these are the Items) a lot more.
Note: Both Orders and their respective items should be in the same row as the Customer
Orders
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
In my first query I attempted to stick to joining instead of subqueries, which resulted in this
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
/* First Order */
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)
But this ends up executing for about 6-7 minutes. From the explain plan, it looks like most of it is taken up by a Key Lookup for Orders based on OrderStatusID >= 7.
So I tried to use window functions to achieve the same:
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
CustomerID,
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) LastOrderDate,
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
from
(
select distinct o.CustomerID,
o.OrderID,
o.OrderDate,
o.BusinessVolumeTotal,
PT.PriceTypeDescription,
RANK() over (partition by o.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)
Alas, this ended up executing even longer. I need a way to optimize this if possible.
Secondary query
I do also need a count and sum of volume per Order in the last 3, 6 and 12 months. I currently do this programatically as secondary queries after the original returns a result, and I forward the CustomerIDs, like this:
select count(OrderID) as Cnt, 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
Times 3, because 3, 6 and 12 months. Ideally, I would also like to make this part of the original but I don't really have a good idea on how to do it, especially with how convoluted the joining is with the orders.
So ideally I'd end up with a result table like this
CustomerID Name CustomerStatus CustomerType FirstOrderID FirstOrderDate FirstBVTotal FirstItemCode FirstItemDesc FirstPriceType LastOrderID LastOrderDate LastBVTotal LastItemCode LastItemDesc LastPriceType ThreeMonthCount ThreeMonthTotal SixMonthCount SixMonthTotal TwelveMonthCount TwelveMonthTotal
512312 'Jane Doe' '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
Any help and advice on how to optimize or reduce the query, as well as anything you believe Im doing wrong would be GREATLY appreciated.
P.S. I don't know if the title is fitting and if I'd be able to change it later, it's been a while since I've used SO to ask a question.
UPDATE
Actual Execution Plan for Query 1:
https://www.brentozar.com/pastetheplan/?id=SJd56RSmK
Actual Execution Plan for Query 2:
https://www.brentozar.com/pastetheplan/?id=BJ7QHk87Y
I think you need to keep in mind two main points with this type of query:
The key to good performance with window functions is to not introduce an unnecessary sort. So while you can use ROW_NUMBER to get the first order in either direction, you should not use another opposing ROW_NUMBER to get the last. Rather use LEAD to check if the next row exists, thereby telling you if this is the last row. You can then use conditional aggregation.
There are generally two ways to calculate first/last: a row-numbering solution, as above, or an APPLY, which picks out the exact one you need.
I think that for the OrderDetails we should use an apply, because there are only two orders per customer that we need to find. This does need good indexing, so if OrderDetails is not well indexed, then you may want to switch to a row-numbering solution for this also.
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
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 is null then OrderID end) as LastOrderId,
min(case when nx is null then OrderDate end) as LastOrderDate,
min(case when nx is null then SubTotal end) as LastSubTotal,
min(case when nx is 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
order by od.BusinessVolume desc
where od.OrderID = o.FirstOrderId
) fod
outer apply (
select top 1
od.ItemCode,
od.ItemDescription
from OrderDetails od
order by od.BusinessVolume desc
where od.OrderID = o.LastOrderId
) lod
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);
I'm also going to give you a row-numbering version, as judging by your execution plan, it may actually be better. You need to try both
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
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.Country end) as FirstCountry,
min(case when rn = 1 then od.ItemCode end) as FirstItemCode,
min(case when rn = 1 then od.ItemDescription end) as FirstItemDescription,
min(case when rn = 1 then opt.PriceTypeDescription end) as FirstPriceTypeDescription,
min(case when nx is null then o.OrderID end) as LastOrderId,
min(case when nx is null then o.OrderDate end) as LastOrderDate,
min(case when nx is null then o.SubTotal end) as LastSubTotal,
min(case when nx is null then o.Country end) as LastCountry,
min(case when nx is null then od.ItemCode end) as LastItemCode,
min(case when nx is null then od.ItemDescription end) as LastItemDescription,
min(case when nx is null then opt.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);
Good indexing is essential to good performance. I would expect roughly the following indexes on your tables, either clustered or non-clustered (clustered indexed INCLUDE every other column automatically), you can obviously add other INCLUDE columns if needed:
Customers (CustomerID) INCLUDE (FirstName, LastName)
CustomerTypes (CustomerTypeID) INCLUDE (CustomerTypeDescription)
CustomerStatuses (CustomerStatusID) INCLUDE (CustomerTypeDescription)
PeriodVolumes (CustomerID) INCLUDE (Volume80)
Periods (PeriodTypeID, StartDate, PeriodID) INCLUDE (EndDate) -- can swap Start and End
Orders (CustomerID, OrderDate) INCLUDE (OrderStatusID, SubTotal, Country, BusinessVolumeTotal)
OrderDetails (OrderID, BusinessVolume) INCLUDE (ItemCode ItemDescription)
PriceTypes (PriceTypeID) INCLUDE (PriceTypeDescription)
You should think carefully about INNER vs LEFT joins, because the optimizer can more easily move around an INNER join.
Note also, that DISTINCT is not a function, it is calculated over an entire set of columns. Generally, one can assume that if a DISTINCT is in the query then the joins have not been thought through properly.

How to get biggest value from 2 or more fields in a subquery

I have a table with customers that I join with a fact table with sales, based on invoices.
What I need from my report is to get in first part the biggest value of sales based on the incoming order type (1,2,3,C,D) for a customer for last year. And in the second part to get the same but for current year. What I get as result from my current query is all incoming order types with the customer revenue made for each of them. I tried with outer apply as subquery to get only the top 1 value ordered by revenue descending, but in the result I get the same - For all order types the customer revenue. Please help! I hope my explanation isn't understood only by me (happens a lot..)
use dwh01;
WITH OrderTypeUsedLY AS
(
SELECT
c.CustomerKey
,c.BranchId
,c.CustomerId
,c.CustomerName
,ISNULL(SUM(y.[Sale_Revenue]), 0) as [Sale_Revenue_LY]
,ISNULL(SUM(y.[Sale_GrossMarginTotal]), 0) as [Sale_GrossMarginTotal_LY]
,y.IncomingOrderTypeId as IncomingOrderType_LY
FROM live.DimCustomer c
left join (SELECT s.CustomerKey,iot.IncomingOrderTypeid, s.[Sale_Revenue], s.[Sale_GrossMarginTotal] FROM [dwh01].[live].[FactSales] s
inner join live.DimDate d
on d.DateId = s.PostingDateKey
inner join live.DimIncomingOrderType iot on iot.IncomingOrderTypeKey = s.IncomingOrderTypeKey
where s.ReportCurrencyId = 'LC'
and D.Year = YEAR(GETDATE())-1 --- Last Year
) y on c.CustomerKey = y.CustomerKey
where c.CustomerKey = '157053'
group by c.CustomerKey, c.CustomerId, c.CustomerName, c.BranchId, y.IncomingOrderTypeId
),
--*********************************************************************************************************************************--
OrderTypeCY as(
SELECT
c.CustomerKey
,c.BranchId
,c.SalesRepKey
,c.CustomerId
,c.CustomerName
,ISNULL(SUM(y.[Sale_Revenue]), 0) as [Sale_Revenue_CY]
,ISNULL(SUM(y.[Sale_GrossMarginTotal]), 0) as [Sale_GrossMarginTotal_CY]
,y.IncomingOrderTypeId as IncomingOrderType_CY
FROM live.DimCustomer c
left join (SELECT s.CustomerKey,iot.IncomingOrderTypeid, s.[Sale_Revenue], s.[Sale_GrossMarginTotal] FROM [dwh01].[live].[FactSales] s
inner join live.DimDate d
on d.DateId = s.PostingDateKey
inner join live.DimIncomingOrderType iot on iot.IncomingOrderTypeKey = s.IncomingOrderTypeKey
where s.ReportCurrencyId = 'LC'
and D.Year = YEAR(GETDATE()) --- Current Year
) y on c.CustomerKey = y.CustomerKey
where c.CustomerKey = '157053'
group by c.CustomerKey, c.CustomerId, c.CustomerName, c.BranchId, y.IncomingOrderTypeId, c.SalesRepKey
)
--*********************************************************************************************************************************--
SELECT
otly.BranchId,
rep.SalesRepId,
rep.SalesRepName,
otly.CustomerId,
otly.CustomerName,
otly.Sale_Revenue_LY,
otly.Sale_GrossMarginTotal_LY,
IncomingOrderType_LY,
otcy.Sale_Revenue_CY,
otcy.Sale_GrossMarginTotal_CY,
IncomingOrderType_CY
from OrderTypeUsedLY otly
left join OrderTypeCY otcy
on otly.CustomerKey = otcy.CustomerKey
join live.DimCustomer cus on cus.CustomerKey = otcy.CustomerKey
join live.DimSalesRep rep on rep.SalesRepKey = otcy.SalesRepKey
order by otcy.Sale_Revenue_CY desc, otly.Sale_Revenue_LY desc
,rep.SalesRepId
And here is the outer apply I tried:
outer apply (
SELECT top 1
iot.IncomingOrderTypeId,
Sale_Revenue
FROM [dwh01].[live].DimIncomingOrderType iot
where iot.IncomingOrderTypeKey = y.IncomingOrderTypeKey
order by Sale_Revenue desc) x
In the first select ( with OrderTypeUsed_LY ) I get this:
And I get the same in the second select, but with the values for current year.
The purpose of the report is to see the difference in the incoming order type most used (most profit made with it) for a customer last year and to see if he continues to use it this year, or uses another incoming order type this year.
Again I'm sorry for the bad explanation, I'm trying my best (I understand myself very well)
Here is the expected result:
Expected Result
I marked in red the last year part and in green the current year part.
If you change the subquery to:
SELECT
iot.IncomingOrderTypeKey,
MAX(Sale_Revenue)
FROM [dwh01].[live].DimIncomingOrderType iot
GROUP BY iot.IncomingOrderTypeKey
then you can JOIN (not APPLY) directly on IncomingOrderTypeKey AND Sale_Revenue.
Try this:
USE dwh01;
DECLARE #CustomerKey varchar(6) = '157053'
, #ReportCurrencyId varchar(2) = 'LC'
, #CurrentYear int = YEAR(GETDATE())
, #TargetYear int = YEAR(GETDATE())-1
;
WITH FactsTable AS
(
SELECT
s.CustomerKey
, i.IncomingOrderTypeid
, [Sale_Revenue] = ISNULL(s.[Sale_Revenue] , 0)
, [Sale_GrossMarginTotal] = ISNULL(s.[Sale_GrossMarginTotal], 0)
, d.[Year]
FROM [dwh01].[live].[FactSales] s
inner join live.DimDate d on d.DateId = s.PostingDateKey
inner join live.DimIncomingOrderType i on i.IncomingOrderTypeKey = s.IncomingOrderTypeKey
where
s.CustomerKey = #CustomerKey
and s.ReportCurrencyId = #ReportCurrencyId
)
, OrderTypeTable
(
SELECT
c.CustomerKey
, c.BranchId
, c.CustomerId
, c.CustomerName
, c.SalesRepKey
, IncomingOrderType_LY = SUM(CASE WHEN y.[Year] = #TargetYear THEN y.IncomingOrderTypeId ELSE 0 END)
, [Sale_Revenue_LY] = SUM(CASE WHEN y.[Year] = #TargetYear THEN y.[Sale_Revenue] ELSE 0 END)
, [Sale_GrossMarginTotal_LY] = SUM(CASE WHEN y.[Year] = #TargetYear THEN y.[Sale_GrossMarginTotal] ELSE 0 END)
, IncomingOrderType_LY = SUM(CASE WHEN y.[Year] = #CurrentYear THEN y.IncomingOrderTypeId ELSE 0 END)
, [Sale_Revenue_CY] = SUM(CASE WHEN y.[Year] = #CurrentYear THEN y.[Sale_Revenue] ELSE 0 END)
, [Sale_GrossMarginTotal_CY] = SUM(CASE WHEN y.[Year] = #CurrentYear THEN y.[Sale_GrossMarginTotal] ELSE 0 END)
FROM live.DimCustomer c
left join FactsTable y on y.CustomerKey = c.CustomerKey
group by
c.CustomerKey
, c.BranchId
, c.CustomerId
, c.CustomerName
, y.IncomingOrderTypeId
, c.SalesRepKey
)
SELECT
O.BranchId
, R.SalesRepId
, R.SalesRepName
, O.CustomerId
, O.CustomerName
, O.Sale_Revenue_LY
, O.Sale_GrossMarginTotal_LY
, O.IncomingOrderType_LY
, O.Sale_Revenue_CY
, O..Sale_GrossMarginTotal_CY
, O.IncomingOrderType_CY
from OrderTypeTable O
join live.DimSalesRep R on R.SalesRepKey = O.SalesRepKey
order by
O.Sale_Revenue_CY desc
, O.Sale_Revenue_LY desc
, R.SalesRepId
The solution is with using row_number() function in the inner query of the first CTE:
(
select *, row_number() over (partition by x0.CustomerKey order by x0.Sale_Revenue desc) as rn
from
(
select fs.CustomerKey, iot.IncomingOrderTypeKey,
sum(fs.Sale_Revenue) as Sale_Revenue
FROM [dwh01].[live].DimIncomingOrderType iot
join live.FactSales fs
on iot.IncomingOrderTypeKey = fs.IncomingOrderTypeKey
join live.DimDate d
on d.DateId = fs.PostingDateKey
where d.[year] = #CurrentYear
GROUP BY fs.CustomerKey, iot.IncomingOrderTypeKey
) as x0
) as x1 on x1.CustomerKey = s.CustomerKey
The row_number() function gets only the first row from the result for each customer, and that is with the biggest sale revenue ( order by sale_revenue desc).

Filter between dates grouping 3 tables in SQL Server

I have this SQL in SQL Server:
SELECT
Itens.Mercadoria, Mercadoria.Nome, Cabecalho.Data,
SUM(ValorUnitario) AS Total,
SUM(Quantidade) AS Quantidade
FROM
Itens
INNER JOIN
Mercadoria ON Itens.Mercadoria = Mercadoria.Codigo
INNER JOIN
Cabecalho ON Cabecalho.Codigo = Itens.Cabecalho
WHERE
Cabecalho.Data >= '2016-01-01'
AND Cabecalho.Data <= '2018-12-31'
GROUP BY
Itens.Mercadoria, Mercadoria.Nome, Cabecalho.Data
ORDER BY
4 DESC
It is returning the following result.
The highlighted values are repeating, I do not want to be repeated, I want to show only once each item and that the Quantidade and Total fields are SUM.
For example:
`Camisa Polo` -> **Quantidade = 23**
`Calça Jeans` -> **Quantidade = 15**
`Camiseta Estampada` -> **Quantidade = 21**
Assuming thate the relation between Sales and SaleItems is based on SalesId
you can use between assign to your_start_date and your_end_date a proper value
select Products.ProductName
, sum(SaleItems.Price)
, sum(SaleItems.Quantity)
from Products
inner join SaleItems on SaleItems.IdProduct = Products.IdProduct
inner join Sales on Sales.IdSale = SaleItems.IdSale
where SaleDate between your_start_date and your_end_date
group by Products.ProductName
In you case remove or aggregated the Cabecalho.Data column eg:
SELECT Itens.Mercadoria
, Mercadoria.Nome
, SUM(ValorUnitario) AS Total
, SUM(Quantidade) AS Quantidade
FROM Itens INNER JOIN Mercadoria ON Itens.Mercadoria = Mercadoria.Codigo
INNER JOIN Cabecalho ON Cabecalho.Codigo = Itens.Cabecalho
WHERE Cabecalho.Data between '2016-01-01' AND '2018-12-31'
GROUP BY Itens.Mercadoria, Mercadoria.Nome
ORDER BY 4 DESC
or
SELECT Itens.Mercadoria
, Mercadoria.Nome
, max(Cabecalho.Data)
, SUM(ValorUnitario) AS Total
, SUM(Quantidade) AS Quantidade
FROM Itens INNER JOIN Mercadoria ON Itens.Mercadoria = Mercadoria.Codigo
INNER JOIN Cabecalho ON Cabecalho.Codigo = Itens.Cabecalho
WHERE Cabecalho.Data between '2016-01-01' AND '2018-12-31'
GROUP BY Itens.Mercadoria, Mercadoria.Nome
ORDER BY 4 DESC

Identical aggregate-function for different WHERE's

Im stuck atm. Trying to figure out how to use an aggregate function like SUM(column1*column2) for making results in multiple columns. I want to print ex. SUM(qty*unitprice) where orderdate between "blabla" and "blabla",
and then i want another column which uses the same function(sum(qty*unitprice)
but with additional expressions in the where clause. this is my code example, it doesnt show anything:
select Orderdates, Sales, SalesDisc
FROM ( select month(OH.OrderDate) as Orderdates, sum(OD.OrderQty*OD.UnitPrice) as Sales
from sales.SalesOrderDetail OD
inner join sales.salesorderheader OH on
OD.SalesOrderID = OH.SalesOrderID
where OrderDate >= ('2014-01-01') and OrderDate < ('2015-01-01')
group by month(OH.orderdate)
) A
Join
(select month(OH.OrderDate) as orderdatez, sum(OD.OrderQty*OD.UnitPrice) as SalesDisc
from sales.SalesOrderDetail OD
inner join sales.SalesOrderHeader OH on
OD.SalesOrderID = OH.SalesOrderID
where OrderDate >= ('2014-01-01') and OrderDate < ('2015-01-01')
and OD.SpecialOfferID between 2 and 16
Group by Month(OH.orderdate)
) B
on A.Orderdates = B.orderdatez and A.Sales = B.SalesDisc
If am not wrong this is what you need
SELECT Month(OH.OrderDate) AS Orderdates,
Sum(OD.OrderQty * OD.UnitPrice) AS Sales,
Sum(CASE
WHEN OD.SpecialOfferID BETWEEN 2 AND 16 THEN ( OD.OrderQty * OD.UnitPrice )
ELSE 0
END) AS SalesDisc
FROM sales.SalesOrderDetail OD
INNER JOIN sales.salesorderheader OH
ON OD.SalesOrderID = OH.SalesOrderID
WHERE OrderDate >= ( '2014-01-01' )
AND OrderDate < ( '2015-01-01' )
GROUP BY Month(OH.orderdate)
Note : It will be more meaningful if you add year(OH.orderdate) in group by