Performing aggregate function on a date range - sql

How to perform an aggregate function (AVG) on a date range
Source data looks like:
AccNo Amt Date
1 100 2/1/2021
1 200 2/5/2021
1 300 3/3/2021
2 400 5/20/2021
2 500 5/18/2021
The target needs to be derived in the following method:
Ex: To derive avg_past_week calculate the average amount for all the rows that are within the date range Date to Date - 7
Similarily, for avg_past_month, it'll be Date to Date - 30
Target:
AccNo Amt Date Avg_past_week Avg_past_month Avg_past_3 month
1 100 2/1/2021 100 100 100
1 200 2/5/2021 150 150 150
1 300 3/3/2021 300 250 200
2 400 5/20/2021 450 450 450
2 500 5/18/2021 500 500 500

select a.AccNo, a.Amt, a.Date,
avg(b.Amt) as Amt7,
avg(c.Amt) as Amt30,
avg(d.Amt) as Amt90
from T a
join T b on a.AccNo = b.AccNo and b.Date > DateAdd(day, -7, a.Date) and b.date <= a.Date
join T c on a.AccNo = c.AccNo and c.Date > DateAdd(day, -30, a.Date) and c.date <= a.Date
join T d on a.AccNo = d.AccNo and d.Date > DateAdd(day, -90, a.Date) and d.date <= a.Date
group by a.AccNo, a.Amt, a.Date
result
1 100 2021-02-01 100 100 100
1 200 2021-02-05 150 150 150
1 300 2021-03-03 300 250 200
2 400 2021-05-20 450 450 450
2 500 2021-05-18 500 500 500

You can use window functions:
select t.*,
avg(amt) over (partition by accNo
order by datediff('2000-01-01', date)
range between 6 preceding and current row
) as avg_7day,
avg(amt) over (partition by accNo
order by datediff('2000-01-01', date)
range between 30 preceding and current row
) as avg_30day,
avg(amt) over (partition by accNo
order by datediff('2000-01-01', date)
range between 90 preceding and current row
) as avg_91day
from t;
This converts the date to a number (number of days since some arbitrary date) and then uses a window frame for the time period.
Note that the number of days going back is one less than the period, because the current day is included as well.

Related

rolling three day average in sql server

I want to find three day average of values. Expected output:
dt
amt
running_avg
2022-05-1
100
0
2022-05-2
150
0
2022-05-3
50
100
2022-05-14
250
150
2022-05-15
0
100
Average should be calculated for 3 day window. My query is:
select a.dt, avg(b.amt) over(order by a.dt ) as running_avg,b.amt
from trans a
left join trans b on b.dt = a.dt
where a.dt between DATEADD(day,-3, a.dt) and getdate()
My query is just giving normal running average and not average for 3 days. Let me know how this can be done in SQL Server.
Thanks!
It seems you need a ROWS/RANGE argument in the windowed AVG():
Data:
SELECT *
INTO trans
FROM (VALUES
(CONVERT(date, '20220501'), 100),
(CONVERT(date, '20220502'), 150),
(CONVERT(date, '20220503'), 50),
(CONVERT(date, '20220514'), 250),
(CONVERT(date, '20220515'), 0)
) v (dt, amt)
Statement:
SELECT
dt,
amt,
AVG(amt) OVER (ORDER BY dt ROWS BETWEEN 2 PRECEDING AND CURRENT ROW) AS running_avg,
CASE WHEN COUNT(*) OVER (ORDER BY dt ROWS BETWEEN 2 PRECEDING AND CURRENT ROW) = 3
THEN AVG(amt) OVER (ORDER BY dt ROWS BETWEEN 2 PRECEDING AND CURRENT ROW)
ELSE 0
END AS running_avg2
FROM trans
ORDER BY dt
Results:
dt
amt
running_avg
running_avg2
2022-05-01
100
100
0
2022-05-02
150
125
0
2022-05-03
50
100
100
2022-05-14
250
150
150
2022-05-15
0
100
100

Sum over N days in SQL server

