SQL - dynamic sum based on dynamic date range - sql

I'm new to SQL and I'm not even sure if what I am trying to achieve is possible.
I have two tables. The first gives an account number, a 'from' date and a 'to' date. The second table shows monthly volume for each account.
Table 1 - Dates
Account# Date_from Date_to
-------- --------- -------
123 2018-01-01 2018-12-10
456 2018-06-01 2018-12-10
789 2018-04-23 2018-11-01
Table 2 - Monthly_Volume
Account# Date Volume
--------- ---------- ------
123 2017-12-01 5
123 2018-01-15 5
123 2018-02-05 5
456 2018-01-01 10
456 2018-10-01 15
789 2017-06-01 5
789 2018-01-15 10
789 2018-06-20 7
I would like to merge the two tables in such a way that each account in Table 1 has a fourth column that gives the sum of Volume between Date_from and Date_to.
Desired Result:
Account# Date_from Date_to Sum(Volume)
-------- --------- ------- -----------
123 2018-01-01 2018-12-10 10
456 2018-06-01 2018-12-10 15
789 2018-04-23 2018-11-01 7
I believe that this would be possible to achieve for each account individually by doing something like the following and joining the result to the Dates table:
SELECT
Account#,
SUM(Volume)
FROM Monthly_Volume
WHERE
Account# = '123'
AND Date_from >= TO_DATE('2018-01-01', 'YYYY-MM-DD')
AND Date_to <= TO_DATE('2018-12-10', 'YYYY-MM-DD')
GROUP BY Account#
What I'd like to know is whether it is possible to achieve this without having to individually fill in the Account#, Date_from and Date_to for each account (there are ~1,000 accounts), but have it be done automatically for each entry in the Dates table.
Thank you!

You should be able to use join and group by:
select d.account#, d.Date_from, d.Date_to, sum(mv.volume)
from dates d left join
monthly_volume mv
on mv.account# = d.account# and
mv.date between d.Date_from and d.Date_to
group by d.account#, d.Date_from, d.Date_to;

Related

Update SQL table date based on column in another table

I have a table like this:
ID
start_date
end_date
1
09/01/2022
1
09/04/2022
2
09/01/2022
I have another reference table like this:
ID
date
owner
1
09/01/2022
null
1
09/02/2022
null
1
09/03/2022
Joe
1
09/04/2022
null
1
09/05/2022
Jack
2
09/01/2022
null
2
09/02/2022
John
2
09/03/2022
John
2
09/04/2022
John
For every ID and start_date in the first table, I need find rows in the reference table that occur after start_date, and have non-null owner. Then I need to update this date value in end_date of first table.
Below is the output that I want:
ID
date
end_date
1
09/01/2022
09/03/2022
1
09/04/2022
09/05/2022
2
09/01/2022
09/02/2022

How to get the last day of the month without LAST_DAY() or EOMONTH()?

I have a table t with:
DATE
LOCATION
PRODUCT_ID
AMOUNT
2021-10-29
1
123
10
2021-10-30
1
123
9
2021-10-31
1
123
8
2021-10-29
1
456
100
2021-10-30
1
456
90
2021-10-31
1
456
80
2021-10-29
2
123
18
2021-10-30
2
123
17
2021-11-29
2
456
18
I need to find the AMOUNT of each PRODUCT_ID for each combination of LOCATION + PRODUCT_ID.
If a PRODUCT_ID has no entry for that day the AMOUNT is NULL.
So the result should look like:
DATE
LOCATION
PRODUCT_ID
AMOUNT
2021-10-31
1
123
8
2021-10-31
1
456
80
2021-10-31
2
123
NULL
2021-11-30
2
456
NULL
Sadly EXASOL has no LAST_DAY() or EOMONTH() function. How can I solve this?
You can get to the last day of the month using a date_trunc function in combination with date_add:
case
when t.date = date_add('day', -1, date_add('month', 1, date_trunc('month', t.date)))
then 'Y' else 'N' end as end_of_month
That being said, if you group your table for all combinations of locations and products, you will not get NULLs for products without sales on the last day of the month as shown in your output table.
When you group your data, any value that does not exist will simply not show up in your output table. If you want to force nulls to show up, you can create a new table that contains all combinations of products, locations, and hard-coded end of month dates.
Then, you can left join your old table with this new hard-coded table by date, location, and product. This method will give you the NULL values you expect.

