Select top n percent by year - sql

I have a query that I built that brings back net sales YTD up to the most recently completed month. The query unions totals from invoices and credit memos. It works great. I use the query in another tool that sums by cardcode and allows me to do interesting pivots and so forth. Here is that query:
select x.cardcode,
x.customer,
case
when x.rep is null then (select slpname from ocrd inner join oslp on ocrd.slpcode = oslp.slpcode where ocrd.cardcode = x.cardcode)
else
x.rep
end as rep,
x.city,
x.state,
x.country,
case
when isnumeric(x.total) = 0 then 0
else x.total
end as [net total],
x.docdate
from (
select t0.cardcode as cardcode,
t0.[cardname] as customer,
t1.city as city,
t1.state as state,
t1.country as country,
t4.slpname as rep,
sum(t3.linetotal) - t2.discsum as total,
t2.docdate as [docdate]
from ocrd t0
inner join crd1 t1 on (t0.cardcode = t1.cardcode and t0.shiptodef = t1.address)
left outer join oinv t2 on t0.cardcode = t2.cardcode
left outer join inv1 t3 on t2.docentry = t3.docentry
left outer join oslp t4 on t2.slpcode = t4.slpcode
where t0.[cardtype] = 'C' and
t1.adrestype = 'S'
group by t0.cardcode, t0.cardname, t1.city, t1.state, t1.country, t4.slpname, t2.discsum, t2.docdate
union all
select t0.cardcode as cardcode,
t0.cardname as customer,
t1.city as city,
t1.state as state,
t1.country as country,
t4.slpname as rep,
-1*(sum(t3.linetotal) - t2.discsum) as total,
t2.docdate
from ocrd t0
inner join crd1 t1 on (t0.cardcode = t1.cardcode and t0.shiptodef = t1.address)
left outer join orin t2 on t0.cardcode = t2.cardcode
left outer join rin1 t3 on t2.docentry = t3.docentry
left outer join oslp t4 on t2.slpcode = t4.slpcode
where t0.[cardtype] = 'C' and
t1.adrestype = 'S'
group by t0.cardcode,
t0.cardname,
t1.city,
t1.state,
t1.country,
t4.slpname,
t2.discsum,
t2.docdate) x
where (x.docdate between '2008/01/01' and dateadd(day, -1, '2008/' + cast(month(getdate()) as varchar(2)) + '/01')
or x.docdate between '2009/01/01' and dateadd(day, -1, '2009/' + cast(month(getdate()) as varchar(2)) + '/01')
or x.docdate between '2010/01/01' and dateadd(day, -1, '2010/' + cast(month(getdate()) as varchar(2)) + '/01'))
group by x.cardcode, x.customer, x.rep, x.city, x.state, x.country, x.total, x.docdate
Now, I want to modify the query to return the top n, say 20, percent of customer's net total for each year. This is where I am having trouble. I am using SQL Server so first I thought I would try using row_number() over(partition.... but I haven't got it quite right (I know its not right because I can check it against the report I am reverse engineering). Here is my first attempt:
select m.Cardcode, m.Customer, m.Rep, m.City, m.State, m.Country, m.Nettotal as 'Net Total', m.docdate as 'Posting Date'
from (
select t.cardcode, t.customer, t.rep, t.city, t.state, t.country, t.nettotal, t.docdate, row_number() over(partition by t.docdate order by t.nettotal desc) as rownum
from (
select x.cardcode,
x.customer,
case
when x.rep is null then (select slpname from ocrd inner join oslp on ocrd.slpcode = oslp.slpcode where ocrd.cardcode = x.cardcode)
else
x.rep
end as rep,
x.city,
x.state,
x.country,
case
when isnumeric(x.total) = 0 then 0
else x.total
end as nettotal,
x.docdate
from (
select t0.cardcode as cardcode,
t0.[cardname] as customer,
t1.city as city,
t1.state as state,
t1.country as country,
t4.slpname as rep,
sum(t3.linetotal) - t2.discsum as total,
t2.docdate as docdate
from ocrd t0
inner join crd1 t1 on (t0.cardcode = t1.cardcode and t0.shiptodef = t1.address)
left outer join oinv t2 on t0.cardcode = t2.cardcode
left outer join inv1 t3 on t2.docentry = t3.docentry
left outer join oslp t4 on t2.slpcode = t4.slpcode
where t0.[cardtype] = 'C' and
t1.adrestype = 'S'
group by t0.cardcode,
t0.cardname,
t1.city,
t1.state,
t1.country,
t4.slpname,
t2.discsum,
t2.docdate
union all
select t0.cardcode as cardcode,
t0.cardname as customer,
t1.city as city,
t1.country as country,
t1.state as state,
t4.slpname as rep,
-1*(sum(t3.linetotal) - t2.discsum) as total,
t2.docdate
from ocrd t0
inner join crd1 t1 on (t0.cardcode = t1.cardcode and t0.shiptodef = t1.address)
left outer join orin t2 on t0.cardcode = t2.cardcode
left outer join rin1 t3 on t2.docentry = t3.docentry
left outer join oslp t4 on t2.slpcode = t4.slpcode
where t0.[cardtype] = 'C' and
t1.adrestype = 'S'
group by t0.cardcode,
t0.cardname,
t1.city,
t1.state,
t1.country,
t4.slpname,
t2.discsum,
t2.docdate) x
where (x.docdate between '2008/01/01' and dateadd(day, -1, '2008/' + cast(month(getdate()) as varchar(2)) + '/01')
or x.docdate between '2009/01/01' and dateadd(day, -1, '2009/' + cast(month(getdate()) as varchar(2)) + '/01')
or x.docdate between '2010/01/01' and dateadd(day, -1, '2010/' + cast(month(getdate()) as varchar(2)) + '/01'))
group by x.cardcode,
x.customer,
x.rep,
x.city,
x.state,
x.country,
x.total,
x.docdate) as t
) as m
where rownum <= 20
Going down this road is troublesome even if I got it right because it doesn't allow me to get the top n percent, just the top n.
I have yet to try using cross apply or sub selects to achieve the result I desire.
Can someone help me get this right? Also, how it is written is probably not efficient and the hard coded date range selection is not a good solution. I guess there is a lot to improve :)
Your help is appreciated.

