Calculate SUM between two dates SQL SERVER - sql

I'm having two tables
Calendar
FirstDate | LastDate
------------+-----------
2020-05-01 2020-05-03
2020-05-02 2020-05-04
ProfileRate
ID | Date | Rate | Product
---+------------+------+----------
1 2020-05-01 100 Prod1
2 2020-05-02 200 Prod1
3 2020-05-03 300 Prod1
4 2020-05-04 400 Prod1
5 2020-05-01 150 Prod2
6 2020-05-02 250 Prod2
7 2020-05-03 350 Prod2
8 2020-05-04 450 Prod2
I want to calculate SUM. Something like this
FirstDate | LastDate | Prod1 | Prod2
------------+-------------+--------+-------
2020-05-01 2020-05-03 600 750
2020-05-02 2020-05-04 900 1050
What I've tried:
SELECT [Date], SUM([Rate])
FROM [ProfileRate]
WHERE [Date] BETWEEN '2020-05-01' AND '2020-05-04'
GROUP BY [Date]
Please help me. Thanks

you have to do several steps to solve the problem:
combine calendar records with profiles
establish which profiles meet the calendar range
total the value for each profile and date range
finally establish the detailed information of the calendar
this query meets the conditions
with DataForCalendar as
(
SELECT IdCalendar,FirstDate,LastDate,Id,DateRate,Rate,Product
FROM
Calendar CROSS JOIN ProfileRate
where DateRate between '2020-05-01' and '2020-05-05'
),
DetailForCalendar as
(
select IdCalendar,Rate,Product
from DataForCalendar
where DateRate between FirstDate and LastDate
),
TotalForCalendar as
(
select IdCalendar,Product,sum(Rate)As Total from DetailForCalendar
GROUP BY IdCalendar,Product
)
select Calendar.IdCalendar,Calendar.FirstDate,Calendar.LastDate,
TotalForCalendar.Product,TotalForCalendar.Total
from TotalForCalendar inner join
Calendar on TotalForCalendar.IdCalendar=Calendar.IdCalendar;
in this example you can find step by step how it work
in this example there is one more product and one more calendar.

I think this is a JOIN and conditional aggregation:
SELECT c.FirstDate, c.LastDate,
SUM(CASE WHEN pr.prod = 'Prod1' THEN pr.Rate END) as prod1,
SUM(CASE WHEN pr.prod = 'Prod2' THEN pr.Rate END) as prod2
FROM Calender c LEFT JOIN
ProfileRate pr
ON pr.Date >= c.FirstDate AND pr.Date <= c.LastDate
GROUP BY c.FirstDate, c.LastDate;

Do the join & conditional aggregation :
select c.fisrtdate, c.lastdate,
sum(case when pr.product = 'prod1' then rate else 0 end) as prod1,
sum(case when pr.product = 'prod2' then rate else 0 end) as prod2
from calendar c join
ProfileRate pr
on pr.date >= c.fisrtdate and pr.date <= c.lastdate
group by c.fisrtdate, c.lastdate;
If you want all calendar dates then use left join instead.

Related

group by of one column and having count of another

I have a table 'customer' which contains 4 columns
name day product price
A 2021-04-01 p1 100
B 2021-04-01 p1 100
C 2021-04-01 p2 120
A 2021-04-01 p2 120
A 2021-04-02 p1 100
B 2021-04-02 p3 80
C 2021-04-03 p2 120
D 2021-04-03 p2 120
C 2021-04-04 p1 100
With a command
SELECT COUNT(name)
FROM (SELECT name
FROM customer
WHERE day > '2021-03-28'
AND day < '2021-04-09'
GROUP BY name
HAVING COUNT(name) > 2)
I could count number of customer that bought something more than twice in a period of time.
I would like to know in each day (GROUP BY over day) how many customers bought something with this condition that in a period they bought something more than twice.
Suggested Edit:
For above example A and C are valid agents by the condition.
The desired output will be:
day how_many
2021-04-01 2
2021-04-02 1
2021-04-03 1
2021-04-04 1
I interpret your question as wanting to know how many customers made more than one purchase on each day. If so, one method uses two levels of aggregation:
select day,
sum(case when day_count >= 2 then 1 else 0 end)
from (select c.name, c.day, count(*) as day_count
from customer c
group by c.name, c.day
) nc
group by day
order by day;

Subtract Daily amount from Withdraw Amount

