Get first date of month in oracle - oracle

I want to get the distinct months between start and end date. The selected month date would be the first date of the corresponding month. For example, my start date is 01/30/2015 00:00:00 & end date is 11/30/2015 23:59:59
Query I created as :
with data1 as(
select to_date('01/30/2015 00:00:00','MM/DD/YYYY HH24:MI:SS')+level-1 dt
from dual
connect by level <= to_date('11/30/2015 23:59:59','MM/DD/YYYY HH24:MI:SS')-to_date('01/30/2015 00:00:00','MM/DD/YYYY HH24:MI:SS')+1)
select distinct trunc(dt,'MM') as date1
from data1
where dt between to_date('01/30/2015 00:00:00','MM/DD/YYYY HH24:MI:SS') and to_date('11/30/2015 23:59:59','MM/DD/YYYY HH24:MI:SS')
and trunc(dt,'MM') <= to_date('11/30/2015 23:59:59','MM/DD/YYYY HH24:MI:SS')
order by trunc(dt,'MM')
Output:
01/01/2015 00:00:00 ,
02/01/2015 00:00:00 ,
03/01/2015 00:00:00 ,
04/01/2015 00:00:00 ,
05/01/2015 00:00:00 ,
06/01/2015 00:00:00 ,
07/01/2015 00:00:00 ,
08/01/2015 00:00:00 ,
09/01/2015 00:00:00 ,
10/01/2015 00:00:00 ,
11/01/2015 00:00:00
This query result in a correct output, but I have a doubt that the above query will run in all versions of oracle database without any issue. Please give me instructions.

Your query will work. But using ADD_MONTHS allows for the same results with much less work in a more direct way.
select trunc(add_months(date '2015-01-30', level - 1), 'MONTH') as THE_MONTH
from dual
connect by trunc(add_months(date '2015-01-30', level - 1), 'MONTH')
<= date '2015-11-30'
order by THE_MONTH

Related

IF / CASE statement in Where in Oracle (OR)

