Insert weekly dates starting fridays - sql

I'm trying to insert weekly dates in my table, the start date is always on Fridays and the end date is always on thursday. I'm using this code :
CREATE TABLE WEEK AS
WITH generator AS (
SELECT DATE '2015-01-02' + LEVEL - 1 dt
FROM dual
CONNECT BY LEVEL <= DATE '2016-01-21' - DATE '2015-01-02' + 1
)
SELECT to_char(dt, 'YYYY "SEM"IW') "KEY",
dt "DATE_START",
least(next_day(dt - 1, to_char(DATE '2015-01-08', 'DAY')),
last_day(dt)) "DATE_END"
FROM generator
WHERE to_char(dt, 'D') = to_char(DATE '2015-01-02', 'D');
The code is working for weeks on the same month, but if I have a starting date on a month and the finish date on the next month, there's no data inserting in my table.
For example :
Date_ START | DATE_END
29-05-2015 | 31-05-2015
05-06-2015 | 11-05-2015
Instead of 31-05-2015 I should have 04-06-2015.

I think the following is what you're after:
with generator as (select date '2015-05-29' + (level - 1)*7 dt
from dual
connect by level <= (date '2016-01-21' - date '2015-05-29')/7 + 1)
select to_char(dt, 'YYYY "SEM"IW') "KEY",
dt "DATE_START",
dt + 6 "DATE_END"
from generator;
KEY DATE_START DATE_END
---------- ---------- ----------
2015 SEM22 2015-05-29 2015-06-04
2015 SEM23 2015-06-05 2015-06-11
2015 SEM24 2015-06-12 2015-06-18
2015 SEM25 2015-06-19 2015-06-25
<snip>
2016 SEM01 2016-01-08 2016-01-14
2016 SEM02 2016-01-15 2016-01-21
This is assuming that the dates you have specified in the generator subquery have already been determined to be a Friday. Otherwise you could use something like trunc(<date> - 4, 'iw') + 4 or trunc(<date> + 3, 'iw') + 4 (depending on whether you want the previous or next Friday to be included for the date specified) to make sure that the seed date is definitely a Friday.

Maybe just an alternative you can try analytical function here. But as
suggested by Boniest "just adding days" will be better approach. Have
fun
WITH generator AS
(SELECT DATE '2015-01-02' + LEVEL - 1 dt
FROM dual
CONNECT BY LEVEL <= DATE '2016-01-21' - DATE '2015-01-02' + 1
)
-- select * from generator;
SELECT TO_CHAR(dt, 'YYYY "SEM"IW') "KEY",
dt "DATE_START",
lead(dt) over (ORDER BY (dt)) -1 "End Date"
FROM generator
WHERE TO_CHAR(dt, 'D') = TO_CHAR(DATE '2015-01-02', 'D');
----------------------------------OUTPUT-----------------------------------------
**KEY DATE_START End Date**
2015 SEM18 05/01/2015 05/07/2015
2015 SEM19 05/08/2015 05/14/2015
2015 SEM20 05/15/2015 05/21/2015
2015 SEM21 05/22/2015 05/28/2015
2015 SEM22 05/29/2015 06/04/2015
----------------------------------------------------------------------------------

Related

Multiple columns from DUAL?