I have two tables, tblWithdraw and tblProfit.
tblWithdraw Table
WithdrawId Date User Price
-----------------------------------------------------
1 2021-02-09 SANDANUWAN 2000.00
2 2021-02-09 GAYAN 300.00
3 2021-02-09 KASUN 1500.00
4 2021-02-09 AMAL 4000.00
5 2021-02-09 UDARA 340.00
6 2021-02-09 SULIN 200.00
7 2021-02-09 PERERA 120.00
tblProfit Table
Id Date Inv.No ItemName ItemCode Qty CostPrice DiscountPrice Amount
----------------------------------------------------------------------------------------------
1 2021-02-09 INV0000001 DELL LAP LP001 5 1500.00 1900.00 9500.00
2 2021-02-09 INV0000001 HP MOUSE MO001 7 2500.00 2940.00 20580.00
3 2021-02-09 INV0000001 PACIFIC FAN FAN001 6 2000.00 4000.00 24000.00
4 2021-02-09 INV0000001 SAMSUNG PHONE PH001 8 1000.00 1350.00 10800.00
This is my question. I want to sum all price from tblWithdraw table according to the date. Its mean withdraw date and profit table must be match. New I want to sum all amount from tblProfit table.Then I want to Subtract like this Price - Amount. Finally I want to show total amount of Profit table after subtracted. I used following join but is not working well. please help me to solve this problem. I want to subtract according to the date.
Select MAX(w.Date)Date, SUM(w.Price)Price, SUM(p.Amount)Amount
From tblWithdraw w
Left Join tblProfit p
ON w.Date = p.CurrentDate
Group by w.Date
Out put is like this. its wrong.
Date Price Amount
2021-02-09 33840.00 454160.00
Here's my CTE-based approach based on the comment which I made on the initial question.
WITH WithDraw AS(
SELECT
[date]
,SUM(price) PRICE
FROM tblWithdraw
GROUP BY [date]
),
Profit AS(
SELECT
[date]
,SUM(amount)AMOUNT
FROM tblProfit
GROUP BY [date]
)
SELECT
W.DATE
,W.PRICE
,P.AMOUNT
FROM WithDraw W
INNER JOIN Profit P ON w.date = p.date;
I'm not sure I fully understand the question, this is how I'm reading it:
Select w.Date,
w.agg_price_by_date,
w.agg_price_by_date - COALESCE(p.agg_amount_by_date, 0) AS diff_price_amount
From (SELECT Date,
SUM(Price) AS agg_price_by_date
FROM tblWithdraw w
GROUP
BY Date
) w
Left Join
(SELECT CurrentDate,
SUM(Amount) AS agg_amount_by_date
FROM tblProfit
GROUP
BY CurrentDate
) p
ON w.Date = p.CurrentDate

Sum only for Employee ID's present in latest snapshot

I have a database with a row per month for each employee working in our company. So, if employee A has been working for our company from July 2016 till now, this person has approx. 24 rows (one row for each month she was in service).
I'm trying to summarize the experience each of the current employees have in a particular function. So, if employee A has worked 6 months in Sales and 18 months in Marketing, then I count the number of rows this employee has Sales or Marketing in the column indicating the function.
I have created a code which does seems to count the functional experience per employee, but it double counts data. It does not take the latest snapshot as starting point.
SELECT A.EMPLOYEE_ID,
SUM(CASE WHEN A.FUNCTION_CODE ='CUS' THEN 1 ELSE 0 END) AS EXP_CUS,
SUM(CASE WHEN A.FUNCTION_CODE ='MKT' THEN 1 ELSE 0 END) AS EXP_MKT
FROM [dbname].[AGL_V_HRA_FE_R].[VW_HRA_EMPLOYEE_DETAIL] AS A INNER JOIN [dbname].[AGL_V_HRA_FE_R].[VW_HRA_EMPLOYEE_DETAIL] AS B ON A.EMPLOYEE_ID = B.EMPLOYEE_ID
WHERE B.WORKLEVEL_CODE > '1'
GROUP BY A.EMPLOYEE_ID
I expected the output for employee A to be EXP_CUS = 6 and EXP_MKT = 18. Instead, the output for both is much higher as it is double counting rows. When I add the line AND B.SNAPSHOT_DATE = '2019-06-30', the output is correct. I don't like to manually adjust the code every month and rather refer to the latest snapshot date.
ADDED
The original table looks like this
SNAPSHOT_DATE | EMPLOYEE_ID | FUNCTION_CODE
2019-06-30 | 000000001 | CUS
2019-06-30 | 000000002 | MKT
2019-05-31 | 000000001 | CUS
2019-05-31 | 000000002 | MKT
2019-04-30 | 000000001 | MKT
2019-04-30 | 000000002 | MKT
The desired output would be
EMPLOYEE_ID | EXP_CUS | EXP_MKT
000000001 | 2 | 1
000000002 | 0 | 3
You can use PIVOT to get your desired result as below-
SELECT EMPLOYEE_ID,
ISNULL([CUS],0) AS [EXP_CUS],
ISNULL([MKT],0) AS [EXP_MKT]
FROM
(
SELECT EMPLOYEE_ID,FUNCTION_CODE,COUNT(SNAPSHOT_DATE) T
FROM your_table
GROUP BY EMPLOYEE_ID,FUNCTION_CODE
)P
PIVOT(
SUM(T)
FOR FUNCTION_CODE IN ([CUS],[MKT])
)PVT
Output is-
EMPLOYEE_ID EXP_CUS EXP_MKT
000000001 2 1
000000002 0 3
I don't understand why you are using a self join. This seems to do what you want:
SELECT ED.EMPLOYEE_ID,
SUM(CASE WHEN ED.FUNCTION_CODE ='CUS' THEN 1 ELSE 0 END) AS EXP_CUS,
SUM(CASE WHEN ED.FUNCTION_CODE ='MKT' THEN 1 ELSE 0 END) AS EXP_MKT
FROM [dbname].[AGL_V_HRA_FE_R].[VW_HRA_EMPLOYEE_DETAIL] ed
WHERE ED.WORKLEVEL_CODE > '1'
GROUP BY ED.EMPLOYEE_ID;
If you only want employees with the most recent snapshot date, then you can use window functions:
SELECT ED.EMPLOYEE_ID,
SUM(CASE WHEN ED.FUNCTION_CODE ='CUS' THEN 1 ELSE 0 END) AS EXP_CUS,
SUM(CASE WHEN ED.FUNCTION_CODE ='MKT' THEN 1 ELSE 0 END) AS EXP_MKT
(SELECT ED.*,
MAX(SNAPSHOT_DATE) OVER () as OVERALL_MAX_SNAPSHOT_DATE,
MAX(SNAPSHOT_DATE) OVER (PARTITION BY EMPLOYEE_ID) as EMPLOYEE_MAX_SNAPSHOT_DATE
FROM [dbname].[AGL_V_HRA_FE_R].[VW_HRA_EMPLOYEE_DETAIL] ED
) ED
WHERE ED.WORKLEVEL_CODE > '1' AND
EMPLOYEE_MAX_SNAPSHOT_DATE = OVERALL_MAX_SNAPSHOT_DATE
GROUP BY ED.EMPLOYEE_ID;