I want to search for a rows between two dates. In each row there is a column with date. I want to decrease this date by 1 and always display the results with that column decreased by 1 day.
For example - I'm searching between 2021-07-08 00:00:00 and 2021-07-08 23:59:59 so I want to search for columns with date 2021-07-09 but display them as 2021-07-08.
The problem is that I want to exclude from that searching holidays and weekend. So for example if I will search between 2021-07-09 00:00:00 and 2021-07-09 23:59:59 then I want to search for columns with with date 2021-07-12 and display them as 2021-07-09.
For holidays I have a list:
with BANKHOLIDAYSUK as(
select COLUMN_VALUE as HOLIDAYDATE
from table(sys.odcivarchar2list (
TO_DATE('30/08/2021', 'DD/MM/YYYY')
,TO_DATE('27/12/2021', 'DD/MM/YYYY')
,TO_DATE('28/12/2021', 'DD/MM/YYYY')
,TO_DATE('01/01/2022', 'DD/MM/YYYY')
,TO_DATE('03/01/2022', 'DD/MM/YYYY')
,TO_DATE('15/04/2022', 'DD/MM/YYYY')
,TO_DATE('18/04/2022', 'DD/MM/YYYY')
,TO_DATE('02/05/2022', 'DD/MM/YYYY')
,TO_DATE('02/06/2022', 'DD/MM/YYYY')
,TO_DATE('03/06/2022', 'DD/MM/YYYY')
,TO_DATE('29/08/2022', 'DD/MM/YYYY')
,TO_DATE('26/12/2022', 'DD/MM/YYYY')
,TO_DATE('27/12/2022', 'DD/MM/YYYY')
,TO_DATE('01/01/2023', 'DD/MM/YYYY')
,TO_DATE('02/01/2023', 'DD/MM/YYYY')
,TO_DATE('07/04/2023', 'DD/MM/YYYY')
,TO_DATE('10/04/2023', 'DD/MM/YYYY')
,TO_DATE('01/05/2023', 'DD/MM/YYYY')
,TO_DATE('29/05/2023', 'DD/MM/YYYY')
,TO_DATE('28/08/2023', 'DD/MM/YYYY')
,TO_DATE('25/12/2023', 'DD/MM/YYYY')
,TO_DATE('26/12/2023', 'DD/MM/YYYY')
,TO_DATE('09/07/2021', 'DD/MM/YYYY')))
)
How to check in where clause that we have :start or :end date into that list or weekend.
I've tried with:
where
to_date(to_char(from_tz( cast( (o.DUEDATEUTC - 1) as timestamp ), 'UTC' ) at time zone to_char(l.oracletimezone ), 'YYYY-MM-DD HH24:MI:SS'), 'YYYY-MM-DD HH24:MI:SS') between :startDate and :endDate
OR (
(SELECT * FROM BANKHOLIDAYSUK WHERE HOLIDAYDATE = TO_DATE(:startdate, 'DD/MM/YYYY')) is not null
and to_date(to_char(from_tz( cast( (o.DUEDATEUTC - 1) as timestamp ), 'UTC' ) at time zone to_char(l.oracletimezone ), 'YYYY-MM-DD HH24:MI:SS'), 'YYYY-MM-DD HH24:MI:SS') between :startDate and :endDate
)
OR
(
((SELECT to_char(:startDate, 'd') FROM DUAL) = 5)
and to_date(to_char(from_tz( cast( (o.DUEDATEUTC - 3) as timestamp ), 'UTC' ) at time zone to_char(l.oracletimezone ), 'YYYY-MM-DD HH24:MI:SS'), 'YYYY-MM-DD HH24:MI:SS') between :startDate and :endDate
)
But it seems like executing of the query goes forever...
l.oracletimezone is an column with timezone for different locations.
Of course I'm using decreasing also in select.
Without OR statements it works but as I said only between monday and thursday. If we select between friday date then we will get nothing cause there is no 'DUEDATE' until weekend days.
Is my logic is wrong here?
Example:
id
name
duedate
1
Electricity bill
2021-07-08
2
Water bill
2021-07-09
3
Rent bill
2021-07-12
Search between 2021-07-07 00:00:00 and 2021-07-07 23:59:59
Result:
id
name
duedate
1
Electricity bill
2021-07-07
Search between 2021-07-08 00:00:00 and 2021-07-08 23:59:59
Result:
id
name
duedate
1
Water bill
2021-07-08
Search between 2021-07-09 00:00:00 and 2021-07-09 23:59:59
Result:
id
name
duedate
1
Rent bill
2021-07-09
Search between 2021-07-07 00:00:00 and 2021-07-09 23:59:59
Result:
id
name
duedate
1
Electricity bill
2021-07-07
2
Water bill
2021-07-08
3
Rent bill
2021-07-09
You can create the function:
CREATE FUNCTION next_working_day(
day IN DATE
) RETURN DATE
IS
working_day DATE;
BEGIN
working_day := day + CASE TRUNC(day) - TRUNC(day, 'IW')
WHEN 5 THEN 2 -- Saturday
WHEN 6 THEN 1 -- Sunday
ELSE 0 -- Weekday
END;
WITH non_holiday_date ( day, skip ) AS (
SELECT working_day,
NVL2(
b.holidaydate,
CASE TRUNC(working_day) - TRUNC(working_day, 'IW')
WHEN 4 THEN 3 -- Friday
ELSE 1 -- Any other weekday
END,
0
)
FROM DUAL d
LEFT OUTER JOIN bankholidaysuk b
ON (TRUNC(working_day) = b.holidaydate)
UNION ALL
SELECT day + skip,
NVL2(
b.holidaydate,
CASE TRUNC(day) - TRUNC(day, 'IW')
WHEN 4 THEN 3 -- Friday
ELSE 1 -- Any other weekday
END,
0
)
FROM non_holiday_date n
LEFT OUTER JOIN bankholidaysuk b
ON (TRUNC(day) + skip = b.holidaydate)
WHERE n.skip > 0
)
SELECT day
INTO working_day
FROM non_holiday_date
WHERE skip = 0;
RETURN working_day;
END;
/
Then, if you have the sample data:
CREATE TABLE your_table (id, name, duedateutc) AS
SELECT 1, 'Electricity bill', DATE '2021-08-20' FROM DUAL UNION ALL
SELECT 2, 'Water bill', DATE '2021-08-23' FROM DUAL UNION ALL
SELECT 3, 'Rent bill', DATE '2021-08-31' FROM DUAL UNION ALL
SELECT 4, 'XYZ bill', DATE '2021-12-29' FROM DUAL;
CREATE TABLE BANKHOLIDAYSUK ( holidaydate ) as
SELECT DATE '2021-08-30' FROM DUAL UNION ALL
SELECT DATE '2021-12-27' FROM DUAL UNION ALL
SELECT DATE '2021-12-28' FROM DUAL;
Then:
SELECT *
FROM your_table o
WHERE o.duedateutc BETWEEN next_working_day( DATE '2021-08-19' + 1 )
AND next_working_day( DATE '2021-08-19' + INTERVAL '23:59:59' HOUR TO SECOND + 1 )
Gets the bill due on the next day and outputs:
ID
NAME
DUEDATEUTC
1
Electricity bill
2021-08-20 00:00:00
and:
SELECT *
FROM your_table o
WHERE o.duedateutc BETWEEN next_working_day( DATE '2021-08-20' + 1 )
AND next_working_day( DATE '2021-08-20' + INTERVAL '23:59:59' HOUR TO SECOND + 1 )
Skips the weekend and gets the bill on the next Monday and outputs:
ID
NAME
DUEDATEUTC
2
Water bill
2021-08-23 00:00:00
and:
SELECT *
FROM your_table o
WHERE o.duedateutc BETWEEN next_working_day( DATE '2021-08-27' + 1 )
AND next_working_day( DATE '2021-08-27' + INTERVAL '23:59:59' HOUR TO SECOND + 1 )
Skips the weekend and the Monday holiday and gets the bill on the next Tuesday and outputs:
ID
NAME
DUEDATEUTC
3
Rent bill
2021-08-31 00:00:00
and:
SELECT *
FROM your_table o
WHERE o.duedateutc BETWEEN next_working_day( DATE '2021-08-19' + 1 )
AND next_working_day( DATE '2021-08-27' + INTERVAL '23:59:59' HOUR TO SECOND + 1 )
Gets all the previous bills, outputting:
ID
NAME
DUEDATEUTC
1
Electricity bill
2021-08-20 00:00:00
2
Water bill
2021-08-23 00:00:00
3
Rent bill
2021-08-31 00:00:00
and:
SELECT *
FROM your_table o
WHERE o.duedateutc BETWEEN next_working_day( DATE '2021-12-24' + 1 )
AND next_working_day( DATE '2021-12-24' + INTERVAL '23:59:59' HOUR TO SECOND + 1 )
Skips the weekend and the 2-day Christmas holiday and gets the bill on the next Wednesday, outputting:
ID
NAME
DUEDATEUTC
4
XYZ bill
2021-12-29 00:00:00
db<>fiddle here