I'm using Oracle 12c.
I need to generate dates for the start and end of weeks which begin on Thursday and end the following Wednesday.
An example of the output I'd like is -
I have the following SQL to generate the Start Date(s) -
SELECT startdate
FROM (SELECT next_day(date '2020-03-12' - 1, 'Thursday') + (level - 1) * 7 AS startdate
FROM dual
CONNECT BY level <=
((date'2024-03-31' - next_day(date '2020-03-12' - 1, 'Wednesday') + 7) / 7))
and this for End Dates -
(SELECT enddate
FROM (SELECT next_day(date '2020-03-12' - 1, 'Wednesday') + (level - 1) * 7 as enddate
FROM dual
CONNECT BY level <= ((date'2024-03-31' - next_day(date'2020-03-12' - 1, 'Thursday') + 7) / 7)))
Is it even possible to combine these in a single SQL query so the output of the query matches the desired format?
If so, then the addition of the week number would also be rather nice...:)
Generate the start date and then add 6 days to get the end date:
SELECT startdate,
startdate + INTERVAL '6' DAY AS enddate,
week
FROM (
SELECT NEXT_DAY(date'2020-03-12' - 1, 'Thursday')
+ ( level - 1 ) * INTERVAL '7' DAY as startdate,
LEVEL AS week
FROM DUAL
CONNECT BY
NEXT_DAY(date'2020-03-12' - 1, 'Thursday')
+ ( level - 1 ) * INTERVAL '7' DAY
+ INTERVAL '6' DAY
<= date'2024-03-31'
)
Which outputs:
STARTDATE
ENDDATE
WEEK
2020-03-12 00:00:00
2020-03-18 00:00:00
1
2020-03-19 00:00:00
2020-03-25 00:00:00
2
2020-03-26 00:00:00
2020-04-01 00:00:00
3
...
...
...
2024-03-07 00:00:00
2024-03-13 00:00:00
209
2024-03-14 00:00:00
2024-03-20 00:00:00
210
2024-03-21 00:00:00
2024-03-27 00:00:00
211
db<>fiddle here

How to call a dynamic day in sql?

I have a table I want to pull all date records before the most recent Friday. I know you can use sysdate (or getdate) to pull the current day, but all the solutions to similar questions I've looked at explicitly specify the numeric day of the week in the query. Todays Thursday so the below would work, but is there a dynamic alternative to this?
SELECT *
FROM table
WHERE datefield < sysdate - 6
You could use:
SELECT NEXT_DAY(TRUNC(SYSDATE) - 7, 'FRIDAY') AS last_friday
FROM DUAL;
However, if someone tries to query the data and is using a different language then you will get an error. I.e.:
ALTER SESSION SET NLS_DATE_LANGUAGE = 'FRENCH';
SELECT NEXT_DAY(TRUNC(SYSDATE) - 7, 'FRIDAY') AS last_friday
FROM DUAL;
Outputs:
ORA-01846: not a valid day of the week
A solution that works regardless of the language is to compare the day to the start of the ISO week (which is always a Monday):
SELECT TRUNC(SYSDATE, 'IW')
+ CASE WHEN SYSDATE - TRUNC(SYSDATE, 'IW') < 5
THEN -3
ELSE +4
END AS last_friday
FROM DUAL;
Outputs (with the NLS_DATE_FORMAT set to YYYY-MM-DD HH24:MI:SS (DY)):
LAST_FRIDAY
2022-01-14 00:00:00 (FRI)
db<>fiddle here
Your query would be:
SELECT *
FROM table
WHERE datefield < TRUNC(SYSDATE, 'IW')
+ CASE WHEN SYSDATE - TRUNC(SYSDATE, 'IW') < 5
THEN -3
ELSE +4
END
next_day function might help.
SQL> with test (datum) as
2 -- sample data; this January up to today
3 (select trunc(sysdate, 'mm') + level - 1
4 from dual
5 connect by level <= 20
6 )
7 select to_char(datum, 'dd.mm.yyyy, dy') datum
8 from test
9 where datum < next_day(sysdate - 7, 'FRIDAY')
10 order by datum;
DATUM
------------------------
01.01.2022, sat
02.01.2022, sun
03.01.2022, mon
04.01.2022, tue
05.01.2022, wed
06.01.2022, thu
07.01.2022, fri
08.01.2022, sat
09.01.2022, sun
10.01.2022, mon
11.01.2022, tue
12.01.2022, wed
13.01.2022, thu
14.01.2022, fri
14 rows selected.
SQL>

How to find out which months in a year range that's having 5 Saturdays between two given dates as per the inputs?

