This question already has answers here:
Generating dates between two dates
(5 answers)
Closed 24 days ago.
I have a table in which is stored such information:
id begin_date end_date
1 01.01.2023 27.01.2023
2 15.09.2023 30.09.2022
I am trying to write a SQL statement which will generate every week in this range of time,
for example the output should be:
id week_from_till
1 26.12.2022 - 01.01.2023
1 02.01.2023 - 08.01.2023
1 09.01.2023 - 15.01.2023
1 16.01.2023 - 22.01.2023
1 23.01.2023 - 29.01.2023
2 12.09.2022 - 18.09.2022
2 19.09.2022 - 25.09.2022
2 26.09.2022 - 02.10.2022
Here's one option.
Sample data:
SQL> with test (id, begin_date, end_date) as
2 (select 1, date '2023-01-01', date '2023-01-27' from dual union all
3 select 2, date '2022-09-15', date '2022-09-30' from dual
4 )
Query:
5 select id,
6 trunc(begin_date, 'iw') + (column_value - 1) * 7 week_from,
7 trunc(begin_date, 'iw') + (column_value - 1) * 7 + 6 week_to
8 from test cross join
9 table(cast(multiset(select level from dual
10 connect by level <= (trunc(end_date, 'iw') + 7
11 - trunc(begin_date, 'iw')) / 7
12 ) as sys.odcinumberlist))
13 order by 1, 2;
ID WEEK_FROM WEEK_TO
---------- ---------- ----------
1 26.12.2022 01.01.2023
1 02.01.2023 08.01.2023
1 09.01.2023 15.01.2023
1 16.01.2023 22.01.2023
1 23.01.2023 29.01.2023
2 12.09.2022 18.09.2022
2 19.09.2022 25.09.2022
2 26.09.2022 02.10.2022
8 rows selected.
SQL>
You asked whether it is possible to do it without sys.odcinumberlist. As usual, there are several ways to do the same. One other option is
<snip>
5 select id,
6 trunc(begin_date, 'iw') + (level - 1) * 7 week_from,
7 trunc(begin_date, 'iw') + (level - 1) * 7 + 6 week_to
8 from test
9 connect by level <= (trunc(end_date, 'iw') + 7
10 - trunc(begin_date, 'iw')) / 7
11 and prior id = id
12 and prior sys_guid() is not null
13 order by 1, 2;
ID WEEK_FROM WEEK_TO
---------- ---------- ----------
1 26.12.2022 01.01.2023
1 02.01.2023 08.01.2023
1 09.01.2023 15.01.2023
1 16.01.2023 22.01.2023
1 23.01.2023 29.01.2023
2 12.09.2022 18.09.2022
2 19.09.2022 25.09.2022
2 26.09.2022 02.10.2022
8 rows selected.
SQL>
Related
I'm working with Oracle and I have a table with a column of type TIMESTAMP. I was wondering how can I extract the records from last 4 weeks of activity on the database, partitioned by week.
Following rows are inserted on week 1
kc 2 04-10-2021
vc 3 06-10-2021
vk 4 07-10-2021
Following rows are inserted on week2
cv 1 12-10-2021
ck 5 14-10-2021
Following rows are inserted on week3
vv 7 19-10-2021
Following rows are inserted on week4
vx 7 29-10-2021
Table now has
SQL>select * from tab;
NAME VALUE TIMESTAMP
-------------------- ----------
kc 2 04-10-2021
vc 3 06-10-2021
vk 4 07-10-2021
cv 1 12-10-2021
ck 5 14-10-2021
vv 7 19-10-2021
vx 7 29-10-2021
I would like a query which would give me the number of rows added each week, in the last 4 weeks.
This is what I would like to see
numofrows week
--------- -----
3 1
2 2
1 3
1 4
One option is to use to_char function and its iw parameter:
SQL> with test (name, datum) as
2 (select 'kc', date '2021-10-04' from dual union all
3 select 'vc', date '2021-10-06' from dual union all
4 select 'vk', date '2021-10-07' from dual union all
5 select 'cv', date '2021-10-12' from dual union all
6 select 'ck', date '2021-10-14' from dual union all
7 select 'vv', date '2021-10-19' from dual union all
8 select 'vx', DATE '2021-10-29' from dual
9 )
10 select to_char(datum, 'iw') week,
11 count(*)
12 from test
13 where datum >= add_months(sysdate, -1) --> the last month
14 group by to_char(datum, 'iw');
WE COUNT(*)
-- ----------
42 1
43 1
40 3
41 2
SQL>
Line #13: I intentionally used "one month" instead of "4 weeks" as I thought (maybe wrongly) that you, actually, want that (you know, "a month has 4 weeks" - not exactly, but close, sometimes not close enough).
If you want 4 weeks, what is that, then? Sysdate minus 28 days (as every week has 7 days)? Then you'd modify line #13 to
where datum >= trunc(sysdate - 4*7)
Or, maybe it is really the last 4 weeks:
SQL> with test (name, datum) as
2 (select 'kc', date '2021-10-04' from dual union all
3 select 'vc', date '2021-10-06' from dual union all
4 select 'vk', date '2021-10-07' from dual union all
5 select 'cv', date '2021-10-12' from dual union all
6 select 'ck', date '2021-10-14' from dual union all
7 select 'vv', date '2021-10-19' from dual union all
8 select 'vx', DATE '2021-10-29' from dual
9 ),
10 temp as
11 (select to_char(datum, 'iw') week,
12 count(*) cnt,
13 row_number() over (order by to_char(datum, 'iw') desc) rn
14 from test
15 group by to_char(datum, 'iw')
16 )
17 select week, cnt
18 from temp
19 where rn <= 4
20 order by week;
WE CNT
-- ----------
40 3
41 2
42 1
43 1
SQL>
Now you have several options, see which one fits the best (if any).
I "simulated" missing data (see TEST CTE), created a calendar (calend) and ... did the job. Read comments within code:
SQL> with test (name, datum) as
2 -- sample data
3 (select 'vv', date '2021-10-19' from dual union all
4 select 'vx', DATE '2021-10-29' from dual
5 ),
6 calend as
7 -- the last 31 days; 4 weeks are included, obviously
8 (select max_datum - level + 1 datum
9 from (select max(a.datum) max_datum from test a)
10 connect by level <= 31
11 ),
12 joined as
13 -- joined TEST and CALEND data
14 (select to_char(c.datum, 'iw') week,
15 t.name
16 from calend c left join test t on t.datum = c.datum
17 ),
18 last4 as
19 -- last 4 weeks
20 (select week, count(name) cnt,
21 row_number() over (order by week desc) rn
22 from joined
23 group by week
24 )
25 select week, cnt
26 from last4
27 where rn <= 4
28 order by week;
WE CNT
-- ----------
40 0
41 0
42 1
43 1
SQL>
name id
----------------
Mon 1
Thu 2
Wen 3
Thr 4
Fri 5
Sat 6
San 7
How get count day where id in eg. (1,2,3,4) and year is 2021
The result should be 208.
Actually, it is 208 for year 2021.
SQL> WITH
2 year AS (SELECT &par_year year FROM DUAL),
3 calendar
4 AS
5 ( SELECT TRUNC (TO_DATE (y.year, 'yyyy'), 'yyyy') + LEVEL - 1 datum
6 FROM year y
7 CONNECT BY LEVEL <=
8 ADD_MONTHS (TRUNC (TO_DATE (y.year, 'yyyy'), 'yyyy'), 12)
9 - TRUNC (TO_DATE (y.year, 'yyyy'), 'yyyy'))
10 SELECT SUM (CASE
11 WHEN TO_NUMBER (TO_CHAR (c.datum, 'd')) IN (1,
12 2,
13 3,
14 4)
15 THEN
16 1
17 ELSE
18 0
19 END) result
20 FROM calendar c;
Enter value for par_year: 2021
RESULT
----------
208
SQL> /
Enter value for par_year: 2020
RESULT
----------
210
SQL>
What does it do?
YEAR CTE contains year you're interested in
CALENDAR CTE creates all dates in that particular year
SUM function conditionally adds 1 if TO_CHAR(datum, 'd') is 1, 2, 3 or 4
that's all
I need to run this query to get 5 years data (2015 to 2019), and I am wondering if there is a way to automatically loop through year, instead of manually changing the year (e.g., from 2015 to 2016) and running this query 5 times? Any help will be appreciated! Thanks!
Select ID,program, open_date, close_date
From clients
Where open_date=to_date('01/01/2015','mm/dd/yyyy')
and close_date=to_date('12/31/2015','mm/dd/yyyy')
You can always generate calendar you need and join it with your query.
with cal as (
select add_months(date '2015-01-01', (level - 1)*12) as start_dt,
add_months(date '2015-12-31', (level - 1)*12) as end_dt
from dual
connect by level <= 5
)
Select c.ID, c.program, c.open_date, c.close_date
From clients c
join cal
on c.open_date=cal.start_dt and c.close_date=cal.end_dt
Row generator it is. For example:
SQL> with period (start_year, end_year) as
2 (select 2015, 2020 from dual)
3 select d.dummy, p.start_year + level - 1 as year
4 from dual d cross join period p
5 connect by level <= end_year - start_year
6 order by year;
D YEAR
- ----------
X 2015
X 2016
X 2017
X 2018
X 2019
SQL>
Applied to your code (can't test it, don't have your tables):
with
period (start_year, end_year) as
(select 2015, 2020 from dual),
select c.id, c.program, c.open_date, c.close_date
from clients c cross join period p
where open_date = add_months(trunc(to_date(p.start_year, 'yyyy'), 'yyyy'), 12 * (level - 1))
and close_date = add_months(trunc(to_date(p.start_year, 'yyyy'), 'yyyy'), 12 * (level )) - 1
connect by level <= p.end_year - p.start_year;
because those values produce
SQL> with
2 period (start_year, end_year) as
3 (select 2015, 2020 from dual)
4 select add_months(trunc(to_date(p.start_year, 'yyyy'), 'yyyy'), 12 * (level - 1)) a,
5 add_months(trunc(to_date(p.start_year, 'yyyy'), 'yyyy'), 12 * (level )) - 1 b
6 from period p
7 connect by level <= end_year - start_year;
A B
---------- ----------
01.01.2015 31.12.2015
01.01.2016 31.12.2016
01.01.2017 31.12.2017
01.01.2018 31.12.2018
01.01.2019 31.12.2019
SQL>
Take a look at the following code snippet:
SELECT EXTRACT(YEAR FROM TO_DATE('01.01.2015', 'dd.mm.yyyy')) + ROWNUM - 1 AS "YEAR"
FROM dual
CONNECT BY ROWNUM <= 5
There is a table on the payment schedule:
| PaySum | PlanDate |
+----------+------------+
| 23928.38 | 14.10.2019 |
| 24347.13 | 12.11.2019 |
| 24773.20 | 12.12.2020 |
| 25206.73 | 13.01.2020 |
Need to pull forthcoming amount for 3 months
My request for example:
select sum(s.PaySum)
from L_DEA s
where s.PlanDate between trunc(sysdate + 1) and
ADD_months(trunc(sysdate + 1), 3)
and ID = :iId;
This query returns 4 months if run sysdate = 12.10.19 or 13.10.19
in other cases shows correctly for 3 months
How to form select correctly
Perhaps try changing the inequality:
SELECT SUM(s.PaySum)
FROM L_DEA s
WHERE
s.PlanDate >= TRUNC(SYSDATE) AND
s.PlanDate < ADD_MONTHS(TRUNC(SYSDATE + 1), 3) AND
ID = :iId;
This would include all plan dates on or after midnight of today, but before midnight of three months from now.
I don't agree with you. At least in the given sample data, It is working correctly.
For 12-Oct-2019
SQL> with L_DEA (PaySum, PlanDate) as
2 (select 23928.38 , to_date('14.10.2019','dd.mm.yyyy') from dual union all
3 select 24347.13 , to_date('12.11.2019','dd.mm.yyyy') from dual union all
4 select 24773.20 , to_date('12.12.2020','dd.mm.yyyy') from dual union all
5 select 24773.20 , to_date('10.02.2020','dd.mm.yyyy') from dual union all -- added this
6 select 25206.73 , to_date('13.01.2020','dd.mm.yyyy') from dual )
7 select * --sum(s.PaySum)
8 from L_DEA s
9 where s.PlanDate between trunc(date '2019-10-12' + 1) and
10 ADD_months(trunc(date '2019-10-12' + 1), 3)
11 --and ID = :iId;
PAYSUM PLANDATE
---------- ---------
23928.38 14-OCT-19
24347.13 12-NOV-19
25206.73 13-JAN-20
SQL>
for another date i.e. 13-Oct-2019
SQL> with L_DEA (PaySum, PlanDate) as
2 (select 23928.38 , to_date('14.10.2019','dd.mm.yyyy') from dual union all
3 select 24347.13 , to_date('12.11.2019','dd.mm.yyyy') from dual union all
4 select 24773.20 , to_date('12.12.2020','dd.mm.yyyy') from dual union all
5 select 24773.20 , to_date('10.02.2020','dd.mm.yyyy') from dual union all -- added this
6 select 25206.73 , to_date('13.01.2020','dd.mm.yyyy') from dual )
7 select * --sum(s.PaySum)
8 from L_DEA s
9 where s.PlanDate between trunc(date '2019-10-13' + 1) and
10 ADD_months(trunc(date '2019-10-13' + 1), 3)
11 --and ID = :iId;
PAYSUM PLANDATE
---------- ---------
23928.38 14-OCT-19
24347.13 12-NOV-19
25206.73 13-JAN-20
SQL>
Cheers!!
One abbreviate option to cover the period starting from the present day to three months later would be :
select sum(s.PaySum)
from l_dea s
where s.PlanDate between trunc(systimestamp) and trunc(systimestamp) + interval '3' month
and ID = :iId
select s.PlanDate, s.PaySum
from L_DEA s
inner join (select PlanDate
from (select distinct s.PlanDate
from L_DEA s
where ID = :iId
and s.PlanDate >= (to_date('10.10.2019'))
order by s.PlanDate) tt
where rownum <= 3) A2
on s.PlanDate = A2.PlanDate
where ID = :iId);
I have to generate date range for 1 year in gap of 4 weeks ( or 28 days ) from a fixed date backward and going forward. For example I have DATE '2016-02-20'. I need to generate the below.
Start date = Sunday , End-date = Saturday
No Start_date End_date
==== ========= =======
1 1/24/2016 2/20/2016
2 12/27/2015 1/23/2016
3 11/29/2015 12/26/2015
4 .....
13 2/22/2015 3/21/2015
14 1/25/2015 2/21/2015
But,when 03/20/2016(Sunday) comes,it should add
1. 2/21/2016 3/19/2016 & remove 14. 1/25/2015 2/21/2015
and so on after every 4 weeks.
I have written the below, but I need help to iterate in minimal code( if possible.)
SELECT LEVEL,
DATE '2016-02-20'-27*LEVEL-LEVEL+1 AS start_date,
DATE '2016-02-20'-28*(LEVEL-1) AS end_date
FROM DUAL
Connect BY LEVEL < 15;
It seems you want to have a rolling window of a year's worth of four-week ranges, based from the current date. To do that you need a fixed known period start (or end) date you can work from. Picking one that happens to be January 1st you can do:
SELECT DATE '2012-01-01' + (28 * (LEVEL - 1)) AS start_date,
DATE '2012-01-01' + (28 * LEVEL) - 1 AS end_date
FROM DUAL
CONNECT BY DATE '2012-01-01' + (28 * LEVEL) - 1 <= TRUNC(sysdate)
Which will find 54 periods up to today. On March 21st it will find 55 periods, etc. You only want those that are in the last year, so use that as an inline view and restrict the range:
SELECT ROW_NUMBER() OVER (ORDER BY start_date DESC) AS no, start_date, end_date
FROM (
SELECT DATE '2012-01-01' + (28 * (LEVEL - 1)) AS start_date,
DATE '2012-01-01' + (28 * LEVEL) - 1 AS end_date
FROM DUAL
CONNECT BY DATE '2012-01-01' + (28 * LEVEL) - 1 <= TRUNC(sysdate)
)
WHERE end_date >= ADD_MONTHS(TRUNC(sysdate), -12)
ORDER BY start_date DESC;
NO START_DATE END_DATE
---------- ---------- ----------
1 01/24/2016 02/20/2016
2 12/27/2015 01/23/2016
3 11/29/2015 12/26/2015
...
11 04/19/2015 05/16/2015
12 03/22/2015 04/18/2015
13 02/22/2015 03/21/2015
The ROW_NUMBER() just generates your NO column, as the LEVEL is now in the wrong order.
If you always want exactly 14 rows in the result set you can move the ROW_NUMBER() into the inline view:
SELECT no, start_date, end_date
FROM (
SELECT ROW_NUMBER() OVER (ORDER BY LEVEL DESC) AS no,
DATE '2012-01-01' + (28 * (LEVEL - 1)) AS start_date,
DATE '2012-01-01' + (28 * LEVEL) - 1 AS end_date
FROM DUAL
CONNECT BY DATE '2012-01-01' + (28 * LEVEL) - 1 <= TRUNC(sysdate)
)
WHERE no <= 14
ORDER BY no;
NO START_DATE END_DATE
---------- ---------- ----------
1 01/24/2016 02/20/2016
2 12/27/2015 01/23/2016
3 11/29/2015 12/26/2015
...
12 03/22/2015 04/18/2015
13 02/22/2015 03/21/2015
14 01/25/2015 02/21/2015
14 rows selected