PostgreSQL - Generate series using subqueries - sql

Using PostgreSQL, I need to accomplish the following scenario. I have a table called routine, where I store start_date and end_date columns. I have another table called exercises, where I store all the data related with each exercise and finally, I have a table called routine_exercise where I create the relationship between the routine and the exercise. Each routine can have seven days (one day indicates the day of the week, e.g: 1 means Monday, etc) of exercises and each day can have one or more exercise. For example:
Exercise Table
Exercise ID
Name
1
Exercise 1
2
Exercise 2
3
Exercise 3
Routine Table
Routine ID
Name
1
Routine 1
2
Routine 2
3
Routine 3
Routine_Exercise Table
Exercise ID
Routine ID
Day
1
1
1
2
1
1
3
1
1
1
1
2
2
1
3
3
1
4
The thing that I'm trying to do is generate a series from start_date to end_date (e.g 03-25-2020 to 05-25-2020, two months) and assign to each date the number of day it supposed to work.
For example, using the data in the Routine_Exercise Table the user should only workout days: 1,2,3,4, so I would like to attach that number to each date. For example, something like this:
Expected Result
Date
Number
03-25-2020
1
03-26-2020
2
03-27-2020
3
03-28-2020
4
03-29-2020
null
03-30-2020
null
03-31-2020
null
04-01-2020
1
04-02-2020
2
04-03-2020
3
04-04-2020
4
04-05-2020
null
Any suggestions or different ideas on how to implement this? Another solution that doesn't require series?
Thanks in advance!

You can generate the dates between start and end input dates using generate_series and then do left join with your routine_exercise table as follows:
SELECT t.d, re.day
FROM generate_series(timestamp '2020-03-25', timestamp '2020-05-25',
interval '1 day') AS t(d)
left join (select distinct day from Routine_Exercise re WHERE ROUTINE_ID = 1) re
on mod(extract(day from (t.d -timestamp '2020-03-25')), 7) + 1 = re.day;

Related

SELECT-SQL-Statement - Transformation of a single data record with a date period into several single data records per day

I have the following example records in a table that contains records with time periods (Originally import data):
ID
DateFrom
DateTo
Value
1
01.01.2021
03.01.2021
A
2
02.03.2021
06.03.2021
B
...
The data is imported as individual records into a separate table.
I would like to put the data records into the following form with a SELECT query in order to be able to check in the 2nd step whether all data were imported as a single data record:
ID
DateFrom
DateTo
Value
1
01.01.2021
01.01.2021
A
1
02.01.2021
02.01.2021
A
1
03.01.2021
03.01.2021
A
2
02.03.2021
02.03.2021
B
2
03.03.2021
03.03.2021
B
2
04.03.2021
04.03.2021
B
2
05.03.2021
05.03.2021
B
2
06.03.2021
06.03.2021
B
..
Unfortunately, I have a knot in my head and cannot find a query approach.
I am sure the hierarchical query suits here. The problem though I still can't fit it in here without using distinct.
This query will work with assummption that "datefrom" and "dateto" columns are of DATE format.
Replace "test_data" with table name you store dates in.
select td.id,
qq.day_date,
value
from test_data td
join (select distinct id,
datefrom + level - 1 day_date
from test_data
connect by level <= (dateto - datefrom + 1)) qq
on qq.id = td.id
order by td.id, qq.day_date;
If datato and datafrom are just varchars, you may convert them to dates using to_date function.

Date snapshot table transformation in SQL

Data transformation issue on Postgres. I have a process where submission is made, and some stages/events take place to process the submission. A row is created per submission. When an stage is complete, a timestamp populates the column for that stage.
The raw data is in the format
submission_id stage1.time stage2.time stage3.time
XYZ 2016/01/01 2016/01/04 2016/01/05
I want to make a "snapshot" table for this (perhaps theres a better name for this?) which looks as follows, for the above example
snapshot_date submission_id stage_number days_in_stage
2016/01/01 XYZ 1 0
2016/01/02 XYZ 1 1
2016/01/03 XYZ 1 2
2016/01/04 XYZ 2 0
2016/01/05 XYZ 3 0
So basically, on a given date in the past, what submissions are in what stages and how long had they been in those stages.
So far I've managed to generate a date table using
SELECT ts::date
FROM (
SELECT min(stage1.time) as first_date
, max(stage1.time) as last_date
FROM schema.submissions
) h
, generate_series(h.first_date, h.last_date, interval '1 day') g(ts)
but I'm stuck on where I should be joining next, so any pointers would be appreciated.

Always Include Certain Records in Daterange