Only through Query mode, no execution block method.
i have tried the following but no luck
SELECT
TO_CHAR(dt, 'mon'),
COUNT(*) AS noofsat
FROM
(
SELECT
from_date + 1 AS dt
FROM
dual
CONNECT BY
level <= (
SELECT
TO_DATE - from_date
FROM
dual
)
)
WHERE
TO_CHAR(dt, 'd') = 7
GROUP BY
TO_CHAR(dt, 'mon')
HAVING
COUNT(*) = 5
i need only the month and year which has five Saturdays like August 2019
Change your date source to:
SELECT
TO_DATE('20190101','YYYYMMDD') + (LEVEL - 1) AS dt
FROM
dual
CONNECT BY
level <= 365
This is a very good question, In my situation I needed exactly the same thing but in SQL Server, and this is how I did it, I will post the answer for those that end up here looking for a SQL Server solution.
I ended up building a Date Dimension in my Data Warehouse and querying it:
DROP TABLE IF EXISTS ##Dates
CREATE TABLE ##Dates(DateValue Date,
YearValue INT,
MonthValue INT,
DayValue INT,
WeekDayValue INT)
DECLARE #start DATE = '2019-01-01'
WHILE #start < '2019-12-31'
BEGIN
INSERT INTO ##Dates(DateValue,
YearValue,
MonthValue,
DayValue,
WeekDayValue)
VALUES(#start,
DATEPART(YY,#start),
DATEPART(mm,#start), -- In oracle TO_CHAR(dt, 'mon')
DATEPART(dd,#start),
DATEPART(dw,#start)) --In oracle TO_CHAR(dt, 'd')
SET #start = DATEADD(dd,1,#start)
END
SELECT MonthValue, SUM(CASE WHEN WeekDayValue = 7 THEN 1 ELSE 0 END) AS NumOfSaturdays FROM ##Dates
WHERE DateValue BETWEEN '2019-01-01' AND '2019-12-01'
GROUP BY MonthValue
HAVING SUM(CASE WHEN WeekDayValue = 7 THEN 1 ELSE 0 END) > 4
Output:
MonthValue NumOfSaturdays
3 5
6 5
8 5
11 5
You could get the start of each month covered by your date range; this uses a pair of made-up fixed values as it isn't clear where you are getting your range from:
select add_months(trunc(date '2019-03-15', 'MM'), level - 1) as month_start
from dual
connect by level <= ceil(months_between(date '2019-11-21', date '2019-03-15'));
MONTH_STAR
----------
2019-03-01
2019-04-01
2019-05-01
2019-06-01
2019-07-01
2019-08-01
2019-09-01
2019-10-01
2019-11-01
9 rows selected.
You can then manipulate those dates in various ways. You can use the next_day() function to find the first Saturday of the month - based on the previous day, in case the 1st of the month is itself a Saturday. If you then add 28 days to that, you get the fifth Saturday of that month - or, for months with only four Saturdays, the first Saturday in the following month. So if you compare those generated Saturdays, if they are in the same month then that month must actually have five Saturdays.
select month_start,
next_day(month_start - 1, 'SATURDAY') as first_sat,
next_day(month_start - 1, 'SATURDAY') + 28 as poss_fifth_sat,
extract(month from month_start) as month_num,
extract(month from (next_day(month_start - 1, 'SATURDAY') + 28)) as poss_fifth_sat_month
from (
select add_months(trunc(date '2019-03-15', 'MM'), level - 1) as month_start
from dual
connect by level <= ceil(months_between(date '2019-11-21', date '2019-03-15'))
);
MONTH_STAR FIRST_SAT POSS_FIFTH MONTH_NUM POSS_FIFTH_SAT_MONTH
---------- ---------- ---------- ---------- --------------------
2019-03-01 2019-03-02 2019-03-30 3 3 -- same
2019-04-01 2019-04-06 2019-05-04 4 5
2019-05-01 2019-05-04 2019-06-01 5 6
2019-06-01 2019-06-01 2019-06-29 6 6 -- same
2019-07-01 2019-07-06 2019-08-03 7 8
2019-08-01 2019-08-03 2019-08-31 8 8 -- same
2019-09-01 2019-09-07 2019-10-05 9 10
2019-10-01 2019-10-05 2019-11-02 10 11
2019-11-01 2019-11-02 2019-11-30 11 11 -- same
9 rows selected.
You don't actually need to see those values, I'm just showing the working. But you can use the last two as a filter instead:
select month_start
from (
select add_months(trunc(date '2019-03-15', 'MM'), level - 1) as month_start
from dual
connect by level <= ceil(months_between(date '2019-11-21', date '2019-03-15'))
)
where extract(month from month_start)
= extract(month from (next_day(month_start - 1, 'SATURDAY') + 28));
MONTH_STAR
----------
2019-03-01
2019-06-01
2019-08-01
2019-11-01
You can then format those dates however you wish.
One possible issue (I'm sure there are others) is mentioned in the documentation:
The argument char must be a day of the week in the date language of your session, either the full name or the abbreviation.
So the code above will work as long as your session date language is English. If you can be 100% sure that everyone running this will always be using the same session settings then you use that fixed value (or whatever language is appropriate).
If you can't control that then you can't override the date language as you can with some other functions. As a workaround you could use a known Saturday date to obtain the current date language's name or abbreviation for Saturday, without you needing to know what the language actually is:
alter session set nls_date_language = 'Spanish';
select month_start
from (
select add_months(trunc(date '2019-03-15', 'MM'), level - 1) as month_start
from dual
connect by level <= ceil(months_between(date '2019-11-21', date '2019-03-15'))
)
where extract(month from month_start)
= extract(month from (next_day(month_start - 1, to_char(date '2000-01-01', 'Dy')) + 28));
MONTH_STAR
----------
2019-03-01
2019-06-01
2019-08-01
2019-11-01

Oracle date as fraction of month

I would like to get a table of months between two dates with a fraction of each month that the two dates cover.
For example with a start date of 15/01/2017 and end date of 01/03/2017 it would output:
01/2017 : 0.5483..
02/2017 : 1
03/2017: 0.0322..
where for January and March the calculations are 17/31 and 1/31 respectively. I currently have the query:
WITH dates_between as (SELECT ADD_MONTHS(TRUNC(TO_DATE(:givenStartDate,'dd/mm/yyyy'), 'MON'), ROWNUM - 1) date_out
FROM DUAL
CONNECT BY ADD_MONTHS(TRUNC(TO_DATE(:givenStartDate,'dd/mm/yyyy'), 'MON'), ROWNUM - 1)
<= TRUNC(TO_DATE(:givenEndDate,'dd/mm/yyyy'), 'MON')
)
select * from dates_between
This outputs each month between two dates and formats it to the start of the month. I just need another column to give me the fraction the start and end dates cover. I'm not sure of a way to do this without it getting messy.
The months_between() function "calculates the fractional portion of the result based on a 31-day month". That means that if your range starts or ends in a month that doesn't have 31 days, the fraction you get might not be quite what you expect:
select months_between(date '2017-04-02', date '2017-04-01') as calc from dual
CALC
----------
.0322580645
... which is 1/31, not 1/30. To get 0.0333... instead you'd need to calculate the number of days in each month, at least for the first and last month. This uses a recursive CTE (11gR2+) to get the months, using a couple of date ranges provided by another CTE as a demo to show the difference (you can use a hierarchical query too of course):
with ranges (id, start_date, end_date) as (
select 1, date '2017-01-15', date '2017-03-01' from dual
union all select 2, date '2017-01-31', date '2017-03-01' from dual
union all select 3, date '2017-02-28', date '2017-04-01' from dual
),
months (id, month_start, month_days, range_start, range_end) as (
select id,
trunc(start_date, 'MM'),
extract(day from last_day(start_date)),
start_date,
end_date
from ranges
union all
select id,
month_start + interval '1' month,
extract(day from last_day(month_start + interval '1' month)),
range_start,
range_end
from months
where month_start < range_end
)
select id,
to_char(month_start, 'YYYY-MM-DD') as month_start,
month_days,
case when month_start = trunc(range_start, 'MM')
then month_days - extract(day from range_start) + 1
when month_start = trunc(range_end, 'MM')
then extract(day from range_end)
else month_days end as range_days,
(case when month_start = trunc(range_start, 'MM')
then month_days - extract(day from range_start) + 1
when month_start = trunc(range_end, 'MM')
then extract(day from range_end)
else month_days end) / month_days as fraction
from months
order by id, month_start;
which gets:
ID MONTH_STAR MONTH_DAYS RANGE_DAYS FRACTION
------ ---------- ---------- ---------- --------
1 2017-01-01 31 17 0.5483
1 2017-02-01 28 28 1
1 2017-03-01 31 1 0.0322
2 2017-01-01 31 1 0.0322
2 2017-02-01 28 28 1
2 2017-03-01 31 1 0.0322
3 2017-02-01 28 1 0.0357
3 2017-03-01 31 31 1
3 2017-04-01 30 1 0.0333
The first CTE ranges is just the demo data. The second, recursive, CTE months generates the start and number of days in each month, while keeping track of the original range dates too. The final query just calculates the fractions based on the number of days in the month in the range against the number of days in that month overall.
The month_days and range_days are only shown in the output so you can see what the calculation is based on, you can obviously omit those from your actual result, and format the month start date however you want.
With your original single pair of bind variables the equivalent would be:
with months (month_start, month_days, range_start, range_end) as (
select trunc(to_date(:givenstartdate, 'DD/MM/YYYY'), 'MM'),
extract(day from last_day(to_date(:givenstartdate, 'DD/MM/YYYY'))),
to_date(:givenstartdate, 'DD/MM/YYYY'),
to_date(:givenenddate, 'DD/MM/YYYY')
from dual
union all
select month_start + interval '1' month,
extract(day from last_day(month_start + interval '1' month)),
range_start,
range_end
from months
where month_start < range_end
)
select to_char(month_start, 'MM/YYYY') as month,
(case when month_start = trunc(range_start, 'MM')
then month_days - extract(day from range_start) + 1
when month_start = trunc(range_end, 'MM')
then extract(day from range_end)
else month_days end) / month_days as fraction
from months
order by month_start;
MONTH FRACTION
------- --------
01/2017 0.5483
02/2017 1
03/2017 0.0322
Here's how I would do it (n.b. I have expanded your dates_between to work against multiple rows, purely for demonstration purposes. If you're only working with a single set of parameters, you wouldn't need to do that):
WITH params AS (SELECT 1 ID, '15/01/2017' givenstartdate, '01/03/2017' givenenddate FROM dual UNION ALL
SELECT 2 ID, '15/01/2017' givenstartdate, '23/01/2017' givenenddate FROM dual UNION ALL
SELECT 3 ID, '01/01/2017' givenstartdate, '07/04/2017' givenenddate FROM dual),
dates_between AS (SELECT ID,
to_date(givenstartdate, 'dd/mm/yyyy') givenstartdate,
to_date(givenenddate, 'dd/mm/yyyy') givenenddate,
add_months(trunc(to_date(givenstartdate, 'dd/mm/yyyy'), 'MON'), LEVEL - 1) start_of_month,
last_day(add_months(trunc(to_date(givenstartdate, 'dd/mm/yyyy'), 'MON'), LEVEL - 1)) end_of_month
FROM params
CONNECT BY add_months(trunc(to_date(givenstartdate, 'dd/mm/yyyy'), 'MON'), LEVEL - 1) <=
trunc(to_date(givenenddate, 'dd/mm/yyyy'), 'MON')
AND PRIOR ID = ID
AND PRIOR sys_guid() IS NOT NULL)
SELECT ID,
givenstartdate,
givenenddate,
start_of_month date_out,
end_of_month,
months_between(LEAST(givenenddate, end_of_month) + 1, GREATEST(start_of_month, givenstartdate))
FROM dates_between;
ID GIVENSTARTDATE GIVENENDDATE DATE_OUT END_OF_MONTH DIFF
1 15/01/2017 01/03/2017 01/01/2017 31/01/2017 0.54838709
1 15/01/2017 01/03/2017 01/02/2017 28/02/2017 1
1 15/01/2017 01/03/2017 01/03/2017 31/03/2017 0.03225806
2 15/01/2017 23/01/2017 01/01/2017 31/01/2017 0.29032258
3 01/01/2017 07/04/2017 01/01/2017 31/01/2017 1
3 01/01/2017 07/04/2017 01/02/2017 28/02/2017 1
3 01/01/2017 07/04/2017 01/03/2017 31/03/2017 1
3 01/01/2017 07/04/2017 01/04/2017 30/04/2017 0.22580645
N.B. You may need to add a case statement to decide whether you want to add 1 or not to the diff calculation, based on your requirements.
Try this
For first month, I have calculated remaining days / total days and for last month, I subtracted it by 1 to get days passed / total days.
DBFiddle Demo
WITH tbl AS
(SELECT date '2017-01-15' AS givenStartDate
,date '2017-03-01' AS givenEndDate
FROM dual
)
SELECT ADD_MONTHS(TRUNC(givenStartDate, 'MON'), ROWNUM - 1) AS date_out ,
CASE
WHEN
rownum - 1 = 0
THEN months_between(last_day(givenStartDate), givenStartDate)
WHEN ADD_MONTHS(TRUNC(givenStartDate, 'MON'), ROWNUM - 1) = TRUNC(givenEndDate, 'MON')
THEN 1 - (months_between(last_day(givenEndDate), givenEndDate))
ELSE 1
END AS perc
FROM tbl
CONNECT BY ADD_MONTHS(TRUNC(givenStartDate, 'MON'), ROWNUM - 1)
<= TRUNC(givenEndDate, 'MON');
Output
+-----------+-------------------------------------------+
| DATE_OUT | PERC |
+-----------+-------------------------------------------+
| 01-JAN-17 | .5161290322580645161290322580645161290323 |
| 01-FEB-17 | 1 |
| 01-MAR-17 | .0322580645161290322580645161290322580645 |
+-----------+-------------------------------------------+

fiscal year date sequence generation

I have a table called fiscal year with column start_date,end_date(empty table), I want to insert records for each fiscal year till 2060
FISCAL_YEAR Start dt is Jul 1st, End dt is Jun 31st of next year
what i tried
select add_months(start_date ,-6),add_months(start_date ,6)-1 from (
select to_date('20000101','yyyymmdd') start_date from dual )
basis
how do i generate this sequence till 2060
Decription start_date end_date
FISCAL YEAR 2000 7/1/1999 6/30/2000
SQL> select
2 to_date('01-07-' || (1999 + rownum), 'dd.mm.yyyy') start_date,
3 to_date('30-06-' || (2000 + rownum), 'dd.mm.yyyy') finish_date
4 from dual
5 connect by level <= 10;
START_DATE FINISH_DATE
----------- -----------
01.07.2000 30.06.2001
01.07.2001 30.06.2002
01.07.2002 30.06.2003
01.07.2003 30.06.2004
01.07.2004 30.06.2005
01.07.2005 30.06.2006
01.07.2006 30.06.2007
01.07.2007 30.06.2008
01.07.2008 30.06.2009
01.07.2009 30.06.2010
10 rows selected
You can do it like this:
SELECT
ADD_MONTHS(DATE '1999-07-01', 12*(LEVEL-1)) as fiscal_year_begin,
ADD_MONTHS(DATE '1999-07-01', 12*LEVEL) - INTERVAL '1' DAY AS fiscal_year_end
FROM dual
CONNECT BY LEVEL < 60;
FISCAL_YEAR_BEGIN FISCAL_YEAR_END
1999-07-01 2000-06-30
2000-07-01 2001-06-30
2001-07-01 2002-06-30
2002-07-01 2003-06-30
2003-07-01 2004-06-30
2004-07-01 2005-06-30
2005-07-01 2006-06-30
2006-07-01 2007-06-30
2007-07-01 2008-06-30
2008-07-01 2009-06-30
...