ORA-01841 , (full) year must be between

I want to convert the epoch date from table into timestamp.
But it results in an error when I run it in Oracle.
But the year showing into "Rabu, 22 April 2465 pukul 15.35.06.289 GMT+07:00" when i run from this link "https://www.epochconverter.com/'
15630394456289509085900
this the epoch time from table
select to_char(
cast(
to_date('01/01/1970 00:00:00','DD/MM/YYYY HH24:MI:SS')+15630394456289509085900/86400
as timestamp with local time zone)
,'YYYY-MM-DD HH24:MI:SS')
from dual
Epoch times are in the UTC time zone. Just use a TIMESTAMP literal and add the correct amount of seconds:
SELECT TIMESTAMP '1970-01-01 00:00:00 UTC'
+ NUMTODSINTERVAL(15630394456289509085900 / 86400e12, 'DAY') AS epoch_time_12,
TIMESTAMP '1970-01-01 00:00:00 UTC'
+ NUMTODSINTERVAL(15630394456289509085900 / 86400e13, 'DAY') AS epoch_time_13
FROM DUAL
Which outputs:
EPOCH_TIME_12 | EPOCH_TIME_13
:-------------------------------- | :--------------------------------
2465-04-22 08:14:16.289509086 UTC | 2019-07-13 17:37:25.628950909 UTC
db<>fiddle here

