I need to populate a calendar table in a Oracle 11g database with the following:
calendar_date,
week_number,
week_year,
day_since_2000,
week_since_2000,
month_since_2000,
quarter_since_2000,
week_of_month
The table is already populated with data since 2000 I now need to populate it going back to 1994 with negative numbers for the since_2000 columns. I think I have everything figured but for week since 2000 and quarter since 2000.
EDIT1: Just noticed my week since 200 and month since 2000 is still messed up. Quarter since 2000 looks good after using the answer mentioned below. I am updating the query with the latest.
EDIT2: It was not working because of the missing trunc(). Its working fine now. Latest query updated.
This is what I am using to do that:
/* Formatted on 2/1/2013 11:54:27 AM (QP5 v5.227.12220.39724) */
CREATE OR REPLACE PROCEDURE populate_d_calendar (start_date IN DATE,
end_date IN DATE)
AS
BEGIN
INSERT INTO d_calendar (calendar_date,
week_number,
week_year,
day_since_2000,
week_since_2000,
month_since_2000,
quarter_since_2000,
week_of_month)
SELECT dt, --Date
TO_CHAR (dt,
'ww'), --Week in the year
TO_CHAR (dt,
'yyyy'), --Year
TO_CHAR (dt - end_date), --Day Since 2000
TRUNC (TO_CHAR (dt - end_date) / 7), --Week Since 2000
TRUNC (MONTHS_BETWEEN (dt,
end_date)), --Month Since 2000
CASE
WHEN TO_CHAR (dt,
'MMDD') >= '0101'
AND TO_CHAR (dt,
'MMDD') < '0401'
THEN
0
+ 4
* TRUNC ( MONTHS_BETWEEN (TO_DATE ( '0101'
|| TO_CHAR (dt,
'yyyy'),
'mmddyyyy'),
TO_DATE ('01Jun2000'))
/ 12) --q1
WHEN TO_CHAR (dt,
'MMDD') >= '0401'
AND TO_CHAR (dt,
'MMDD') < '0701'
THEN
1
+ 4
* TRUNC ( MONTHS_BETWEEN (TO_DATE ( '0101'
|| TO_CHAR (dt,
'yyyy'),
'mmddyyyy'),
TO_DATE ('01Jun2000'))
/ 12) --q2
WHEN TO_CHAR (dt,
'MMDD') >= '0701'
AND TO_CHAR (dt,
'MMDD') < '1001'
THEN
2
+ 4
* TRUNC ( MONTHS_BETWEEN (TO_DATE ( '0101'
|| TO_CHAR (dt,
'yyyy'),
'mmddyyyy'),
TO_DATE ('01Jun2000'))
/ 12) --q3
WHEN TO_CHAR (dt,
'MMDD') >= '1001'
AND TO_CHAR (dt,
'MMDD') <= '1231'
THEN
3
+ 4
* TRUNC ( MONTHS_BETWEEN (TO_DATE ( '0101'
|| TO_CHAR (dt,
'yyyy'),
'mmddyyyy'),
TO_DATE ('01Jun2000'))
/ 12) --q4
END
quarters_since_2000, --Quarter Since 2000
TO_CHAR (dt,
'w') --Week of the month
FROM (SELECT start_date + LEVEL - 1 dt
FROM DUAL
CONNECT BY LEVEL <= end_date - start_date + 1);
END;
/
i think you're after a floating point like with the others so maybe:
TO_CHAR (dt - end_date)/7 week_since_2000
and
(MONTHS_BETWEEN (dt,
end_date)/3) quarter_since_2000
with weeks you can follow one of the methods here:
http://searchoracle.techtarget.com/answer/Calculating-weeks-between-two-dates
because it will depend on which rules are more useful to you
with quarters, use this:
case
WHEN TO_CHAR (dt,'MMDD') >= '0101' and TO_CHAR (dt,'MMDD') < '0401' then 0 + 4 * trunc(months_between( to_date('0101' || to_char(dt,'yyyy'),'mmddyyyy'),to_date('01Jun2000' )) /12) --q1
WHEN TO_CHAR (dt,'MMDD') >= '0401' and TO_CHAR (dt,'MMDD') < '0701' then 1 + 4 * trunc(months_between( to_date('0101' || to_char(dt,'yyyy'),'mmddyyyy'),to_date('01Jun2000' )) /12) --q2
WHEN TO_CHAR (dt,'MMDD') >= '0701' and TO_CHAR (dt,'MMDD') < '1001' then 2 + 4 * trunc(months_between( to_date('0101' || to_char(dt,'yyyy'),'mmddyyyy'),to_date('01Jun2000' )) /12) --q3
WHEN TO_CHAR (dt,'MMDD') >= '1001' and TO_CHAR (dt,'MMDD') <= '1231' then 3 + 4 * trunc(months_between( to_date('0101' || to_char(dt,'yyyy'),'mmddyyyy'),to_date('01Jun2000' )) /12) --q4
end quarters_since_2000,
This is my version of calendar table from 1/1/2013 till today. I'm not sure how do you need to insert number of months, quarters, days etc... You need to display total number of months between every year or once? You can add those calc yourself or add desired output in your post to make it clear. See addl queries/comments below:
-- Days,weeks, quarters from 1/1/2013 --
SELECT start_date -- 1/1/2013 --
, TRUNC(start_date, 'iw') wk_starts
, TRUNC(start_date, 'iw') + 7 - 1/86400 wk_ends
, TO_NUMBER (TO_CHAR (start_date, 'IW')) ISO_wk#
, TO_NUMBER (TO_CHAR (start_date, 'Q')) Quarters
FROM
(
SELECT TRUNC(SYSDATE, 'Y')-1 + LEVEL AS start_date
FROM dual
CONNECT BY LEVEL <=
( -- replace this part to go back to 1994 - see below --
SELECT TRUNC(ADD_MONTHS (SYSDATE, 12), 'Y')-TRUNC(SYSDATE, 'Y') "Num of Days in 2013"
FROM dual
)
)
/
START_DATE WK_STARTS WK_ENDS ISO_WK# QUARTERS
---------------------------------------------------------------------
1/1/2013 12/31/2012 1/6/2013 11:59:59 PM 1 1
1/2/2013 12/31/2012 1/6/2013 11:59:59 PM 1 1
......
2/19/2013 2/18/2013 2/24/2013 11:59:59 PM 8 1
2/20/2013 2/18/2013 2/24/2013 11:59:59 PM 8 1
......
3/10/2013 3/4/2013 3/10/2013 11:59:59 PM 10 1
3/11/2013 3/11/2013 3/17/2013 11:59:59 PM 11 1
.......
-- 6971 days from 1/1/1994 till today - 2/1/2013 --
SELECT TRUNC(SYSDATE) - TRUNC(ADD_MONTHS (SYSDATE, -12*19), 'Y') "Num of Days from 1994"
FROM dual
/
-- 4780 days from 1/1/2000 till today - 2/1/2013 --
SELECT TRUNC(SYSDATE) - TRUNC(ADD_MONTHS (SYSDATE, -12*13), 'Y') "Num of Days from 2000"
FROM dual
/
-- 156 Months between from 2000 toll today --
SELECT MONTHS_BETWEEN(TRUNC(SYSDATE), TRUNC(ADD_MONTHS (SYSDATE, -12*13))) mo_btw_2000_till_today
FROM dual
/
-- Number of weeks btw. 2 dates --
SELECT to_char(sysdate, 'WW') - to_char(sysdate-30, 'WW') number_of_weeks FROM dual
/
This may not be exactly what you want... To help you more I need to see the actual expected output of your query. Just trying to help...
Related
I need to get the last 3 full months of data so March, April, and May in this case. This almost gives me what I want but it includes June 1st which I don't need. I need March 1st through May 31st.
Oracle SQL:
where d.D_DATE between add_months(trunc(sysdate, 'month'), -3) and add_months(trunc(sysdate, 'month'),0)
Use >= and < rather than BETWEEN:
SELECT *
FROM d
WHERE d.D_DATE >= ADD_MONTHS(TRUNC(SYSDATE, 'MM'), -3)
AND d.D_DATE < TRUNC(SYSDATE, 'MM')
Which, for the sample data:
CREATE TABLE d (D_DATE) AS
SELECT ADD_MONTHS(TRUNC(SYSDATE, 'MM'), -3) - INTERVAL '1' SECOND FROM DUAL UNION ALL
SELECT ADD_MONTHS(TRUNC(SYSDATE, 'MM'), -3) FROM DUAL UNION ALL
SELECT TRUNC(SYSDATE, 'MM') - INTERVAL '1' DAY FROM DUAL UNION ALL
SELECT TRUNC(SYSDATE, 'MM') - INTERVAL '1' DAY FROM DUAL UNION ALL
SELECT TRUNC(SYSDATE, 'MM') - INTERVAL '1' DAY + INTERVAL '1' SECOND FROM DUAL UNION ALL
SELECT TRUNC(SYSDATE, 'MM') - INTERVAL '1' SECOND FROM DUAL UNION ALL
SELECT TRUNC(SYSDATE, 'MM') FROM DUAL;
Outputs:
D_DATE
2022-03-01 00:00:00
2022-05-31 00:00:00
2022-05-31 00:00:00
2022-05-31 00:00:01
2022-05-31 23:59:59
db<>fiddle here
Got it. where d.D_DATE between add_months(trunc(sysdate, 'month'), -3) and last_day(add_months(trunc(sysdate, 'month'),-1))
How can I count the days of each week for a specific month that the user will give to my SQL query? For example, if the user gives April 2021, the result will be:
If the user gives May 2021 the result will be:
Here is a direct computation of the same. The input is given as a string, such as May 2021; you can use a bind variable in its place. Just keep in mind the possibility that the user may be in a non-English-speaking locale; as long as they use their local language in passing the month to the query, everything should work fine.
with
inputs (mth) as (select 'May 2021' from dual)
, first_day (dt) as (select to_date(mth, 'fmMonth yyyy') from inputs)
, mondays (dt, ord, lst) as (
select trunc(dt, 'iw') + 7 * (level - 1), level, max(level) over ()
from first_day
connect by level <= 1 + (trunc(add_months(dt, 1), 'iw') - trunc(dt, 'iw')) / 7
)
select to_number(to_char(dt, 'iw')) as week_number,
case ord when 1 then dt + 7 - trunc(dt + 7, 'mm')
when lst then last_day(dt) + 1 - dt
else 7 end as week_days
from mondays
order by week_number
;
WEEK_NUMBER WEEK_DAYS
----------- ----------
17 2
18 7
19 7
20 7
21 7
22 1
I think this is the query you are looking for:
SELECT WEEK AS WEEK_NUMBER, COUNT(*) AS WEEK_DAYS
FROM (SELECT TO_CHAR(FIRST_DAY + (LEVEL-1), 'IW') AS WEEK
FROM (SELECT supplied_date AS FIRST_DAY, LAST_DAY(supplied_date) - SUPPLIED_DATE+1 AS DAYS
FROM (SELECT TO_DATE('05/2021', 'MM/YYYY') AS SUPPLIED_DATE
FROM DUAL))
CONNECT BY LEVEL <= DAYS)
GROUP BY WEEK
ORDER BY WEEK;
Just replace the inner TO_DATE with your date.
Edit 1: Additional Column (See Comments)
SELECT MONTH_NAME, WEEK AS WEEK_NUMBER, COUNT(*) AS WEEK_DAYS
FROM (SELECT TO_CHAR(FIRST_DAY, 'MONTH') AS MONTH_NAME, TO_CHAR(FIRST_DAY + (LEVEL-1), 'IW') AS WEEK
FROM (SELECT supplied_date AS FIRST_DAY, TO_CHAR(LAST_DAY(supplied_date), 'DD') AS DAYS
FROM (SELECT TO_DATE('05/2021', 'MM/YYYY') AS SUPPLIED_DATE
FROM DUAL))
CONNECT BY LEVEL <= DAYS)
GROUP BY MONTH_NAME, WEEK
ORDER BY WEEK;
Note: I also changed the way I calculate the number of days in the month.
You can generate the weeks (without needing to use any aggregation) using:
WITH input ( month ) AS (
SELECT DATE '2021-04-01' FROM DUAL
),
weeks_of_month ( month_start, week_start, end_day ) AS (
SELECT TRUNC( month, 'MM' ),
TRUNC( month, 'IW' ),
LAST_DAY( TRUNC( month ) )
FROM input
UNION ALL
SELECT month_start,
week_start + INTERVAL '7' DAY,
end_day
FROM weeks_of_month
WHERE week_start + INTERVAL '7' DAY <= end_day
)
SELECT TO_CHAR( week_start, 'IW' ) AS iso_week,
GREATEST( month_start, week_start ) AS first_day_of_week,
LEAST( end_day, week_start + INTERVAL '6' DAY ) AS last_day_of_week,
LEAST( end_day + 1, week_start + INTERVAL '7' DAY )
- GREATEST( month_start, week_start ) AS days
FROM weeks_of_month;
Which outputs (with the NLS_DATE_FORMAT set to YYYY-MM-DD (DY)):
ISO_WEEK
FIRST_DAY_OF_WEEK
LAST_DAY_OF_WEEK
DAYS
13
2021-04-01 (THU)
2021-04-04 (SUN)
4
14
2021-04-05 (MON)
2021-04-11 (SUN)
7
15
2021-04-12 (MON)
2021-04-18 (SUN)
7
16
2021-04-19 (MON)
2021-04-25 (SUN)
7
17
2021-04-26 (MON)
2021-04-30 (FRI)
5
db<>fiddle here
I am trying to achieve a query that returns the time difference between two dates excluding weekends(Saturday and Sunday) and excluding time (6 pm-9 am).
For now, I have a function that is excluding the weekends, But I am unable to exclude time from the query. Can anyone help with this?
The article from which I take help is this
CREATE OR REPLACE FUNCTION get_bus_minutes_between(
p_start_date DATE,
p_end_date DATE
)
RETURN NUMBER
DETERMINISTIC -- ***** Can't hurt
IS
days_diff NUMBER := 0;
end_date DATE := p_end_date;
minutes_diff NUMBER;
start_date DATE := p_start_date;
weeks_diff NUMBER;
BEGIN
IF start_date <= end_date
THEN
-- Move start_date and end_date away from weekends
IF start_date > TRUNC (start_date, 'IW') + 5
THEN -- Use next Monday for start_date
start_date := TRUNC (start_date, 'IW') + 7;
END IF;
IF end_date > TRUNC (end_date, 'IW') + 5
THEN -- Use Friday quitting time
end_date := TRUNC (end_date, 'IW') + 4 + (16.5 / 24);
END IF;
-- Move start_date into the same weeek as end_date
-- (Remember how many weeks we had to move it)
weeks_diff := ( TRUNC (end_date, 'IW')
- TRUNC (start_date, 'IW')
) / 7;
IF weeks_diff > 0
THEN
start_date := start_date + (7 * weeks_diff);
END IF;
-- Make start_date the same day as end_date
-- (Remember how many days we had to move it)
days_diff := TRUNC (end_date) - TRUNC (start_date);
IF days_diff > 0
THEN
start_date := start_date + days_diff;
END IF;
-- Move start_date up to starting time
start_date := GREATEST ( start_date
, TRUNC (start_date) + (8.75 / 24)
);
-- Move end_date back to quitting time
end_date := LEAST ( end_date
, TRUNC (end_date) + ( CASE
WHEN TO_CHAR ( end_date
, 'DY'
, 'NLS_DATE_LANGUAGE=ENGLISH'
) = 'FRI'
THEN 16.5
ELSE 17
END
/ 24
)
);
minutes_diff := ( GREATEST ( 0
, end_date - start_date
)
* 24 * 60
)
+ (days_diff * 495) -- 495 minutes per full day (Mon.-Thu.)
+ (weeks_diff * 2445); -- 2445 minutes per full week
ELSIF start_date > end_date
THEN
minutes_diff := -get_bus_minutes_between (end_date, start_date);
ELSE -- One of the arguments was NULL
minutes_diff := NULL;
END IF;
RETURN ROUND(minutes_diff);
END get_bus_minutes_between;
You can directly calculate the difference in days (adapted from my answer here):
SELECT start_date,
end_date,
ROUND(
(
-- Calculate the full weeks difference from the start of ISO weeks.
( TRUNC( end_date, 'IW' ) - TRUNC( start_date, 'IW' ) ) * (9/24) * (5/7)
-- Add the full days for the final week.
+ LEAST( TRUNC( end_date ) - TRUNC( end_date, 'IW' ), 5 ) * (9/24)
-- Subtract the full days from the days of the week before the start date.
- LEAST( TRUNC( start_date ) - TRUNC( start_date, 'IW' ), 5 ) * (9/24)
-- Add the hours of the final day
+ LEAST( GREATEST( end_date - TRUNC( end_date ) - 9/24, 0 ), 9/24 )
-- Subtract the hours of the day before the range starts.
- LEAST( GREATEST( start_date - TRUNC( start_date ) - 9/24, 0 ), 9/24 )
)
-- Multiply to give minutes rather than fractions of full days.
* 24 * 60
) AS work_day_mins_diff
FROM table_name;
Which, for the sample data:
CREATE TABLE table_name ( start_date, end_date ) AS
SELECT DATE '2020-12-30' + INTERVAL '00' HOUR, DATE '2020-12-30' + INTERVAL '12' HOUR FROM DUAL UNION ALL
SELECT DATE '2020-12-30' + INTERVAL '18' HOUR, DATE '2020-12-30' + INTERVAL '20' HOUR FROM DUAL UNION ALL
SELECT DATE '2020-12-30' + INTERVAL '17:30' HOUR TO MINUTE, DATE '2020-12-30' + INTERVAL '21:30' HOUR TO MINUTE FROM DUAL UNION ALL
SELECT DATE '2021-01-01' + INTERVAL '00' HOUR, DATE '2021-01-04' + INTERVAL '00' HOUR FROM DUAL UNION ALL
SELECT DATE '2021-01-02' + INTERVAL '00' HOUR, DATE '2021-01-04' + INTERVAL '00' HOUR FROM DUAL UNION ALL
SELECT DATE '2020-12-28' + INTERVAL '00' HOUR, DATE '2021-01-04' + INTERVAL '00' HOUR FROM DUAL UNION ALL
SELECT DATE '2020-12-28' + INTERVAL '00' HOUR, DATE '2020-12-29' + INTERVAL '00' HOUR FROM DUAL;
Outputs:
(Using ALTER SESSION SET NLS_DATE_FORMAT = 'YYYY-MM-DD HH24:MI:SS (DY)';)
START_DATE | END_DATE | WORK_DAY_MINS_DIFF
:------------------------ | :------------------------ | -----------------:
2020-12-30 00:00:00 (WED) | 2020-12-30 12:00:00 (WED) | 180
2020-12-30 18:00:00 (WED) | 2020-12-30 20:00:00 (WED) | 0
2020-12-30 17:30:00 (WED) | 2020-12-30 21:30:00 (WED) | 30
2021-01-01 00:00:00 (FRI) | 2021-01-04 00:00:00 (MON) | 540
2021-01-02 00:00:00 (SAT) | 2021-01-04 00:00:00 (MON) | 0
2020-12-28 00:00:00 (MON) | 2021-01-04 00:00:00 (MON) | 2700
2020-12-28 00:00:00 (MON) | 2020-12-29 00:00:00 (TUE) | 540
db<>fiddle here
I need to return the time between two dates in Oracle except the time during the weekends, I could return the minute. But when I set a weekend date, I receive a null result instead of the remaining time in workweek.
First, we need to create a function:
CREATE OR REPLACE FUNCTION get_bus_minutes_between (start_dt DATE, end_dt DATE)
RETURN NUMBER
IS
v_return NUMBER;
BEGIN
select sum(greatest(end_dt - start_dt,0)) * 24 * 60 work_minutes
into v_return
from dual
where trunc(start_dt) - trunc(start_dt,'iw') < 5; -- exclude weekends
RETURN v_return;
END;
Case 1 - Return the minutes in the workweek - Ok
Starting and ending in the workweek.
SELECT
"GET_BUS_MINUTES_BETWEEN"(TO_DATE('14-09-2020 06:00:00', 'dd-mm-yyyy hh24:mi:ss'),
TO_DATE('14-09-2020 10:00:00', 'dd-mm-yyyy hh24:mi:ss')) "WORK_MINUTES"
FROM
"SYS"."DUAL";
Case 2 - Return the remaining minutes in the workweek - Fail
Starting at the weekend and ending in workweek.
SELECT
"GET_BUS_MINUTES_BETWEEN"(TO_DATE('13-09-2020 06:00:00', 'dd-mm-yyyy hh24:mi:ss'),
TO_DATE('14-09-2020 10:00:00', 'dd-mm-yyyy hh24:mi:ss')) "WORK_MINUTES"
FROM
"SYS"."DUAL";
13-09-2020 is Sunday, therefore I was expected the return as 600 minutes related the Monday.
In these possibilities, we can start at the workweek and end at weekend.
You don't need to use SQL or a row generator and can do it with a simple calculation using only PL/SQL. Adapted from my answers here and here.
CREATE OR REPLACE FUNCTION get_bus_minutes_between (start_dt DATE, end_dt DATE)
RETURN NUMBER
IS
p_start_date DATE;
p_end_date DATE;
p_working_days NUMBER;
BEGIN
IF start_dt IS NULL OR end_dt IS NULL THEN
RETURN NUll;
END IF;
-- Enforce that the values are earliest start date to latest end date.
p_start_date := LEAST( start_dt, end_dt );
p_end_date := GREATEST( start_dt, end_dt );
-- Calculate the number of days from the beginning of the ISO week containing
-- the start date and the beginning of the ISO week containing the end date
-- and then multiply this by 5/7 to get the number of full business days.
--
-- Then add on the extra days from the beginining of the ISO week containing
-- the end date and the end date and subtract the extra days from the
-- beginning of the ISO week containing the start date to the start date.
p_working_days := ( TRUNC( p_end_date, 'IW' ) - TRUNC( p_start_date, 'IW' ) ) * 5 / 7
+ LEAST( p_end_date - TRUNC( p_end_date, 'IW' ), 5 )
- LEAST( p_start_date - TRUNC( p_start_date, 'IW' ), 5 );
-- If the start date and end date are reversed then return a negative value.
IF start_dt > end_dt THEN
RETURN -ROUND( p_working_days * 24 * 60, 3 );
ELSE
RETURN +ROUND( p_working_days * 24 * 60, 3 );
END IF;
END;
/
Then:
SELECT GET_BUS_MINUTES_BETWEEN(
DATE '2020-09-14' + INTERVAL '6' HOUR,
DATE '2020-09-14' + INTERVAL '10' HOUR
) AS minutes_between
FROM DUAL;
Outputs:
| MINUTES_BETWEEN |
| --------------: |
| 240 |
and:
SELECT GET_BUS_MINUTES_BETWEEN(
DATE '2020-09-13' + INTERVAL '6' HOUR,
DATE '2020-09-14' + INTERVAL '10' HOUR
) AS minutes_between
FROM DUAL;
outputs:
| MINUTES_BETWEEN |
| --------------: |
| 600 |
db<>fiddle here
If the intervals are not too big, one method uses a brute force approach to generate all minutes in the range, then exclude week-ends:
with cte(dt, end_dt) as (
select start_dt, end_dt from dual
union all
select dt + 1 / 24 / 60, end_dt from cte where dt < end_dt
)
select count(*) work_minutes
from cte
where trunc(dt) - trunc(dt,'iw') < 5
If the intervals are not too big, one method uses a brute force approach to generate all minutes in the range, then exclude week-ends:
with cte(dt, end_dt) as (
select start_dt, end_dt from dual
union all
select dt + 1 / 24 / 60, end_dt from cte where dt < end_dt
)
select count(*) work_minutes
from cte
where to_char(dt, 'IW') <= 5
If you have large intervals, we can reduce the number of iterations by pre-generating minutes / hours series:
with
params (start_dt, end_dt) as (
select start_dt, end_dt from dual
)
minutes (mi) as (
select 0 from dual
union all select mi + 1 from minutes where mi < 59
),
hours (hr) as (
select 0 from dual
union all select hr + 1 from hours where hr < 23
)
select count(*) work_minutes
from params p
cross join minutes m
cross join hours h
where
p.start_dt + h.hr / 24 + m.mi / 24 / 60 <= end_dt
and trunc(p.start_dt + h.hr / 24 + m.mi / 24 / 60) - trunc(p.start_dt + h.hr / 24 + m.mi / 24 / 60,'iw') < 5
How to find number of Sundays in a given year in Oracle SQL.
Input: 1996
Expected output:
<date-of-sunday-1>
<date-of-sunday-2>
.............
.............
<date-of-sunday-n>
<count-of-no-of-sundays-in-that-year>
This is how to fetch Sundays:
SQL> with the_whole_year as
2 (select trunc(to_date(&&par_year, 'yyyy'), 'yyyy') + level - 1 c_date,
3 to_char(trunc(to_date(&&par_year, 'yyyy'), 'yyyy') + level - 1, 'fmday') c_day
4 from dual
5 connect by level <= add_months(trunc(to_date(&&par_year, 'yyyy'), 'yyyy'), 12) -
6 trunc(to_date(&&par_year, 'yyyy'), 'yyyy')
7 )
8 select c_date
9 from the_whole_year
10 where c_day = 'sunday';
C_DATE
----------
07.01.1996
14.01.1996
21.01.1996
28.01.1996
04.02.1996
11.02.1996
<snip>
I suppose you'll be able to count them yourself.
[EDIT: a function that does the counting]
SQL> CREATE OR REPLACE FUNCTION f_count_of_sundays (par_year IN NUMBER)
2 RETURN NUMBER
3 IS
4 retval NUMBER;
5 BEGIN
6 WITH the_whole_year
7 AS ( SELECT TRUNC (TO_DATE (par_year, 'yyyy'), 'yyyy') + LEVEL - 1
8 c_date,
9 TO_CHAR (
10 TRUNC (TO_DATE (par_year, 'yyyy'), 'yyyy') + LEVEL - 1,
11 'fmday')
12 c_day
13 FROM DUAL
14 CONNECT BY LEVEL <=
15 ADD_MONTHS (
16 TRUNC (TO_DATE (par_year, 'yyyy'), 'yyyy'),
17 12)
18 - TRUNC (TO_DATE (par_year, 'yyyy'), 'yyyy'))
19 SELECT COUNT (*)
20 INTO retval
21 FROM the_whole_year
22 WHERE c_day = 'sunday';
23
24 RETURN retval;
25 END;
26 /
Function created.
SQL> select f_count_of_sundays(1996) from dual;
F_COUNT_OF_SUNDAYS(1996)
------------------------
52
SQL>