I have below table
AccountID
Date
Amount
123
07/06/2021
2000
123
07/12/2021
9000
123
07/16/2021
500
123
07/20/2021
500
123
07/28/2021
500
I am trying to sum the amount over 5 working days and get the output like below
AccountID
Date
Sum Amount
123
07/06/2021
11000
123
07/12/2021
9500
123
07/16/2021
1000
123
07/20/2021
500
123
07/28/2021
500
Also I am trying to ignore weekends(Saturday and Sunday)
I was able to add over 5 days using the below query. But not able to skip weekends.
Select distinct
t1.accountid,
convert(datetime,t1.[date]),
t1.amount,
sum(t2.amount)
from [dbo].[HANMI_ABRIGO_TRANSACTIONS] t1
cross apply
(
SELECT *
FROM [dbo].[HANMI_ABRIGO_TRANSACTIONS] a
WHERE a.accountid= t1.accountid
AND
(
convert(datetime,a.[date]) < DATEADD(DAY,5,convert(datetime,t1.[date]))
AND
convert(datetime,a.[date]) >= convert(datetime,t1.[date])
)
And a.accountid = '123'
And a.date like '2021-07%'
and a.amount > 0
)t2
where t1.accountid = '123'
And t1.date like '2021-07%'
and t1.amount > 0
group by
t1.accountid,
convert(datetime,t1.[date]),
t1.amount
order by convert(datetime,t1.[date])
Thanks!
I think this is the query you are asking for:
SELECT AccountId, Date,
(
SELECT SUM(Amount)
FROM HANMI_ABRIGO_TRANSACTIONS h2
WHERE
h1.AccountID = h2.AccountID and
DATEPART(WEEKDAY, h2.Date) not in (1, 7) and
h2.Date between h1.Date AND DATEADD(d, 5, h1.Date)
) as SumAmount
FROM HANMI_ABRIGO_TRANSACTIONS h1
The results are:
AccountId
Date
SumAmount
123
2021-07-06
2000
123
2021-07-12
9500
123
2021-07-16
1000
123
2021-07-20
500
123
2021-07-28
500
SQL Fiddle: http://sqlfiddle.com/#!18/3d6bae/8

Running assignment of values with break T-SQL