Oracle sql how to get start and end dates of weeks between two dates

I want to get the start and end days of every week between two dates. The dates's format is dd/mm/yyy hh24:mi:ss. I need the weeks in the format dd/mm/yyyy hh24:mi:ss because I have to calculate the days and hours between the start and end day of the week with the times
I wrote this statement
WITH
date_range AS (
SELECT
pdm.des_comercial serie,
pdm.id_material codserie,
ri.id_accion intervencion,
TO_CHAR(NVL(ri.fecha_salida_rev, SYSDATE), 'dd/mm/RRRR') fecha1,
to_char((CASE
WHEN ri.fecha_salida_rev > TO_DATE('18/06/2019', 'dd/mm/yyyy') THEN TO_DATE('18/06/2019', 'dd/mm/yyyy')
WHEN ri.fecha_salida_Rev IS NULL THEN TO_DATE('18/06/2019', 'dd/mm/yyyy')
ELSE ri.fecha_salida_Rev
END),'dd/mm/yyyy hh24:mi:ss') fechasalida,
to_char((CASE
WHEN ri.fecha_entrada_rev < TO_DATE('01/06/2019', 'dd/mm/yyyy') THEN TO_DATE('01/06/2019', 'dd/mm/yyyy')
ELSE ri.fecha_entrada_Rev
END),'dd/mm/yyyy hh24:mi:ss') fechaentrada
,
ri.cod_taller_rev,
ri.COD_MATRICULA,
ri.fecha_entrada_rev start_date,
ri.fecha_salida_rev end_date
FROM
r_intervencion ri,
planificador.pl_dh_material pdm
WHERE
ri.id_accion = ri.amortizada_por
AND ri.causa_entrada = 1
AND ri.tipo_accion = 1
AND pdm.id_material = ri.cod_serie
AND pdm.hasta = 99999999
AND ri.ID_ACCION = 'IM4'
AND ri.fecha_salida_rev BETWEEN TO_DATE('01/06/2019', 'dd/mm/yyyy') AND TO_DATE('18/06/2019', 'dd/mm/yyyy')
),
semanas AS (
SELECT LEVEL "Week"
,to_char(to_date(start_date,'dd/mm/yyyy hh24:mi:ss') + (7 * (LEVEL - 1)),'IW') startweek
,to_char(to_date(start_date ,'dd/mm/yyyy hh24:mi:ss')+ (7 * (LEVEL - 1)),'IW') + 6 endweek
,TO_CHAR(start_date + (7 * (LEVEL - 1)),'IW') "Iso Week",
serie,
codserie,
intervencion,
cod_taller_rev,
cod_matricula,
fechaentrada,
fechasalida,
start_date,
end_date
FROM date_range
CONNECT BY LEVEL <= (to_char(To_date(end_date,'dd/mm/yyyy hh24:mi:ss'),'IW') - to_char(To_date(start_date,'dd/mm/yyyy hh24:mi:ss'),'IW')) / 7 + 1
)
SELECT startweek,
endweek,
to_date(endweek,'dd/mm/yyyy hh24:mi:ss') - to_date(startweek,'dd/mm/yyyy hh24:mi:ss') dias,
serie,
codserie,
intervencion,
cod_taller_rev,
cod_matricula,
start_Date,
end_date,
fechaentrada,
fechasalida,
rd.descripcion
FROM semanas,r_depositos rd
WHERE cod_taller_rev = rd.cod_deposito
When I execute it, I get
Query execution failed
SQL Error [1840] [22008]: ORA-01840: ORA-01840: input value not long enough for date format
The error is in
,to_char(to_date(start_date,'dd/mm/yyyy hh24:mi:ss') + (7 * (LEVEL - 1)),'IW') startweek
,to_char(to_date(start_date ,'dd/mm/yyyy hh24:mi:ss')+ (7 * (LEVEL - 1)),'IW') + 6 endweek
How can I get the startweek and endweek with the format dd/mm/yyyy hh24:mi:ss
EDITED
start_date end_date
20/05/2019 20:00:00 05/06/2019 08:00:00
weeks
20/05/2019 20:00:00 26/05/2019 -> 6 days and xxx hours
27/05/2019 02/06/2019 -> 7 days
03/06/2019 05/06/2019 08:00:00 -> 3 days and xxx hours
I need to calculate the difference in days and hours for each week.
For example between 20/05/2019 20:00:00 and 26/05/2019
and last one between 03/06/2019 and 05/06/2019 08:00:00
My issue is with the calculation
to_date(endweek,'dd/mm/yyyy hh24:mi:ss') - to_date(startweek,'dd/mm/yyyy hh24:mi:ss') dias,
endweek and startweek have to have dd/mm/yyyy hh24:mi:ss
My issue is with the calculation
to_date(endweek,'dd/mm/yyyy hh24:mi:ss') - to_date(startweek,'dd/mm/yyyy hh24:mi:ss') dias,
endweek and startweek have to have dd/mm/yyyy hh24:mi:ss
Oracle dates are stored in an internal format which you generally don't need to worry about. Your application or client formats the date as a string, based on its own setting or your session NLS settings.
When you do something like:
to_date(endweek,'dd/mm/yyyy hh24:mi:ss')
you're really doing:
to_date(to_char(endweek),'dd/mm/yyyy hh24:mi:ss')
and as there is no explicit format mask specified for the implicit to_char() call it used your current session's NLS settings. Depending on the setting it might error; or might corrupt the value - e.g. mixing YY and YYYY masks can lose the century, converting 2019 to 0019. (Given the error you are getting, your NLS settings might be unusual?).
At best you're converting the date value to a string and back to exactly the same date value, which is pointless. You aren't changing the format of the datem because it doesn't have one. The intermediate string does, but you aren't using that, and you can't for calculations (at least without converting back to a date as you are, which again is pointless.)
Oracle has other functions to manipulate date values, including trunc(), so I think you might want something like this - showing the difference in three ways, though there are others and you can format the last one however you want:
with date_range (start_date, end_date) as (
-- dummy data from your example
select to_date('20/05/2019 20:00:00', 'DD/MM/YYYY HH24;MI:SS') as start_date,
to_date('05/06/2019 08:00:00', 'DD/MM/YYYY HH24;MI:SS') as end_date
from dual
),
semanas as (
select level as week,
start_date,
end_date,
greatest(trunc(start_date + (7 * (level - 1)), 'IW'), start_date) as start_week,
least(trunc(start_date + (7 * level), 'IW'), end_date) as end_week
from date_range
connect by level <= (trunc(end_date, 'IW') - trunc(start_date, 'IW')) / 7 + 1
)
select week,
to_char(start_week, 'IW') as iso_week,
to_char(start_week, 'DD/MM/YYYY HH24:MI:SS') as start_week,
to_char(end_week, 'DD/MM/YYYY HH24:MI:SS') as end_week,
end_week - start_week as diff_num,
numtodsinterval(end_week - start_week, 'DAY') as diff_interval,
to_char(date '1999-12-31' + (end_week - start_week), 'FMDD "days" HH24 "hours"') as diff_words
from semanas;
WEEK IS START_WEEK END_WEEK DIFF_NUM DIFF_INTERVAL DIFF_WORDS
---------- -- ------------------- ------------------- ---------- ------------------- ----------------
1 21 20/05/2019 20:00:00 27/05/2019 00:00:00 6.16666667 +06 04:00:00.000000 6 days 4 hours
2 22 27/05/2019 00:00:00 03/06/2019 00:00:00 7 +07 00:00:00.000000 7 days 0 hours
3 23 03/06/2019 00:00:00 05/06/2019 08:00:00 2.33333333 +02 08:00:00.000000 2 days 8 hours
As currently written the connect by only works properly if the date_range CTE generates a single value; if you actually get multiple rows back from your real query then you'll have to do a bit more work, or switch to recursive CTEs, or cross join/apply, depending on your Oracle version.
Your endweek calculation is
to_char(to_date(start_date ,'dd/mm/yyyy hh24:mi:ss')+ (7 * (LEVEL - 1)),'IW') + 6 endweek
This attempts to add the number 6 to a character string. I suspect that what you wanted was
to_char(to_date(start_date ,'dd/mm/yyyy hh24:mi:ss') + (7 * (LEVEL - 1) + 6),'IW') endweek
Here I've moved the + 6 so you're adding 6 to the date value, rather than to a character string.