Use Calendar table to generate historical view of the data

I have a created_date (timestamp) on 1 of my tables, that also has the duration column of a project, and I need to join with another table that only has first_day_of_month column that has the first day of each month, and other relevant information.
Table 1
id project_id created_date duration
1 12345 01/01/2015 10
2 12345 20/10/2015 11
3 12345 10/04/2016 13
4 12345 10/08/2016 15
Table 2
project_id month_start_date
12345 01/01/2015
12345 01/02/2015
12345 01/03/2015
12345 01/04/2015
...
12345 01/08/2016
Expected result
project_id month_start_date duration
12345 01/01/2015 10
12345 01/02/2015 10
...
12345 01/10/2015 11
12345 01/11/2015 11
...
12345 01/04/2016 13
12345 01/05/2016 13
12345 01/06/2016 13
...
12345 01/08/2016 15
I want to be able to present the data listed in my second table historically. So, basically I want the query to return the same duration related to the month_start_date, so that values will repeat until another dateadd(month,datediff(month,0,created_date),0) = first_day_of_month is met... and so forth.
This is my query:
select table2.project_name,
table2.month_start_date,
table1.duration,
table1.created_date
from table1 left outer join table2
on table1.project_id=table2.project_id
where dateadd(month,datediff(month,0,table1.created_date),0)<=table2.month_start_date
group by table2.project_name,table2.month_start_date,table1.duration,table1.created_date
order by table2.month_start_date asc
but I get repeated records on this:
Result I'm getting
project_id month_start_date duration
12345 01/01/2015 10
12345 01/02/2015 10
...
12345 01/10/2015 10
12345 01/10/2015 11
...
12345 01/04/2016 10
12345 01/04/2016 11
12345 01/04/2016 13
...
12345 01/08/2016 10
12345 01/08/2016 11
12345 01/08/2016 13
12345 01/08/2016 15
Can anyone help?
Thank you!
I'd use CROSS/OUTER APPLY operator.
Here is one possible variant. For each row in your calendar table Table2 (for each month) the inner correlated subquery inside the CROSS APPLY finds one row from Table1. It will be the row with the same project_id and the first row with created_date before the month_start_date plus 1 month.
SELECT
Table2.project_id
,Table2.month_start_date
,Durations.duration
FROM
Table2
CROSS APPLY
(
SELECT TOP(1) Table1.duration
FROM Table1
WHERE
Table1.project_id = Table2.project_id
AND Table1.created_date < DATEADD(month, 1, Table2.month_start_date)
ORDER BY Table1.created_date DESC
) AS Durations
;
Make sure that Table1 has index on (project_id, created_date) include (duration). Otherwise, performance would be poor.

Subtition of cursor for combining tables with time periods