Calculate payment date for each invoice

Please consider the following table transaction: a company regularly sends invoices to their customers that are part of the same order. The companies' clients will often pay only once per so many weeks.
(trans_date in format yyyy-mm-dd)
id order_id trans_type trans_date trans_amount
----------------------------------------------------------
1 1 invoice 2017-01-10 100
2 1 invoice 2017-05-23 150
3 1 invoice 2017-05-28 200
4 2 invoice 2017-03-01 700
5 2 payment 2017-06-16 700
6 1 payment 2017-10-12 450
7 3 invoice 2017-06-24 199
The company would like to see on what date each invoice was paid for. For example: invoice (id) 1 (part of order_id=1 group) was sent on 2017-01-10 and paid on 2017-10-12 (id=6). Invoice with id=7 has not been paid at all.
The desired output would be the payment date for each invoice (payment_date):
id order_id trans_type trans_date trans_amount payment_date
--------------------------------------------------------------------------
1 1 invoice 2017-01-10 100 2017-10-12
2 1 invoice 2017-05-23 150 2017-10-12
3 1 invoice 2017-05-28 200 2017-10-12
4 2 invoice 2017-03-01 700 2017-06-16
5 2 payment 2017-06-16 700
6 1 payment 2017-10-12 450
7 3 invoice 2017-06-24 199
For transactions 5, 6 and 7, the payment_date is empty because it is either a payment (id=5 and 6) or an unpaid invoice (id=7).
I don't understand how I should solve this issue. In combination with regular scripting, I would get the whole set and loop through it to find each payment. But how can this be solved in SQL only?
Any help would be greatly appreciated!
Did you try a simple left join?
Below code is standard SQL.
Select a.id , a.order_id, a.trans_type, a.trans_date, a.trans_amount, isnull(b.trans_date, '') As payment_date
From transaction a
Left join transaction b
On a.order_id = b.order_id
And a.trans_type = 'invoice'
And b.trans_type = 'payment'
You can do a cumulative sum of payments and invoices and get the first date when the payment total meets or exceeds the invoice total:
with ip as (
select ip.*,
sum(case when ip.trans_type = 'invoice' then ip.trans_amount else 0 end) over (order by ip.trans_date) as running_invoice,
sum(case when ip.trans_type = 'payment' then ip.trans_amount else 0 end) over (order by ip.trans_date) as running_payment,
from invoicepayments i
)
select ip.*,
(select min(ip2.trans_date)
from ip ip2
where ip2.running_payment >= ip.running_invoice and
ip.trans_type = 'invoice'
) as payment_date
from ip;

SQL to work out sales by product taking into account age