If you need a more individual percentage (let's say 17%) you can use row_number and count:
with cSalesPerYear as (
select s.Year, c.Customer,
RankNo = rank() over (partition by s.Year order by S.Amount desc),
RowNo = row_number() over (partition by s.Year order by S.Amount desc),
CountOrders = count() over (partition by s.Year)
from dbo.Customers c
inner join dbo.Sales s
on s.CustomerID = c.CustomerID
)
select *
from cSalesPerYear
where RowNo <= #Percentage * CountOrders
-- RankNo <= #Percentage * CountOrders --<-- "with ties" version

You can use one of the ranking functions in SQL Server - but not ROW_NUMBER() but NTILE() instead.
NTILE() will break up a result set into as many chunks of data that you specify - since you want to the top 20%, you'll probably use NTILE(5).
So your CTE should look something like this:
WITH CustomerPerYear AS
(
SELECT
c.Name, s.Sales,
NTILE(5) OVER (PARTITION BY c.CustomerID ORDER BY s.Amount DESC) as 'NTile'
FROM
dbo.Customer c
INNER JOIN
dbo.Sales s ON s.CustomerID = c.CustomerID
)
SELECT *
FROM CustomerPerYear
WHERE NTile = 1
So basically you're partioning your data by customer, and then you're ranking each customers' sales into 5 NTile groups ordered by the sales amount. The NTILE = 1 is the top 20% of your sales, for each customer.
See the MSDN docs on NTILE for more details, if needed.

Related

How to find 10 items that sold the most in the last year?

I need a list of the top 10 best selling items in the last year.
I am required to use dateadd.
In my opinion, fromdate and dateto should also be used, but I didn't succeed.
I would appreciate your help
SELECT
RDR1.ItemCode [Itemcode],
RDR1.Quantity [Quantity],
YEAR(RDR1.DocDate) [DocDate]
FROM
ORDR
INNER JOIN
RDR1 ON ORDR.DocEntry = RDR1.DocEntry
WHERE
RDR1.DocDate BETWEEN (#date) AND (YEAR(GETDATE())- 1)
-- RDR1.DocDate BETWEEN (#DateTo) AND (#date)
GROUP BY
(RDR1.ItemCode)
ORDER BY
SUM(RDR1.Quantity) DESC
SELECT TOP 20 T1.ItemCode, T2.ItemName, cast(sum(T1.Quantity) as numeric) as 'Quantity Sold', sum(T1.LineTotal - T1.VatSum) as 'Total Net Value', sum(T1.LineTotal) as 'Total Gross Value', T1.Currency
FROM dbo.INV1 T1 inner join OINV T3 on T1.docentry = t3.docentry
LEFT OUTER JOIN OITM T2 on T1.ItemCode = T2.ItemCode INNER JOIN OCRD T4 ON T2.CardCode = T4.CardCode INNER JOIN OCRG T5 ON T4.GroupCode = T5.GroupCode
WHERE T1.ItemCode is not null and (t3.docdate between [%0] and [%1]) and t3.series = 229 and T5.GroupName = '[%2]'
GROUP BY T1.ItemCode, T2.ItemName, T1.Currency
order by sum(T1.Quantity) desc
Hope this helps :)

Analytics function is not allowed in Group By

I have a following query:
SELECT t.date,
t.transactionId,
t.channelGrouping,
tp.itemRevenue,
t.transactionItemQuantity,
t.company,
t.campaign,
ifnull(replace((regexp_extract(tp.productCategory, r"(^[a-zA-Z0-9_.&;+ -]+)")),"&", "&"), "N/A") as productCategory,
ifnull(sum(a.adCost),0) as adCost
FROM `kpi.TransactionsByChannel` t left join kpi.TransactionProducts tp
on tp.transactionId = t.transactionId
left join kpi.Ads a
on a.company = t.company and a.date = t.date and a.campaign = t.campaign group by 1,2,3,4,5,6,7,8
which returns me result:
However, as you can see, adCost is not correct and duplicated. I need to get ratio of it based on itemRevenue or just write first row and other rows should be 0.
I tried following query:
SELECT t.date,
t.transactionId,
t.channelGrouping,
tp.itemRevenue,
t.transactionItemQuantity,
t.company,
t.campaign,
ifnull(replace((regexp_extract(tp.productCategory, r"(^[a-zA-Z0-9_.&;+ -]+)")),"&", "&"), "N/A") as productCategory,
round(adCost*(itemRevenue/SUM(itemRevenue) over (partition by tp.company, tp.productCategory, tp.date)),2) as adCost_adjusted,
FROM kpi.TransactionsByChannel t left join kpi.TransactionProducts tp
on tp.transactionId = t.transactionId
left join kpi.Ads a
on a.company = t.company and a.date = t.date and a.campaign = t.campaign group by 1,2,3,4,5,6,7,8,9
but I am getting an error of:
Column 9 contains an analytic function, which is not allowed in GROUP BY at [13:99]
Any helps would be really appreciated!
Try something like this approach:
with ttr as
(
select
t.transactionId,
sum(tp.itemRevenue) as TotalItemRevenue
from
`kpi.TransactionsByChannel` t
inner join
kpi.TransactionProducts tp
on
tp.transactionId = t.transactionId
group by
t.transactionId
)
SELECT
t.date,
t.transactionId,
t.channelGrouping,
tp.itemRevenue,
t.transactionItemQuantity,
t.company,
t.campaign,
ifnull(replace((regexp_extract(tp.productCategory, r"(^[a-zA-Z0-9_.&;+ -]+)")),"&", "&"), "N/A") as productCategory,
ifnull(a.adCost,0) * tp.itemRevenue / ttr.TotalItemRevenue as adCost_adjusted
FROM
`kpi.TransactionsByChannel` t
inner join
ttr
on
t.transactionId = ttr.transactionId
inner join
kpi.TransactionProducts tp
on
tp.transactionId = t.transactionId
left join
kpi.Ads a
on
a.company = t.company
and a.date = t.date
and a.campaign = t.campaign

Problem with merging two SELECTS (One of them is PIVOT)

I'm newbie in SQL but I'm trying to do some stuff. I wanted to make some queries in sap sql. I done two.
1st:
SELECT
OINV.DocEntry,
OINV.DocNum AS N'FV',
OITM.ItemCode,
OITM.ItemName,
CAST(T1.Ilosc AS int) AS N'Sum',
T1.unitMsr AS N'UoM',
T1.[Price Min],
T1.Currency AS N'PLN',
T1.DocDate AS N'Last'
FROM
OITM
INNER JOIN
(
SELECT
OINV.CardCode,
MAX(OINV.DocDate) AS DocDate,
MAX(OINV.DocEntry) AS DocEntry,
INV1.ItemCode,
SUM(INV1.Quantity) AS Ilosc,
INV1.unitMsr,
MIN(INV1.Price) AS 'Price Min',
INV1.Currency
FROM OINV inner join INV1 ON OINV.DocEntry = INV1.DocEntry
WHERE INV1.ItemCode is not null
GROUP BY OINV.CardCode, INV1.ItemCode, INV1.unitMsr, INV1.Currency
) AS T1 ON OITM.ItemCode = T1.ItemCode
INNER JOIN OINV ON OINV.DocEntry = T1.DocEntry
WHERE T1.CardCode = N'OT-05453' AND T1.ItemCode NOT IN (SELECT DISTINCT U_ItemCode FROM [#BP2] WHERE U_CardCode = N'OT-05453' )
ORDER BY IIF(OITM.QryGroup1 = 'Y', 'Tak', '') desc, T1.DocDate, ItemCode
And 2nd one:
SELECT
P.ItemCode,
[2017] [2017],
[2018] [2018],
[2019] [2019]
FROM
(SELECT
INV1.ItemCode,
OINV.CardCode as C,
INV1.Quantity Volume,
year(INV1.[DocDate]) as year
FROM INV1 INNER JOIN OINV ON INV1.docentry = OINV.docentry
WHERE INV1.[docDate] between year(2007) and GETDATE() AND OINV.cardcode = N'OT-05453'
) S
Pivot
(sum([volume]) For year IN ([2017],[2018],[2019])) P
Both works correctly.
But I want to merge them. Both selects has same itemcodes. Just wanted to add 2nd query results on the right.
I was trying to use UNION but it wasn't working.
Hope that you will show me right way :)
The results of both queries produces some tables. You can put in brackets each query and join them using Itemcode column from both queries. Something like below:
SELECT t1.*, t2.[2017], t2.[2018], t2.[2019]
FROM
(
SELECT
OINV.DocEntry,
OINV.DocNum AS N'FV',
OITM.ItemCode,
OITM.ItemName,
CAST(T1.Ilosc AS int) AS N'Sum',
T1.unitMsr AS N'UoM',
T1.[Price Min],
T1.Currency AS N'PLN',
T1.DocDate AS N'Last'
FROM
OITM
INNER JOIN
(
SELECT
OINV.CardCode,
MAX(OINV.DocDate) AS DocDate,
MAX(OINV.DocEntry) AS DocEntry,
INV1.ItemCode,
SUM(INV1.Quantity) AS Ilosc,
INV1.unitMsr,
MIN(INV1.Price) AS 'Price Min',
INV1.Currency
FROM OINV inner join INV1 ON OINV.DocEntry = INV1.DocEntry
WHERE INV1.ItemCode is not null
GROUP BY OINV.CardCode, INV1.ItemCode, INV1.unitMsr, INV1.Currency
) AS T1 ON OITM.ItemCode = T1.ItemCode
INNER JOIN OINV ON OINV.DocEntry = T1.DocEntry
WHERE T1.CardCode = N'OT-05453' AND T1.ItemCode NOT IN (SELECT DISTINCT U_ItemCode FROM [#BP2] WHERE U_CardCode = N'OT-05453' )) t1
INNER JOIN
(SELECT
P.ItemCode,
[2017] [2017],
[2018] [2018],
[2019] [2019]
FROM
(SELECT
INV1.ItemCode,
OINV.CardCode as C,
INV1.Quantity Volume,
year(INV1.[DocDate]) as year
FROM INV1 INNER JOIN OINV ON INV1.docentry = OINV.docentry
WHERE INV1.[docDate] between year(2007) and GETDATE() AND OINV.cardcode = N'OT-05453'
) S
Pivot
(sum([volume]) For year IN ([2017],[2018],[2019])) P) t2
on t1.ItemCode = t2.ItemCode
EDIT: changed wrong alias in select clause

Invoke a column twice with different conditions

I really appreciate any help with this matter :)
Am Working on a Report now and I had faced some troubles
I have this Query and it work fine , now I want to add a coulmn that is already exist in the query(from the same table) , but this time i'll change the condition of it , BTW the conditions in both of the 2 column are based on one other column
like for example If I have this :
Select Price from ITM1 WHERE PriceList = '1'
and also this
Select Price from ITM1 WHERE PriceList = '10'
how I can write in the same query and let them display in two different column ?
I will put the Query here in case if some one can help me through it :
you can see THE Column Price & PriceList in the lower part of it ,Bolded.
I just need to make the samething again but with a new coulmn name thats it.
Using the IN Operator will give you what you want. However, there are other changes that you can make to your query which would boost performance - but it's out of scope to the question. I'm unclear as to what you're trying to do with the different "columns" Please help explain. Else see #Dave.Gugg's answer which does just that.
SELECT T0.ItemCode,
T0.ItemName,
T0.CardCode,
T0.CodeBars,
T2.UgpCode,
T3.AltQty,
T3.BaseQty,
CASE
WHEN T4.Uomentry = - 1
THEN T0.[BuyUnitMsr]
ELSE t4.UomName
END AS 'UoMName',
T4.UomEntry,
T0.U_CAT_CODE,
T0.U_CAT_NAME,
T1.CardName,
(
SELECT TOP (1) dbo.PDN1.U_AC_QTY_ORDER
FROM dbo.PDN1
INNER JOIN dbo.OPDN ON dbo.PDN1.DocEntry = dbo.OPDN.DocEntry
WHERE (dbo.PDN1.ItemCode = T0.ItemCode)
AND (dbo.OPDN.CardCode = T0.CardCode)
ORDER BY dbo.OPDN.DocDate DESC
) AS OQuantity,
(
SELECT TOP (1) PDN1_1.U_AC_QTY_BONUS
FROM dbo.PDN1 AS PDN1_1
INNER JOIN dbo.OPDN AS OPDN_1 ON PDN1_1.DocEntry = OPDN_1.DocEntry
WHERE (PDN1_1.ItemCode = T0.ItemCode)
AND (OPDN_1.CardCode = T0.CardCode)
ORDER BY OPDN_1.DocDate DESC
) AS BQuantity,
ITM1.Price,
T0.U_DISC_PER
FROM dbo.OITM AS T0
INNER JOIN dbo.OCRD AS T1 ON T0.CardCode = T1.CardCode
INNER JOIN dbo.OUGP AS T2 ON T0.UgpEntry = T2.UgpEntry
INNER JOIN dbo.UGP1 AS T3 ON T2.UgpEntry = T3.UgpEntry
INNER JOIN dbo.ITM1 ON T0.ItemCode = dbo.ITM1.ItemCode
AND dbo.ITM1.PriceList IN ('1', '10')
LEFT JOIN dbo.OUOM AS T4 ON T3.UomEntry = T4.UomEntry
WHERE (T0.Series = '65')
AND (
T4.UomEntry = 3
OR T4.UomEntry = '-1'
)
If you want a different column (this may perform better than two joins):
SELECT T0.ItemCode,
T0.ItemName,
T0.CardCode,
T0.CodeBars,
T2.UgpCode,
T3.AltQty,
T3.BaseQty,
CASE
WHEN T4.Uomentry = - 1
THEN T0.[BuyUnitMsr]
ELSE t4.UomName
END AS 'UoMName',
T4.UomEntry,
T0.U_CAT_CODE,
T0.U_CAT_NAME,
T1.CardName,
(
SELECT TOP (1) dbo.PDN1.U_AC_QTY_ORDER
FROM dbo.PDN1
INNER JOIN dbo.OPDN ON dbo.PDN1.DocEntry = dbo.OPDN.DocEntry
WHERE (dbo.PDN1.ItemCode = T0.ItemCode)
AND (dbo.OPDN.CardCode = T0.CardCode)
ORDER BY dbo.OPDN.DocDate DESC
) AS OQuantity,
(
SELECT TOP (1) PDN1_1.U_AC_QTY_BONUS
FROM dbo.PDN1 AS PDN1_1
INNER JOIN dbo.OPDN AS OPDN_1 ON PDN1_1.DocEntry = OPDN_1.DocEntry
WHERE (PDN1_1.ItemCode = T0.ItemCode)
AND (OPDN_1.CardCode = T0.CardCode)
ORDER BY OPDN_1.DocDate DESC
) AS BQuantity,
CASE
WHEN ITM1.PriceList = '1'
THEN ITM1.Price
ELSE '0'
END AS Price1,
CASE
WHEN ITM1.PriceList = '10'
THEN ITM1.Price
ELSE '0'
END AS Price2,
T0.U_DISC_PER
FROM dbo.OITM AS T0
INNER JOIN dbo.OCRD AS T1 ON T0.CardCode = T1.CardCode
INNER JOIN dbo.OUGP AS T2 ON T0.UgpEntry = T2.UgpEntry
INNER JOIN dbo.UGP1 AS T3 ON T2.UgpEntry = T3.UgpEntry
INNER JOIN dbo.ITM1 ON T0.ItemCode = dbo.ITM1.ItemCode
AND dbo.ITM1.PriceList IN ('1', '10')
LEFT JOIN dbo.OUOM AS T4 ON T3.UomEntry = T4.UomEntry
WHERE (T0.Series = '65')
AND (
T4.UomEntry = 3
OR T4.UomEntry = '-1'
)
You should be able to just join to the table a second time, but you will need to make the joins outer:
SELECT T0.ItemCode ,
T0.ItemName ,
T0.CardCode ,
T0.CodeBars ,
T2.UgpCode ,
T3.AltQty ,
T3.BaseQty ,
CASE WHEN T4.Uomentry = -1 THEN T0.[BuyUnitMsr]
ELSE t4.UomName
END AS 'UoMName' ,
T4.UomEntry ,
T0.U_CAT_CODE ,
T0.U_CAT_NAME ,
T1.CardName ,
( SELECT TOP ( 1 )
dbo.PDN1.U_AC_QTY_ORDER
FROM dbo.PDN1
INNER JOIN dbo.OPDN ON dbo.PDN1.DocEntry = dbo.OPDN.DocEntry
WHERE ( dbo.PDN1.ItemCode = T0.ItemCode )
AND ( dbo.OPDN.CardCode = T0.CardCode )
ORDER BY dbo.OPDN.DocDate DESC
) AS OQuantity ,
( SELECT TOP ( 1 )
PDN1_1.U_AC_QTY_BONUS
FROM dbo.PDN1 AS PDN1_1
INNER JOIN dbo.OPDN AS OPDN_1 ON PDN1_1.DocEntry = OPDN_1.DocEntry
WHERE ( PDN1_1.ItemCode = T0.ItemCode )
AND ( OPDN_1.CardCode = T0.CardCode )
ORDER BY OPDN_1.DocDate DESC
) AS BQuantity ,
dbo.ITM1.Price ,
ITM1Second.Price,
T0.U_DISC_PER
FROM dbo.OITM AS T0
INNER JOIN dbo.OCRD AS T1 ON T0.CardCode = T1.CardCode
INNER JOIN dbo.OUGP AS T2 ON T0.UgpEntry = T2.UgpEntry
INNER JOIN dbo.UGP1 AS T3 ON T2.UgpEntry = T3.UgpEntry
LEFT OUTER JOIN dbo.ITM1 ON T0.ItemCode = dbo.ITM1.ItemCode
AND dbo.ITM1.PriceList = '10'
LEFT OUTER JOIN dbo.ITM1 ITM1Second ON T0.ItemCode = ITM1Second.ItemCode
AND ITM1Second.PriceList = '1'
LEFT OUTER JOIN dbo.OUOM AS T4 ON T3.UomEntry = T4.UomEntry
WHERE ( T0.Series = '65' )
AND ( T4.UomEntry = 3
OR T4.UomEntry = '-1'
)

How to modify an SQL query with sub query to get an extra column

I am trying to modify a user query in order to return an extra column, INV1.WhsCode from the following SQL query:
SELECT T0.CardCode,
T2.CardName,
T0.CodeBars,
T0.ItemCode,
T0.ItemName,
T3.Price AS [POS Price],
T1.AvgPrice,
T1.OnHand,
T1.MinStock,
T1.MaxStock,
T0.NumInBuy AS Packsize,
T0.LstSalDate,
(
SELECT SUM(Quantity) AS Expr1
FROM dbo.INV1
INNER JOIN OINV
ON INV1.DocEntry = OINV.DocEntry
WHERE INV1.ItemCode = T0.ItemCode
AND INV1.WhsCode = [%2]
AND Month(OINV.DocDate) = month(GetDate())
) AS [Current Period],
(
SELECT SUM(Quantity) AS Expr1
FROM dbo.INV1
INNER JOIN OINV
ON INV1.DocEntry = OINV.DocEntry
WHERE INV1.ItemCode = T0.ItemCode
AND INV1.WhsCode = [%2]
AND Month(OINV.DocDate) = month(GetDate()) - 1
) AS [Previous Period],
(
SELECT SUM(Quantity) AS Expr1
FROM dbo.INV1
INNER JOIN OINV
ON INV1.DocEntry = OINV.DocEntry
WHERE INV1.ItemCode = T0.ItemCode
AND INV1.WhsCode = [%2]
AND Month(OINV.DocDate) = month(GetDate()) - 2
) AS [60-90],
(
SELECT TOP 1 OPDN.DocDate AS Expr1
FROM dbo.PDN1
INNER JOIN OPDN
ON PDN1.DocEntry = OPDN.DocEntry
WHERE PDN1.ItemCode = T0.ItemCode
ORDER BY OPDN.DocDate DESC
) AS LastGRNDate
FROM OITM T0
INNER JOIN OITW T1
ON T0.ItemCode = T1.ItemCode
INNER JOIN OCRD T2
ON T0.CardCode = T2.CardCode
INNER JOIN ITM1 T3
ON T0.ItemCode = T3.ItemCode
INNER JOIN OWHS T4
ON T1.WhsCode = T4.WhsCode
INNER JOIN OITB T5
ON T0.ItmsGrpCod = T5.ItmsGrpCod
WHERE T3.PriceList = '3'
AND T4.WhsName = [%0]
AND T5.ItmsGrpNam = [%1]
How do I achieve this? (MS SQL Server 2008)
There are a number of things wrong/inefficient about your query which I know you didn't ask about, but I am going to answer anyway because it will help answer the question you did ask.
You need to avoid correlated subqueries where possible, there are times that they are unavoidable and the best solution, however I so often see them in a place where a JOIN would do the same job and the optimiser will deal with join so much better. For instance you have:
SELECT (
SELECT TOP 1 OPDN.DocDate AS Expr1
FROM dbo.PDN1
INNER JOIN OPDN
ON PDN1.DocEntry = OPDN.DocEntry
WHERE PDN1.ItemCode = T0.ItemCode
ORDER BY OPDN.DocDate DESC
) AS LastGRNDate
FROM OITM T0
This evaluates the subquery for each row, whereas if you re-wrote as so:
SELECT LastGRN.LastGRNDate
FROM OITM TO
LEFT JOIN
( SELECT PDN1.ItemCode, [LastGRNDate] = MAX(OPDN.DocDate)
FROM dbo.PDN1
INNER JOIN OPDN
ON PDN1.DocEntry = OPDN.DocEntry
GROUP BY PDN1.ItemCode
) LastGRN
ON LastGRN.ItemCode = T0.ItemCode
you would get the same result, but evaluated in a much more efficent manner.
The next fault is your method of using MONTH(GETDATE()) - 1 to get 2 months ago. In January this will evauluate to 0 and get no matches. The best way to do this is to convert each date to the first of each month using something akin to his:
SELECT [FirstOfThisMonth] = DATEADD(MONTH, DATEDIFF(MONTH, 0, GETDATE()), 0),
[FirstOfLastMonth] = DATEADD(MONTH, DATEDIFF(MONTH, 0, GETDATE() - 1), 0)
The same principle of joins rather than correlated subqueries can also be applied to your quantity columns, and this gives access to the WhsCode columns, it is not necessary, but I have used a common table expression to clean up the query (using the date logic from above)
WITH Quantities AS
( SELECT [DocMonth] = DATEADD(MONTH, DATEDIFF(MONTH, 0, IONV.DocDate),
Inv1.WhsCode,
ItemCode,
[Quantity] = SUM(Quantity)
FROM dbo.Inv1
INNER JOIN OINV
ON Inv1.DocEntry = OINV.DocEntry
GROUP BY DATEADD(MONTH, DATEDIFF(MONTH, 0, IONV.DocDate), WhsCode, itemCode
)
SELECT T0.ItemCode,
[Current Period] = COALESCE(Cur.Quantity, 0),
[Previous Period] = COALESCE(prev.Quantity, 0),
[60-90] = COALESCE(prev2.Quantity, 0)
FROM OITM T0
LEFT JOIN Quantities cur
ON cur.ItemCode = T0.ItemCode
AND cur.DocDate = DATEADD(MONTH, DATEDIFF(MONTH, 0, CURRENT_TIMESTAMP), 0)
AND Cur.WhsCode = [%2]
LEFT JOIN Quantities prev
ON prev.ItemCode = T0.ItemCode
AND prev.DocDate = DATEADD(MONTH, DATEDIFF(MONTH, 0, CURRENT_TIMESTAMP) - 1, 0)
AND prev.WhsCode = [%2]
LEFT JOIN Quantities prev2
ON prev2.ItemCode = T0.ItemCode
AND prev2.DocDate = DATEADD(MONTH, DATEDIFF(MONTH, 0, CURRENT_TIMESTAMP) - 2, 0)
AND prev2.WhsCode = [%2]
Combining all this into your final query gives:
SELECT T0.CardCode,
T2.CardName,
T0.CodeBars,
T0.ItemCode,
T0.ItemName,
T3.Price,
T1.AvgPrice,
T1.OnHand,
T1.MinStock,
T1.MaxStock,
T0.NumInBuy AS Packsize,
T0.LstSalDate
[Current Period] = COALESCE(Cur.Quantity, 0),
[Previous Period] = COALESCE(prev.Quantity, 0),
[60-90] = COALESCE(prev2.Quantity, 0)
LastGRN.LastGRNDate
FROM OITM T0
INNER JOIN OITW T1
ON T0.ItemCode = T1.ItemCode
INNER JOIN OCRD T2
ON T0.CardCode = T2.CardCode
INNER JOIN ITM1 T3
ON T0.ItemCode = T3.ItemCode
INNER JOIN OWHS T4
ON T1.WhsCode = T4.WhsCode
INNER JOIN OITB T5
ON T0.ItmsGrpCod = T5.ItmsGrpCod
LEFT JOIN Quantities cur
ON cur.ItemCode = T0.ItemCode
AND cur.DocDate = DATEADD(MONTH, DATEDIFF(MONTH, 0, CURRENT_TIMESTAMP), 0)
AND Cur.WhsCode = [%2]
LEFT JOIN Quantities prev
ON prev.ItemCode = T0.ItemCode
AND prev.DocDate = DATEADD(MONTH, DATEDIFF(MONTH, 0, CURRENT_TIMESTAMP) - 1, 0)
AND prev.WhsCode = [%2]
LEFT JOIN Quantities prev2
ON prev2.ItemCode = T0.ItemCode
AND prev2.DocDate = DATEADD(MONTH, DATEDIFF(MONTH, 0, CURRENT_TIMESTAMP) - 2, 0)
AND prev2.WhsCode = [%2]
LEFT JOIN
( SELECT PDN1.ItemCode, [LastGRNDate] = MAX(OPDN.DocDate)
FROM dbo.PDN1
INNER JOIN OPDN
ON PDN1.DocEntry = OPDN.DocEntry
GROUP BY PDN1.ItemCode
) LastGRN
ON LastGRN.ItemCode = T0.ItemCode
WHERE T3.PriceList = '3'
AND T4.WhsName = [%0]
AND T5.ItmsGrpNam = [%1]
This is all untested so there may be some typos/slight syntax errors, but the same principals should still apply.
If you are still set on the idea of correlated subqueries, you could use APPLY to allow you to access multiple columns from it. e.g.
SELECT T0.Code, cur.WhsCode, cur.Expr1 AS [Current Period]
FROM OITM T0
OUTER APPLY
( SELECT INV1.WhsCode, SUM(Quantity) AS Expr1
FROM dbo.INV1
INNER JOIN OINV
ON INV1.DocEntry = OINV.DocEntry
WHERE INV1.ItemCode = T0.ItemCode
AND INV1.WhsCode = [%2]
AND MONTH(OINV.DocDate) = MONTH(GETDATE())
) cur
All your subqueries contain in the where clause the following: INV1.WhsCode = [%2]
That means instead of INV1.WhsCode you can just return [%2] like this:
SELECT [%2] as WhsCode,
T0.CardCode,
T2.CardName,
T0.CodeBars,
......
This assumes that [%2] and the other similar tokens get replaced by the app with something SQL Server understands, before this gets send to get executed.