Returning times between other times

I have two tables AVAIL and AVAIL_TIMES. AVAIL contains avail_id, avail_date, open_flag. AVAIL_TIMES contains avail_times_id, Avail_id, Start_Time, End_time. All date and time fields are typed as DATE
If a date is flagged in the avail open_flag column it means that the facility is open for that date, but the times it is open is listed in avail_times. There can be multiple time ranges for a particular day.
I need to return a list of times it is not open for that day.
For Example (one day of many)
Open times for day:
Start_time: 08:00 End_time 10:00
Start_time: 12:00 End_time 14:00
Start_time: 15:00 End_time 17:00
I want it to return something like:
00:00 - 07:59
10:01 - 11:59
14:01 - 14:59
17:01 - 23:59
I think I would be able to work through this with a temporary table and some plsql logic, but ideally this would be a pure sql solution.
I am not exactly sure how you want to input the date of interest (I used a bind variable, passed in as a string - but that may not be the right way for you, perhaps you want to join to your other table, etc.) - or the exact output you want. In any case, the query below demonstrates the "core" of the code you need to achieve this kind of output from the inputs.
alter session set nls_date_format='mm/dd/yyyy hh24:mi';
with
avail_times ( start_time, end_time ) as (
select to_date('06/20/2017 08:00'), to_date('06/20/2017 10:00') from dual union all
select to_date('06/20/2017 12:00'), to_date('06/20/2017 14:00') from dual union all
select to_date('06/20/2017 15:00'), to_date('06/20/2017 17:00') from dual
)
select trunc(min(start_time)) as start_time, min(start_time) as end_time
from avail_times
where trunc(start_time) = to_date(:input_date, 'mm/dd/yyyy')
union all
select end_time,
lead(start_time, 1, trunc(start_time) + 1) over (order by start_time)
from avail_times
where trunc(end_time) = trunc(start_time)
order by start_time
;
START_TIME END_TIME
---------------- ----------------
06/20/2017 00:00 06/20/2017 08:00
06/20/2017 10:00 06/20/2017 12:00
06/20/2017 14:00 06/20/2017 15:00
06/20/2017 17:00 06/21/2017 00:00
Another Approach. Hope this helps.
SELECT ID,
START_TME,
END_TM,
DIFF_TM
FROM
--Not part of SQL just to simulate the table data
(WITH TMP AS
(SELECT 1 ID,
TO_DATE('06/27/2017 00:00','mm/dd/yyyy hh24:mi') START_TME,
TO_DATE('06/27/2017 08:00','mm/dd/yyyy hh24:mi') END_TM
FROM DUAL
UNION ALL
SELECT 1 ID,
TO_DATE('06/27/2017 10:00','mm/dd/yyyy hh24:mi') START_TME,
TO_DATE('06/27/2017 15:00','mm/dd/yyyy hh24:mi') END_TM
FROM DUAL
UNION ALL
SELECT 1 ID,
TO_DATE('06/27/2017 16:00','mm/dd/yyyy hh24:mi') START_TME,
TO_DATE('06/27/2017 17:00','mm/dd/yyyy hh24:mi') END_TM
FROM DUAL
UNION ALL
SELECT 1 id,
to_date('06/27/2017 17:00','mm/dd/yyyy hh24:mi') start_tme,
TO_DATE('06/27/2017 18:00','mm/dd/yyyy hh24:mi') END_TM
FROM DUAL
)
--SQL start from here
SELECT TMP.*,
LEAD(START_TME) OVER(PARTITION BY ID ORDER BY 1 DESC) next_st_tm,
LEAD(END_TM) OVER(PARTITION BY ID ORDER BY 1 DESC) NEXT_EN_TM,
EXTRACT( HOUR FROM TO_TIMESTAMP(LEAD(START_TME) OVER(PARTITION BY ID ORDER BY 1 DESC),'MM/DD/YYYY HH24:MI'))- EXTRACT(HOUR FROM TO_TIMESTAMP(end_tm,'MM/DD/YYYY HH24:MI')) DIFF_TM
FROM TMP
ORDER BY 1 ,
2
)
WHERE DIFF_TM <> 0;

