SQL Sum Sales for a week based on previous week range - sql

Need help with SQL query in resolving the below problem:
Input table has Product ID, Week and sales which is unique, while columns Start and End Week column are the range based on which I want to sum up sales for that particular week.
From the Input table we want to extract the Product ID along with Week and get the sum of sales based on the week being between start week and end week range.
The Sales value against each Product ID and Week is the sum of sales based on the corresponding start and end week for that product and week combination in the input table.
I was trying to do a self join on the input table but realized it would not work as I need to join on both Product ID and Week which will nullify the objective.
Select a.Product ID, a.Week, Sum(a.Sales)
from Input as a, Input as b
where a.Product ID = b.Product ID
and a.Week between b.Start Week and b.End Week
group by 1,2

You just need to switch to an Outer Join:
Select a.Product ID, a.Week, Sum(a.Sales)
from Input as a LEFT JOIN Input as b
ON a.Product ID = b.Product ID
and a.Week between b.Start Week and b.End Week
group by 1,2
This should result in a better plan than Alec's subquery.

I'm not crazy about the sub query, but it should at least get you started:
SELECT
a.ProductID,
a.Week,
(
SELECT
SUM(b.Sales)
FROM
Table b
WHERE
b.ProductID = a.ProductID AND
b.Week BETWEEN a.StartWeek AND a.EndWeek
) as CumulativeSales
FROM
Table a

Related

query to find the maximum gap between dates

