I would like to build a SQL statement to automatically retrieve information about all the weeks of a month.
For example, for the month of February 2023:
Retrieve the first week (Monday to Sunday): 30/01/2023 - 05/01/2023
...
Retrieve the last week :
27/02/2023 - 05/03/2023.
Thanks in advance!
Use a row-generator to generate a list of weeks for the month:
WITH input (month) AS (
SELECT DATE '2023-02-01' FROM DUAL
),
calendar (week_start) AS (
SELECT TRUNC(TRUNC(month, 'MM'), 'IW') + 7 * (LEVEL - 1)
FROM input
CONNECT BY TRUNC(TRUNC(month, 'MM'), 'IW') + 7 * (LEVEL - 1)
< ADD_MONTHS(TRUNC(month, 'MM'), 1)
)
SELECT week_start,
week_start + INTERVAL '6 23:59:59' DAY TO SECOND AS week_end
FROM calendar
Which outputs:
WEEK_START
WEEK_END
2023-01-30 00:00:00
2023-02-05 23:59:59
2023-02-06 00:00:00
2023-02-12 23:59:59
2023-02-13 00:00:00
2023-02-19 23:59:59
2023-02-20 00:00:00
2023-02-26 23:59:59
2023-02-27 00:00:00
2023-03-05 23:59:59
fiddle
Related
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
I need to select all dates, from current date, with a 30 days jump between each other in the past.
In example, today is October 18th, so I would need to get September 18th , August 19th, July 20th.
I cannot simply write the function current_date - 30, current_date - 60, current_date - 90 because the beginning of the data is far.
If you are using Oracle you can use below code -
WITH DATES(CUR_DATE, MON) AS (SELECT CURRENT_DATE CUR_DATE, 1 MON FROM DUAL
UNION ALL
SELECT D.CUR_DATE - 30, D.MON + 1
FROM DATES D
WHERE TO_CHAR(D.CUR_DATE, 'MM') > 1
AND TO_CHAR(D.CUR_DATE, 'YY') = TO_CHAR(SYSDATE, 'YY'))
SELECT * FROM DATES
Here is the fiddle. For other DBs please post your DBMS product.
If you are using Postgres this is quite easy using generate_series()
select d::date as "date"
from generate_series(current_date,
current_date - interval '1 year',
interval '-1 month') as t(d)
That returns the "last 12 months" starting from today.
If today is 2019-10-18, this returns:
date
----------
2019-10-18
2019-09-18
2019-08-18
2019-07-18
2019-06-18
2019-05-18
2019-04-18
2019-03-18
2019-02-18
2019-01-18
2018-12-18
2018-11-18
2018-10-18
If you want 30 days intervals (rather than 1 month), use:
select d::date as date
from generate_series(current_date,
current_date - interval '1 year',
interval '-30 day') as t(d)
For SQL Server, you can use the code below:
;WITH cte_Date30Days(n, d, dte)
AS (
SELECT 0, 0, current_timestamp as dte
UNION ALL
SELECT n+1, d - 30, dateadd(day, -30, dte)
FROM
cte_Date30Days
WHERE n < 7-1
)
SELECT dte FROM cte_Date30Days;
Only 7 rows are returned but change that value to suit your needs.
How do i find out the number of days in a previous month in Oracle
You can get the first day of the current month with trunc(sysdate, 'MM'), then subtract a month to get the first day of the previous month; then use last_day() to get the last day of that month; then extract() the day number from that:
select extract(day from last_day(add_months(trunc(sysdate, 'MM'), -1)))
from dual;
EXTRACT(DAYFROMLAST_DAY(ADD_MONTHS(TRUNC(SYSDATE,'MM'),-1)))
------------------------------------------------------------
31
Mostly just for fun, you can see the numbers that gets for say the last six months with a hierarchical query:
select last_day(add_months(trunc(sysdate, 'MM'), - level)) as last_date,
extract(day from last_day(add_months(trunc(sysdate, 'MM'), - level))) as last_day_number
from dual
connect by level <= 6;
LAST_DATE LAST_DAY_NUMBER
---------- ---------------
2019-03-31 31
2019-02-28 28
2019-01-31 31
2018-12-31 31
2018-11-30 30
2018-10-31 31
As #GordonLinoff pointed out, the trunc() call is a bit redundant - because add_months() is quite forgiving - so you can simplify further to:
select extract(day from last_day(add_months(sysdate, -1)))
from dual;
You could also use subtract an interval instead, but then you do need to truncate the current date first, as that won't like dates at the end of the month if the preceding month has fewer days:
select extract(day from last_day(trunc(sysdate, 'MM') - interval '1' month))
from dual;
Are you looking for this?
SELECT 1 + LAST_DAY(ADD_MONTHS(trunc(SYSDATE), -1)) - TRUNC(ADD_MONTHS(SYSDATE, -1), 'MM')
from dual;
31
I'm looking to return data in Oracle for the last full week starting Sunday and finishing Saturday. This needs to be able to run any day of the week.
So I know that this is possible in SQL Server as I have reports that do the exact same thing:-
SET #startdate = DATEADD(wk, -1, DATEADD(wk, DATEDIFF(wk, 0,getdate()), -1))
SET #enddate = DATEADD(wk, DATEDIFF(wk, 0, getdate()), -1)
Today being Friday 17th March the above would return data between Sunday 5th March and Saturday 11th March.
I want to do the same thing in Oracle. Everywhere I've looked so far comes back with results like this:-
SELECT TRUNC (SYSDATE) - (SELECT TO_CHAR (SYSDATE, 'D') FROM DUAL),
TRUNC (SYSDATE) - (SELECT TO_CHAR (SYSDATE, 'D') + 1 FROM DUAL)
FROM DUAL
Or
SELECT SYSDATE AS TODAYS_DATE,
NEXT_DAY (SYSDATE - 7, 'SAT') AS PREV_SATURDAY,
NEXT_DAY (SYSDATE - 7, 'SUN') AS PREV_SUNDAY
FROM DUAL
I'm trying to avoid any 'sysdate-7' type code since that's pretty unwieldy in this situation - can anyone help at all?
Thanks
If, at any given point in time, "previous week" means the seven-day period that ENDS on the most recent midnight at the beginning of a Sunday, then something like this should work:
with inputs (dt) as (
select sysdate from dual union all
select sysdate + 1 from dual union all
select sysdate + 2 from dual union all
select sysdate + 3 from dual
)
-- end of test data; SQL solution begins below this line
select to_char(dt, 'Dy dd-Mon-yyyy hh:mi AM') as dt,
trunc(dt + 1, 'iw') - 8 as prev_wk_start,
trunc(dt + 1, 'iw') - 1 as prev_wk_end
from inputs;
DT PREV_WK_START PREV_WK_END
------------------------ ------------------- -------------------
Fri 17-Mar-2017 10:58 AM 03/05/2017 00:00:00 03/12/2017 00:00:00
Sat 18-Mar-2017 10:58 AM 03/05/2017 00:00:00 03/12/2017 00:00:00
Sun 19-Mar-2017 10:58 AM 03/12/2017 00:00:00 03/19/2017 00:00:00
Mon 20-Mar-2017 10:58 AM 03/12/2017 00:00:00 03/19/2017 00:00:00
Note: Whenever we work with time intervals, we must decide if the endpoints are included. In most cases, the best (and most used) convention is that the start date/time is included, while the end date/time is NOT included. The query above is consistent with this interpretation. If the query is run for an input like date '2017-03-19', which is midnight at the beginning of a Sunday, the query will return the week that ENDS exactly at that date and time. All of this "previous week" strictly precedes the input date/time, because the end point of the week is NOT included in the "one-week interval."
Use TRUNC( date_value, 'IW' ) to do it independent of the NLS_TERRITORY or NLS_DATE_LANGUAGE session parameters:
SELECT SYSDATE AS TODAYS_DATE,
TRUNC( SYSDATE, 'IW' ) AS MONDAY_OF_THIS_ISO_WEEK,
TRUNC( SYSDATE, 'IW' ) - INTERVAL '2' DAY AS PREV_SATURDAY,
TRUNC( SYSDATE, 'IW' ) - INTERVAL '8' DAY AS PREV_SUNDAY
FROM DUAL;
Output:
TODAYS_DATE MONDAY_OF_THIS_ISO_ PREV_SATURDAY PREV_SUNDAY
------------------- ------------------- ------------------- -------------------
2017-03-17 15:45:25 2017-03-13 00:00:00 2017-03-11 00:00:00 2017-03-05 00:00:00
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
----------------------------------------------------------------------------------