Oracle Week Number From Date ISO Week But From December 1

Ok, I've had a query that has been working fine that calculates the week number from December 1 (the start of our Sales Fiscal Year).
Now the requirements have changed. I still need to calculate the week number based on the field (Invoice_Date). However, instead of starting to count from December 1 (Dec 1-7, Week 1, etc.) now I need to start counting on the nearest Monday to December 1st. As I understand it, the ISO week is kind of what I'm looking for but it starts January 1. How do I modify this to work from December 1?
Any help would be greatly appreciated.
select next_day(to_date('0112' || to_char(sysdate, 'YYYY'),'ddmmyyyy') - 1, 'MONDAY') dec_mon from dual; gives you first Monday of December current year
Number of week is just ceil((sysdate - dec_mon)/7).
If you want last Monday before 1st Dec you can get it by:
select next_day(to_date('2511' || to_char(sysdate, 'YYYY'),'ddmmyyyy') - 1, 'MONDAY')
from dual;
In this proposed solution, I build a "helper table" first, showing the Monday_from and Monday_to for each fiscal year (in the third CTE, named ranges). Then I build a few test dates - I was lazy, I should have used to_date() so I can include time-of-day component as well. The join condition in the actual solution (at the end of the code) is written so it works without modification for dates with non-zero "time-of-day" component.
I used the nice feature of Oracle 11.2 which allows us to give column aliases in the declaration of CTEs - otherwise the column aliases would need to be moved inside the respective SELECTs. Otherwise the solution should work at least for Oracle 9 and above (I think).
with
y ( dt ) as (
select add_months(date '2000-12-01', 12 * level )
from dual
connect by level <= 30
),
m ( dt ) as (
select trunc(dt, 'iw') + case when dt - trunc(dt, 'iw') <= 3 then 0 else 7 end
from y
),
ranges ( monday_from, monday_to ) as (
select dt, lead(dt) over (order by dt) - 1
from m
),
test_dates ( t_date ) as (
select date '2013-02-23' from dual union all
select date '2008-12-01' from dual union all
select date '2008-04-28' from dual union all
select date '2016-11-29' from dual
)
select t_date, monday_from, 1 + trunc((t_date - monday_from)/7) as week_no
from test_dates t inner join ranges r
on t.t_date >= r.monday_from and t.t_date < r.monday_to
;
T_DATE MONDAY_FROM WEEK_NO
------------------- ------------------- ----------
2008-04-28 00:00:00 2007-12-03 00:00:00 22
2008-12-01 00:00:00 2008-12-01 00:00:00 1
2013-02-23 00:00:00 2012-12-03 00:00:00 12
2016-11-29 00:00:00 2016-11-28 00:00:00 1
The nearest Monday to any any given date is returned with the following function:
NEXT_DAY(some_date-4,'Monday')
as shown by this query:
with dts(some_date) as (
select date '2006-12-1' from dual
union all
select add_months(some_date,12)
from dts
where some_date <= date '2014-12-1'
)
select some_date
, next_day(some_date-4,'monday') nearest
, some_date - next_day(some_date-4,'monday') dist
from dts;
SOME_DATE NEAREST DIST
----------- ----------- ----------
01-DEC-2006 04-DEC-2006 -3
01-DEC-2007 03-DEC-2007 -2
01-DEC-2008 01-DEC-2008 0
01-DEC-2009 30-NOV-2009 1
01-DEC-2010 29-NOV-2010 2
01-DEC-2011 28-NOV-2011 3
01-DEC-2012 03-DEC-2012 -2
01-DEC-2013 02-DEC-2013 -1
01-DEC-2014 01-DEC-2014 0
01-DEC-2015 30-NOV-2015 1
10 rows selected

Resources