I have sales line table that each row represent row in invoice sum price of the row etc. I also have sales headers table that each row represent invoice + discount (the rows represented in the sales line table).
I need to calculate for each item the quantity sales and the sum price of the item sales in each row after discount. I wonder if I calculate the price after discount correctly or if I missed something.
Here is the query
SELECT
SL.ItemCode,
SUM(SL.Qty) AS TotalQuantity,
SUM(SL.LineSum) - SUM(ISNULL(SH.DocDiscount, 0) * (SL.LineSum / ISNULL(TotalInvoiceSum.TotalSum, 0))) AS TotalSalesAfterDiscount ,
COUNT(DISTINCT SL.DocNum) AS InvoiceCount
FROM SalesLine SL
JOIN SalesHeader SH ON SL.DocNum = SH.DocNum
JOIN (
-- Calculate total sum per invoice
SELECT DocNum, SUM(LineSum) AS TotalSum
FROM SalesLine
GROUP BY DocNum
) AS TotalInvoiceSum ON SL.DocNum = TotalInvoiceSum.DocNum
GROUP BY SL.ItemCode;
sales line table sample
DocNum | DocLine | ItemCode | Qty | LineSum |
---|---|---|---|---|
61748 | 1 | 3620010 | 36240 | 86035.57 |
544567 | 1 | 3611000 | 25960 | 119520.88 |
544566 | 1 | 3611000 | 35500 | 163443.42 |
544565 | 1 | 3611600 | 1320 | 110880 |
544564 | 1 | 3611010 | 2600 | 15685.49 |
544563 | 1 | 3611010 | 2240 | 13513.65 |
544561 | 1 | 3611010 | 8720 | 41531.62 |
I have sales line table that each row represent row in invoice sum price of the row etc. I also have sales headers table that each row represent invoice + discount (the rows represented in the sales line table).
I need to calculate for each item the quantity sales and the sum price of the item sales in each row after discount. I wonder if I calculate the price after discount correctly or if I missed something.
Here is the query
SELECT
SL.ItemCode,
SUM(SL.Qty) AS TotalQuantity,
SUM(SL.LineSum) - SUM(ISNULL(SH.DocDiscount, 0) * (SL.LineSum / ISNULL(TotalInvoiceSum.TotalSum, 0))) AS TotalSalesAfterDiscount ,
COUNT(DISTINCT SL.DocNum) AS InvoiceCount
FROM SalesLine SL
JOIN SalesHeader SH ON SL.DocNum = SH.DocNum
JOIN (
-- Calculate total sum per invoice
SELECT DocNum, SUM(LineSum) AS TotalSum
FROM SalesLine
GROUP BY DocNum
) AS TotalInvoiceSum ON SL.DocNum = TotalInvoiceSum.DocNum
GROUP BY SL.ItemCode;
sales line table sample
DocNum | DocLine | ItemCode | Qty | LineSum |
---|---|---|---|---|
61748 | 1 | 3620010 | 36240 | 86035.57 |
544567 | 1 | 3611000 | 25960 | 119520.88 |
544566 | 1 | 3611000 | 35500 | 163443.42 |
544565 | 1 | 3611600 | 1320 | 110880 |
544564 | 1 | 3611010 | 2600 | 15685.49 |
544563 | 1 | 3611010 | 2240 | 13513.65 |
544561 | 1 | 3611010 | 8720 | 41531.62 |
sales headers table sample
DocNum | DocDate | DocDiscount | SalesPersonCode |
---|---|---|---|
61748 | 2023-10-17 | -33 | 172 |
544567 | 2023-10-15 | 37 | 120 |
544566 | 2023-10-15 | -17 | 120 |
544565 | 2023-10-15 | -34 | 100 |
544564 | 2023-10-15 | 2 | 100 |
61750 | 2023-10-15 | NULL | 172 |
2 Answers
Reset to default 1You can use a window function for this. Just window it per DocNum
and sum up the LineSum
, then you can apportion the DocDiscount
based on that.
SELECT
SL.ItemCode,
COUNT(DISTINCT SL.DocNum) AS InvoiceCount,
SUM(SL.Qty) AS TotalQuantity,
SUM(SL.FinalLineSum) AS TotalSalesAfterDiscount
FROM (
SELECT
SL.*,
SL.LineSum - ISNULL(
SH.DocDiscount, 0) * SL.LineSum
/ NULLIF(SUM(SL.LineSum) OVER (PARTITION BY SL.DocNum), 0) -- NULLIF in case of division by zero
) AS FinalLineSum
FROM SalesLine SL
JOIN SalesHeader SH ON SH.DocNum = SL.DocNum
) AS SL
GROUP BY
SL.ItemCode;
Correctness
With a small dataset and hand-verification, you can verify that your query is in fact right, you did not miss anything.
With a dataset of 2 invoices of 2 products each (codes 123
and 999
, at an initial price of 20 whatever-you-want each),
when each product gets ordered 4 times (1 123
+ 3 999
on the 25 %-discounted invoice; and 3 123
+ 1 999
on the (not discounted) second invoice):
DocNum | DocDiscount | ItemCode | Qty | LineSum | DiscountPercent | DiscountedPrice | DocTotalBySumThenDiscount | DocTotalByDiscountThenSum |
---|---|---|---|---|---|---|---|---|
1 | 20 | 123 | 1 | 20 | 25 | 15 | 60 | 60 |
1 | 20 | 999 | 3 | 60 | 25 | 45 | 60 | 60 |
2 | null | 123 | 3 | 60 | null | 60 | 80 | 80 |
2 | null | 999 | 1 | 20 | null | 20 | 80 | 80 |
You obtain:
ItemCode | TotalQuantity | TotalSalesAfterDiscount | InvoiceCount |
---|---|---|---|
123 | 4 | 75 | 2 |
999 | 4 | 65 | 2 |
which corresponds to the sums of individual lines (15 + 60 for 123
, 45 + 20 for 999
).
This fitting a fiddle.
Efficiency
Additionally, you can avoid joining twice to SalesLine
by using a window function that will gives each SalesLine a copy of the sum(LineSum)
of all its siblings (as if you computed TotalInvoiceSum
grouped by DocNum
but, instead of materializing it as a separate subtable, you directly associated each SalesLine
with a copy of the result corresponding to its DocNum
).
Thus the following query returns the same as yours:
WITH SL AS (SELECT *, SUM(LineSum) OVER (PARTITION BY DocNum) TotalSum FROM SalesLine SL)
SELECT
SL.ItemCode,
SUM(SL.Qty) AS TotalQuantity,
SUM(SL.LineSum - COALESCE(SH.DocDiscount * SL.LineSum / TotalSum, 0)) AS TotalSalesAfterDiscount,
COUNT(DISTINCT SL.DocNum) AS InvoiceCount
FROM SL
JOIN SalesHeader SH ON SL.DocNum = SH.DocNum
GROUP BY SL.ItemCode;
(note I used a Common Table Expression, introduced by the WITH
, instead of a subselect, to show SL
as an independant first step)
DocDiscount
represent a percentage or a total discount for the wholeSalesHeader
? How doesDocNum
coincide withSalesLine
andSalesHeader
rows? – Charlieface Commented Mar 17 at 16:10