I want to work out sales by product grouped by release date, but also grouped by the age of that product when sold, something like this:
| 3 months | 6 months
2015-01 | 28.1 | 37.1
2015-02 | 29.3 | 35.6
So 28.1 is the average number of products sold of each type, 3 months after being released, for those products released in 2015-01. There are obviously more products sold 6 months after the release date, 37.1.
The following SQL gets a list of sales:
SELECT
d.item AS title,
d.quantity,
a.firstdate AS release_date,
i.date AS invoice_date,
i.date - a.firstdate AS age
FROM invoices i
JOIN invoice_details d ON i.id = d.invoice_id
JOIN (SELECT
d.item,
d.binding,
min(i.date) AS firstdate
FROM invoices i
JOIN invoice_details d ON i.id = d.invoice_id
GROUP BY d.item, d.binding) AS a ON a.item = d.item AND a.binding = d.binding
WHERE
i.discount != 100 AND d.price > 0
AND (d.binding != 'Hardback' OR d.binding != 'Ebooks')
ORDER BY title, invoice_date
And the result looks something like:
title | quantity | release date | invoice date | age
A | 1 | 2013-11-14 | 2013-11-14 | 0
A | 2 | 2013-11-14 | 2013-12-14 | 30
A | 3 | 2013-11-14 | 2014-01-14 | 60
A | 4 | 2013-11-14 | 2014-02-14 | 90
A | 5 | 2013-11-14 | 2014-03-14 | 120
B | 6 | 2013-11-14 | 2013-11-14 | 0
B | 7 | 2013-11-14 | 2013-12-14 | 30
B | 8 | 2013-11-14 | 2014-01-14 | 60
B | 9 | 2013-11-14 | 2014-02-14 | 90
B | 10 | 2013-11-14 | 2014-03-14 | 120
For product A, the total sales 3 months after the release date of 2013-11-14 are 1+2+3=6. For product B, total sales 3 months after are 6+7+8=21.
Average sales per title for the month of 2013-11, 3 months after are (6+21)/2=13.5
For 6 months after it's ((1+2+3+4+5) + (6+7+8+9+10)) / 2 = 27.5
The release date is just the first date the product was sold - this is what the joined sub-query is for. There is probably a better way of doing it.
I tried this to get the averages across 3, 6, 12 and 24 months:
SELECT
to_char(a.release_date, 'YYYY-MM') AS release_date,
avg(CASE WHEN i.date - a.release_date < 92
THEN d.quantity END) AS three_months,
avg(CASE WHEN i.date - a.release_date < 183
THEN d.quantity END) AS six_months,
avg(CASE WHEN i.date - a.release_date < 365
THEN d.quantity END) AS twelve_months,
avg(CASE WHEN i.date - a.release_date < 730
THEN d.quantity END) AS twentyfour_months
FROM invoices i
JOIN invoice_details d ON i.id = d.invoice_id
JOIN (SELECT
d.item,
d.binding,
min(i.date) AS release_date
FROM invoices i
JOIN invoice_details d ON i.id = d.invoice_id
GROUP BY d.item, d.binding) AS a ON a.item = d.item AND a.binding = d.binding
WHERE
i.discount != 100 AND d.price != 0
AND (d.binding != 'Hardback' OR d.binding != 'Ebooks')
GROUP BY release_date
ORDER BY release_date desc
Obviously it's totally wrong because it's not grouping the results by title. It's giving me the average items per order rather than the average items per title.
By the way I am stuck on Postgres 8.2.
If I understand you correctly, this is what you want:
SELECT
to_char(date, 'YYYY-MM') AS release_date,
avg(CASE WHEN age < 92 THEN quantity ELSE 0 END) AS three_months,
avg(CASE WHEN age < 183 THEN quantity ELSE 0 END) AS six_months,
avg(CASE WHEN age < 365 THEN quantity ELSE 0 END) AS twelve_months,
avg(CASE WHEN age < 730 THEN quantity ELSE 0 END) AS twentyfour_months
FROM (
SELECT d.item, d.quantity, (i.date - first_release.date) AS age, fr.date
FROM invoice_details d
JOIN (
SELECT d.item, min(i.date) AS date
FROM invoice_details d
JOIN invoices i ON i.id = d.invoice_id
WHERE d.binding != 'Hardback' AND d.binding != 'Ebooks'
GROUP BY d.item) AS fr USING (item)
JOIN invoice i ON i.id = d.invoice_id
WHERE i.discount != 100 AND d.price > 0) AS foo
GROUP BY release_date
ORDER BY release_date;
This is quite obviously untested because I can't even remember when I last touched an 8.2 installation. Your version does not have common table expressions or lateral joins, to name two critical features in later releases that would have made this rather more intuitive.
Anyway, the trick is to first calculate the age of every invoice relative to the book release date for every book sold, then average it out over the various time periods. Look carefully at the filters as I moved them and slightly altered them ((d.binding != 'Hardback' OR d.binding != 'Ebooks') is very likely not what you want).