Sorry if this is too general a question, but I couldn't find much material on this.
I'm wondering if there is any way in SQL or Tableau to always include certain records despite changes in a date range?
For example, I have 200 records that range from 1940-2004 and want 2 or 3 of these records to always be returned in the query (which includes a date range statement) is there a known method?
I'd like to avoid altering the date attributes based on the date range statement itself is possible.
Initial data:
Person_ID | Group | Date
ID 1 2 1-1-2003
ID 2 1 1-1-1994
ID 3 1 1-1-1985
ID 4 1 1-1-1992
ID 5 2 1-1-1991
ID 6 2 1-1-2002
ID 7 1 1-1-2003
ID 8 2 1-1-2005
ID 9 2 1-1-1999
ID 10 1-1-2002
ID 11 1-1-1989
For my results, I want it to be possible so that no matter the daterange I select, ID 10 and ID 11 are included.
SELECT Person_ID
FROM table
WHERE DATE BETWEEN date1 AND date2
Will always yield ID 10 and ID 11 no matter the dates inputted.
I don't know much about tableau but you can try this...
SELECT Person_ID
FROM table
WHERE (DATE BETWEEN date1 AND date2) OR Person_ID = 10 OR Person_ID = 11
If you want help on figuring out queries try and say what you want as literal as possible using sql words. So in this case you could say "I want to select the person_id from table where the date is between date1 and date2 or if the person_id is 10 or if the person_id is 11". If you ever say but (ex: date between date1 and date2 but if their id is 10 then also do it) then most likely you can put an or there : ). So if I were to do it without the or it sound more normal (in my opinion at least -> "where the date is between date1 and date2 but if the person_id is 10 or 11 also include it"). Hope that helps!

Join to Calendar Table - 5 Business Days

So this is somewhat of a common question on here but I haven't found an answer that really suits my specific needs. I have 2 tables. One has a list of ProjectClosedDates. The other table is a calendar table that goes through like 2025 which has columns for if the row date is a weekend day and also another column for is the date a holiday.
My end goal is to find out based on the ProjectClosedDate, what date is 5 business days post that date. My idea was that I was going to use the Calendar table and join it to itself so I could then insert a column into the calendar table that was 5 Business days away from the row-date. Then I was going to join the Project table to that table based on ProjectClosedDate = RowDate.
If I was just going to check the actual business-date table for one record, I could use this:
SELECT actual_date from
(
SELECT actual_date, ROW_NUMBER() OVER(ORDER BY actual_date) AS Row
FROM DateTable
WHERE is_holiday= 0 and actual_date > '2013-12-01'
ORDER BY actual_date
) X
WHERE row = 65
from here:
sql working days holidays
However, this is just one date and I need a column of dates based off of each row. Any thoughts of what the best way to do this would be? I'm using SQL-Server Management Studio.
Completely untested and not thought through:
If the concept of "business days" is common and important in your system, you could add a column "Business Day Sequence" to your table. The column would be a simple unique sequence, incremented by one for every business day and null for every day not counting as a business day.
The data would look something like this:
Date BDAY_SEQ
========== ========
2014-03-03 1
2014-03-04 2
2014-03-05 3
2014-03-06 4
2014-03-07 5
2014-03-08
2014-03-09
2014-03-10 6
Now it's a simple task to find the N:th business day from any date.
You simply do a self join with the calendar table, adding the offset in the join condition.
select a.actual_date
,b.actual_date as nth_bussines_day
from DateTable a
join DateTable b on(
b.bday_seq = a.bday_seq + 5
);

Oracle Database Temporal Query Implementation - Collapse Date Ranges

This is the result of one of my queries:
SURGERY_D
---------
01-APR-05
02-APR-05
03-APR-05
04-APR-05
05-APR-05
06-APR-05
07-APR-05
11-APR-05
12-APR-05
13-APR-05
14-APR-05
15-APR-05
16-APR-05
19-APR-05
20-APR-05
21-APR-05
22-APR-05
23-APR-05
24-APR-05
26-APR-05
27-APR-05
28-APR-05
29-APR-05
30-APR-05
I want to collapse the date ranges which are continuous, into intervals. For examples,
[01-APR-05, 07-APR-05], [11-APR-05, 16-APR-05] and so on.
In terms of temporal databases, I want to 'collapse' the dates. Any idea how to do that on Oracle? I am using version 11. I searched for it and read a book but couldn't find/understand how to do it. It might be simple, but everyone has their own flaws and Oracle is mine. Also, I am new to SO so my apologies if I have violated any rules. Thank You!
You can take advantage of the ROW_NUMBER analytical function to generate a unique, sequential number for each of the records (we'll assign that number to the dates in ascending order).
Then, you group the dates by difference between the date and the generated number - the consecutive dates will have the same difference:
Date Number Difference
01-APR-05 1 1 -- MIN(date_val) in group with diff. = 1
02-APR-05 2 1
03-APR-05 3 1
04-APR-05 4 1
05-APR-05 5 1
06-APR-05 6 1
07-APR-05 7 1 -- MAX(date_val) in group with diff. = 1
11-APR-05 8 3 -- MIN(date_val) in group with diff. = 3
12-APR-05 9 3
13-APR-05 10 3
14-APR-05 11 3
15-APR-05 12 3
16-APR-05 13 3 -- MAX(date_val) in group with diff. = 3
Finally, you select the minimal and maximal date in each of the groups to get the beginning and ending of each range.
Here's the query:
SELECT
MIN(date_val) start_date,
MAX(date_val) end_date
FROM (
SELECT
date_val,
row_number() OVER (ORDER BY date_val) AS rn
FROM date_tab
)
GROUP BY date_val - rn
ORDER BY 1
;
Output:
START_DATE END_DATE
------------ ----------
01-04-2005 07-04-2005
11-04-2005 16-04-2005
19-04-2005 24-04-2005
26-04-2005 30-04-2005
You can check how that works on SQLFidlle: Dates ranges example