I have to combine two tables into one but I have to take validation dates into consideriation. For instance having two tables:
Address
ID AddressValue ValidFrom ValidTo
----------- --------------- ----------------------- -----------------------
1 Pink Street 2010-01-01 00:00:00.000 2010-01-20 00:00:00.000
2 Yellow Street 2010-01-20 00:00:00.000 2010-02-28 00:00:00.000
Phone
ID PhoneValue ValidFrom ValidTo
----------- ------------ ----------------------- -----------------------
1 123456789 2010-01-01 00:00:00.000 2010-01-15 00:00:00.000
2 987654321 2010-01-16 00:00:00.000 2010-01-31 00:00:00.000
I need to do combine them into new one:
NewSystem
ID NewPhone NewAddress ValidFrom ValidTo Version
----------- ----------- --------------- ----------------------- ----------------------- -------
1 123456789 Pink Street 2010-01-01 00:00:00.000 2010-01-15 00:00:00.000 4
2 NULL Pink Street 2010-01-15 00:00:00.000 2010-01-16 00:00:00.000 3
3 987654321 Pink Street 2010-01-16 00:00:00.000 2010-01-20 00:00:00.000 2
4 987654321 Yellow Street 2010-01-20 00:00:00.000 2010-01-31 00:00:00.000 1
5 NULL Yellow Street 2010-01-31 00:00:00.000 2010-02-28 00:00:00.000 0
The idea is quite simple. I create periods based on dates and then query each table in subqueries. I pasted my solution here: http://pastebin.com/cdKePA9X.
Right now I am trying to get rid of the cursor but I failed. I tried to use CTE but without success. Maybe someone of you faced similar problem or know how to combine these tables into one without using cursor. I pasted the 'create table' scripts here: http://pastebin.com/BeRspb6K.
Thank you in advanced.
First, construct new date ranges by merging the date ranges from the source tables. Second, for each new date range, lookup the valid data in the source tables.
WITH
old_ranges(d1,d2) AS (
SELECT ValidFrom,ValidTo FROM #Address UNION
SELECT ValidFrom,ValidTo FROM #Phone
),
new_ranges(d1,d2) AS (
SELECT d,LEAD(d) OVER(ORDER BY d)
FROM (
SELECT DISTINCT d
FROM old_ranges
UNPIVOT(d FOR dx IN (d1,d2)) p
) t
)
SELECT
ROW_NUMBER() OVER (ORDER BY d1) AS ID,
NewPhone,
NewAddress,
d1 AS ValidFrom,
d2 AS ValidTo
FROM new_ranges
OUTER APPLY (
SELECT PhoneValue AS NewPhone
FROM #Phone
WHERE ValidFrom <= d1 AND ValidTo >= d2
) x1
OUTER APPLY (
SELECT AddressValue AS NewAddress
FROM #Address
WHERE ValidFrom <= d1 AND ValidTo >= d2
) x2
WHERE d2 IS NOT NULL

Update the list of dates to have the same day

I have this in my table
TempTable
Id Date
1 1-15-2010
2 2-14-2010
3 3-14-2010
4 4-15-2010
i would like to change every record so that they have all same day, that is the 15th
like this
TempTable
Id Date
1 1-15-2010
2 2-15-2010 <--change to 15
3 3-15-2010 <--change to 15
4 4-15-2010
what if i like on the 30th?
the records should be
TempTable
Id Date
1 1-30-2010
2 2-28-2010 <--change to 28 because feb has 28 days only
3 3-30-2010 <--change to 30
4 4-30-2010
thanks
You can play some fun tricks with DATEADD/DATEDIFF:
create table T (
ID int not null,
DT date not null
)
insert into T (ID,DT)
select 1,'20100115' union all
select 2,'20100214' union all
select 3,'20100314' union all
select 4,'20100415'
SELECT ID,DATEADD(month,DATEDIFF(month,'20100101',DT),'20100115')
from T
SELECT ID,DATEADD(month,DATEDIFF(month,'20100101',DT),'20100130')
from T
Results:
ID
----------- -----------------------
1 2010-01-15 00:00:00.000
2 2010-02-15 00:00:00.000
3 2010-03-15 00:00:00.000
4 2010-04-15 00:00:00.000
ID
----------- -----------------------
1 2010-01-30 00:00:00.000
2 2010-02-28 00:00:00.000
3 2010-03-30 00:00:00.000
4 2010-04-30 00:00:00.000
Basically, in the DATEADD/DATEDIFF, you specify the same component to both (i.e. month). Then, the second date constant (i.e. '20100130') specifies the "offset" you wish to apply from the first date (i.e. '20100101'), which will "overwrite" the portion of the date your not keeping. My usual example is when wishing to remove the time portion from a datetime value:
SELECT DATEADD(day,DATEDIFF(day,'20010101',<date column>),'20100101')
You can also try something like
UPDATE TempTable
SET [Date] = DATEADD(dd,15-day([Date]), DATEDIFF(dd,0,[Date]))
We have a function that calculates the first day of a month, so I just addepted it to calculate the 15 instead...