I have a table with the name of each customer and date columns and want to write a query to give me the number of gap days for each user,
name date
ali 2022-01-01
ali 2022-01-04
ali 2022-01-05
ser 2022-03-01
the answer should be 3 for ali and for ser will be null.
here is what I tried:
select name ,min(date) over (partition by name order by date) start_date , max(date) over (partition by name order by date) end_date from table
One approach to achieve this is using a window function (like lag, lead) to find the prior/next day and then find the difference between the dates (current and prior, for example ) using datediff function. Something like this..
SELECT name,
MAX(datediff(date, PreviousDate)) AS Gap
FROM (SELECT name,
date,
LAG(date) OVER(PARTITION BY name ORDER BY date) as PreviousDate
FROM table t
GROUP BY name
my approach is to match every record with the closest date then find the maximum gap and left join with the original table to get the gap for each user.
here's MySQL version:
select
cu.name, max(cg.gap) maxgap
from
customers cu left join
(
select
c.name, datediff(min(cn.date), c.date) gap
from
customers c left join customers cn on c.name = cn.name
where
cn.date > c.date
group by
c.name, c.date
) cg
on cu.name = cg.name
group by
cu.name

Latest value of compared date range? (SQL/Snowflake)

I have values in Table-A like:
Patient|Invoice|Date
A,111,2021-02-01
A,222,2021-01-01
B,333,2021-03-01
B,444,2021-02-01
C,555,2021-04-01
C,666,2021-03-01
And values in Table-B like:
Patient|Value|Date
A,2,2021-01-05
A,3,2021-01-05
A,3,2021-02-05
B,1,2021-02-05
B,1,2021-03-05
C,6,2021-01-01
And I want to join the two tables such that I see the most recent cumulative sum of values in Table-B as-of the Date in Table-A for a given Patient.
Patient|Invoice|Latest Value|Date
A,111,5,2021-02-01
A,222,0,2021-01-01
B,333,1,2021-03-01
B,444,0,2021-02-01
C,555,6,2021-04-01
C,666,6,2021-03-01
How would I join these two tables by date to accomplish this?
First step seems like a basic SQL join:
select patient, invoice, sum(value), date
from table1 a
join table2 b
on a.patient=b.patient
and a.date=b.date
group by patient, invoice, date
But instead of a plain sum() you can apply a sum() over():
select patient, invoice
, sum(value) over(partition by patient order by date)
, date
from table1 a
join table2 b
on a.patient=b.patient
and a.date=b.date
group by patient, invoice, date
I think that first we need to calculate the time intervals when the invoice is valid (using LAG function), then calculate the cumulative SUM.
WITH A AS (
SELECT Patient, Invoice, Date, IFNULL(LAG(Date) OVER(PARTITION BY Patient ORDER BY Date), '1900-01-01') AS LG
FROM Table_A
)
SELECT DISTINCT A.Patient, A.Invoice, IFNULL(SUM(B.Value) OVER(PARTITION BY A.Patient ORDER BY A.Date), 0) AS Latest_Value, A.Date
FROM A
LEFT JOIN Table_B AS B
ON A.Patient = B.Patient
AND B.Date >= A.LG AND B.Date < A.Date
GROUP BY A.Patient, A.Invoice, A.Date, B.Value
ORDER BY A.Patient, A.Invoice, A.Date;

SQL join with dates

I have table A with columns:
customer_id, month, amount
Month is like 2015/12/01 meaning it's amount paid in December 2015.
Then there is table B with columns:
customer_id, plan_id, start_date, end_date
This is information on when a particular customer started and ended using a particular plan. The current plan will have end_date NULL. One customer could have used many different plans in the past.
I need to add plan_id column to table A by joining these 2 tables but I have no idea how to deal with the dates.
Note that for each customer one month should correspond to one plan only. So even if the start_date for a plan is 2015/11/02, it should only be applied for the next month (2015/12/01).
This is a basically a join, but with inequalities:
select a.*, b.*
from a left join
b
on a.customer_id = b.customer_id and
a.month >= b.start_date and
(a.month <= b.end_date or b.end_date is null);

SQL monthly rolling sum

I am trying to calculate monthly balances of bank accounts from the following postgresql table, containing transactions:
# \d transactions
View "public.transactions"
Column | Type | Collation | Nullable | Default
--------+------------------+-----------+----------+---------
year | double precision | | |
month | double precision | | |
bank | text | | |
amount | numeric | | |
In "rolling sum" I mean that the sum should contain the sum of all transactions until the end of the given month from the beginning of time, not just all transactions in thegiven month.
I came up with the following query:
select
a.year, a.month, a.bank,
(select sum(b.amount) from transactions b
where b.year < a.year
or (b.year = a.year and b.month <= a.month))
from
transactions a
order by
bank, year, month;
The problem is that this contains as many rows for each of the months for each banks as many transactions were there. If more, then more, if none, then none.
I would like a query which contains exactly one row for each bank and month for the whole time interval including the first and last transaction.
How to do that?
An example dataset and a query can be found at https://rextester.com/WJP53830 , courtesy of #a_horse_with_no_name
You need to generate a list of months first, then you can outer join your transactions table to that list.
with all_years as (
select y.year, m.month, b.bank
from generate_series(2010, 2019) as y(year) --<< adjust here for your desired range of years
cross join generate_series(1,12) as m(month)
cross join (select distinct bank from transactions) as b(bank)
)
select ay.*, sum(amount) over (partition by ay.bank order by ay.year, ay.month)
from all_years ay
left join transactions t on (ay.year, ay.month, ay.bank) = (t.year::int, t.month::int, t.bank)
order by bank, year, month;
The cross join with all banks is necessary so that the all_years CTE will also contain a bank for each month row.
Online example: https://rextester.com/ZZBVM16426
Here is my suggestion in Oracle 10 SQL:
select a.year,a.month,a.bank, (select sum(b.amount) from
(select a.year as year,a.month as month,a.bank as bank,
sum(a.amount) as amount from transactions c
group by a.year,a.month,a.bank
) b
where b.year<a.year or (b.year=a.year and b.month<=a.month))
from transactions a order by bank, year, month;
Consider aggregating all transactions first by bank and month, then run a window SUM() OVER() for rolling monthly sum since earliest amount.
WITH agg AS (
SELECT t.year, t.month, t.bank, SUM(t.amount) AS Sum_Amount
FROM transactions t
GROUP BY t.year, t.month, t.bank
)
SELECT agg.year, agg.month, agg.bank,
SUM(agg.Sum_Amount) OVER (PARTITION BY agg.bank ORDER BY agg.year, agg.month) AS rolling_sum
FROM agg
ORDER BY agg.year, agg.month, agg.bank
Should you want YTD rolling sums, adjust the OVER() clause by adding year to partition:
SUM(agg.Sum_Amount) OVER (PARTITION BY agg.bank, agg.year ORDER BY agg.month)

SQL - daily change in a value with business day into consideration

Hi I am trying to write a query that will track daily changes of a column which isn't populated on weekends/holidays.
First my data looks something like this :
Date Value
11/5/2015 10
11/6/2015 11
11/9/2015 12
11/10/2015 12
11/11/2015 11
so i want my query to give me result of the value change each date vs. the previous business day to return something like this:
Date Change in Value since previous business day
11/5/2015 -
11/6/2015 1
11/9/2015 1
11/10/2015 0
11/11/2015 -1
how do i write a write a query in MS Access which tracks daily changes over a business day? Currently i have written the following which only returns daily change over a calendar day as opposed to a biz day. so it won't return anything on Mondays.
SELECT A.Date, A.Value, ( A.Value - B.Value) as [Daily change]
FROM Table as A INNER JOIN Table as B on (A.date = B.date+1)
=============================================================================
thanks guys I've tried all 3 suggestions but they didn't work unfortunately :( there's another column called product ID and perhaps that is why? in other words, on each day, each product ID will have their own distinct values. there is a total of 100 product IDs so on each date there are 100 different values and I would like to track daily changes (business day basis) for each of the 100 product IDs. could anyone kindly help here? :(
It's hacky, but why not:
Join on 3 days ago also
use iif to say "if the 1 day ago diff is null then show the 3 days ago diff"
SELECT
A.Date, A.Value,
iif (isNull( A.Value - B.Value), ( A.Value - C.Value), ( A.Value - B.Value) ) as [change since last biz day]
FROM [Table] as A
left JOIN [Table] as B on ( A.Date = B.Date + 1 )
left JOIN [Table] as C on ( A.Date = C.Date + 3 )
Sometimes I just say it many times in English and the SQL follows. You want it where B equals the maximum date that is less than A.
SELECT A.Date,
A.Value,
A.Value - B.Value as [Daily Change]
FROM MyTable as A
INNER JOIN MyTable as B
ON B.date = (SELECT MAX(C.date) FROM MyTable C WHERE C.Date < A.Date)
ORDER BY A.Date