With the below table of data
Customer
Amount Billed
Amount Paid
Date
1
100
60
01/01/2000
1
100
40
01/02/2000
2
200
150
01/01/2000
2
200
30
01/02/2000
2
200
10
01/03/2000
2
200
15
01/04/2000
I would like to create the next two columns
Customer
Amount Billed
Amount Paid
Assigned
Remainder
Date
1
100
60
60
40
01/01/2000
1
100
40
40
0
01/02/2000
2
200
150
150
50
01/01/2000
2
200
30
30
20
01/02/2000
2
200
10
10
10
01/03/2000
2
200
15
10
-5
01/04/2000
The amount paid on each line should be removed from the amount billed and pushed onto the next line for the same customer. The process should continue until there are no more records or the remainder is < 0.
Is there a way of doing this without a cursor? Maybe a recursive CTE?
Thanks
As I mentioned in the comments, this is just a cumulative SUM:
WITH YourTable AS(
SELECT *
FROM (VALUES(1,100,60 ,CONVERT(date,'01/01/2000')),
(1,100,40 ,CONVERT(date,'01/02/2000')),
(2,200,150,CONVERT(date,' 01/01/2000')),
(2,200,30 ,CONVERT(date,'01/02/2000')),
(2,200,10 ,CONVERT(date,'01/03/2000')),
(2,200,15 ,CONVERT(date,'01/04/2000')))V(Customer,AmountBilled,AmountPaid,[Date]))
SELECT Customer,
AmountBilled,
AmountPaid,
AmountBilled - SUM(AmountPaid) OVER (PARTITION BY Customer ORDER BY [Date] ASC
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS Remainder,
[Date]
FROM YourTable
ORDER BY Customer,
[Date];
Note this returns -5 for the last row, not 5, as 200 - 205 = -5. If you want 5 wrap the whole expression in an absolute function.
You can achieve this using recursive CTE as well.
DECLARE #customer table (Customer int, AmountBilled int, AmountPaid int, PaidDate date)
insert into #customer
values
(1 ,100, 60 ,'01/01/2000')
,(1 ,100, 40 ,'01/02/2000')
,(2 ,200, 150 ,'01/01/2000')
,(2 ,200, 30 ,'01/02/2000')
,(2 ,200, 10 ,'01/03/2000')
,(2 ,200, 15 ,'01/04/2000');
;WITH CTE_CustomerRNK as
(
SELECT *, ROW_NUMBER() OVER(PARTITION BY customer order by paiddate) AS RNK
from #customer),
CTE_Customer as
(
SELECT customer, AmountBilled, AmountPaid, (amountbilled-amountpaid) as remainder, paiddate ,RNK FROM CTE_CustomerRNK where rnk = 1
union all
SELECT r.customer, r.AmountBilled, r.AmountPaid, (c.remainder - r.AmountPaid) as remainder, r.PaidDate, r.rnk
FROM CTE_CustomerRNK as r
inner join CTE_Customer as c
on c.Customer = r.Customer
and r.rnk = c.rnk + 1
)
SELECT customer, AmountBilled, AmountPaid, remainder, paiddate
FROM CTE_Customer order by Customer
customer
AmountBilled
AmountPaid
remainder
paiddate
1
100
60
40
2000-01-01
1
100
40
0
2000-01-02
2
200
150
50
2000-01-01
2
200
30
20
2000-01-02
2
200
10
10
2000-01-03
2
200
15
-5
2000-01-04

How to write sql to generate cumulative monthly sales per customer in Postgres

Given that I have a table called orders
orders
id
customer_id
created_at
How do I write a query to return the monthly cumulative order counts for each customer? I want to include the missing months in the series for Jan 2018 to May 2018
data
id customer_id created_at
1 200 01/20/2018
2 300 01/21/2018
3 200 01/22/2018
4 200 03/20/2018
5 300 03/20/2018
6 200 04/20/2018
7 200 04/20/2018
expected result
customer_id month count
200 01/01/2018 2
200 02/01/2018 2
200 03/01/2018 3
200 04/01/2018 5
200 05/01/2018 5
300 01/01/2018 1
300 02/01/2018 1
300 03/01/2018 2
300 04/01/2018 2
300 05/01/2018 2
I have a query to calculate the net cumulative count per month. I didn't have much success while converting the query to work for per customer cumulative counts.
WITH monthly_orders AS (
SELECT date_trunc('month', orders.created_at) AS mon,
COUNT(orders.id) AS mon_count
from orders
GROUP BY 1
)
SELECT TO_CHAR(mon, 'YYYY-MM') AS mon_text,
COALESCE(SUM(c.mon_count) OVER (ORDER BY c.mon), 0) AS running_count
FROM generate_series('2018-01-01'::date, '2018-06-01'::date, interval '1 month') mon
LEFT JOIN monthly_orders c USING(mon)
ORDER BY mon_text;
If I understand correctly, you can just do:
select o.customer_id, date_trunc('month', o.created_at) AS mon,
count(*) AS mon_count,
sum(count(*)) over (partition by o.customer_id
order by date_trunc('month', o.created_at)
) as running_count
from orders o
group by o.customer_id, mon
order by o.customer_id, mon;

return enddate where cumulative sum of fee column less than 1100 using sql

end date fee
-----------------
05-Sep-14 700
12-Sep-14 200
19-Sep-14 100
26-Sep-14 300
03-Oct-14 400
In the table shown here, I need to return enddate where cumulative sum of fee column is less than 1100 using SQL.
Example:
19-Sep-14 (700 + 200 + 100 < 1100)
SELECT TOP 1
t1.enddate,
t1.fee,
SUM(t2.fee) as cumulative_sum
FROM test t1
INNER JOIN tableName t2 on t1.enddate >= t2.enddate
GROUP BY t1.enddate, t1.fee
HAVING SUM(t2.fee) < 1100
ORDER BY t1.enddate DESC
Data sample
create view fees
as
select cast('05-Sep-14' as date) as end_date, 700 as fee
union all select '12-Sep-14', 200
union all select '19-Sep-14', 100
union all select '26-Sep-14', 300
union all select '03-Oct-14', 400
Solution
SELECT TOP 1
a.end_date,
SUM(b.fee) as cumulative
FROM fees a CROSS JOIN fees b
WHERE a.end_date >= b.end_date
GROUP BY a.end_date
HAVING SUM(b.fee) < 1100
ORDER BY end_date desc