Hello everyone I have written an oracle query which is calculating working according to 8 hours, but I want according to 8.5 hours result, there is a minor change but I am not getting it please help. Now according to the start and end date, it should return 8.5 working house, but it is returning 8 working hours please assist.
Query
with dates as (
select to_date('20-oct-2022 09:00:00','dd-mon-yyyy hh24:mi:ss') start_dt,
to_date('20-oct-2022 17:30:00','dd-mon-yyyy hh24:mi:ss') end_dt
from dual
),
-- get work hours for each date
t as (
select case level
when 1 then greatest(start_dt,trunc(start_dt) + 8 / 24)
else trunc(start_dt) + level - 16 / 24
end start_dt,
case connect_by_isleaf
when 1 then least(end_dt,trunc(end_dt) + 17 / 24)
else trunc(start_dt) + level - 7 / 24
end end_dt
from dates
connect by level <= trunc(end_dt) - trunc(start_dt) + 1
)
select sum(greatest(end_dt - start_dt,0)) * 24 work_hours
from t
where trunc(start_dt) - trunc(start_dt,'iw') < 5
You do not need to generate all the days; you can directly calculate the number of hours:
SELECT start_dt,
end_dt,
ROUND(
(
-- Calculate the full weeks difference from the start of ISO weeks.
( TRUNC( end_dt, 'IW' ) - TRUNC( start_dt, 'IW' ) ) * 8.5 * (5/7)
-- Add the full days for the final week.
+ LEAST( TRUNC( end_dt ) - TRUNC( end_dt, 'IW' ), 5 ) * 8.5
-- Subtract the full days from the days of the week before the start date.
- LEAST( TRUNC( start_dt ) - TRUNC( start_dt, 'IW' ), 5 ) * 8.5
-- Add the hours of the final day
+ CASE
WHEN end_dt - TRUNC( end_dt, 'IW' ) < 5 -- Weekday
THEN LEAST(
GREATEST(
end_dt - (TRUNC( end_dt ) + INTERVAL '09:00' HOUR TO MINUTE),
0
) * 24,
8.5
)
ELSE 0
END
-- Subtract the hours of the day before the range starts.
- CASE
WHEN start_dt - TRUNC( start_dt, 'IW' ) < 5 -- Weekday
THEN LEAST(
GREATEST(
start_dt - (TRUNC( start_dt ) + INTERVAL '09:00' HOUR TO MINUTE),
0
) * 24,
8.5
)
ELSE 0
END
),
15 -- Number of decimal places
) AS work_hours_diff
FROM dates;
Which, for the sample data:
CREATE TABLE dates (start_dt, end_dt) AS
SELECT DATE '2022-10-20' + INTERVAL '09:00:00' HOUR TO SECOND,
DATE '2022-10-20' + INTERVAL '17:30:00' HOUR TO SECOND
FROM DUAL
UNION ALL
SELECT DATE '2022-10-20' + INTERVAL '10:00:00' HOUR TO SECOND,
DATE '2022-10-21' + INTERVAL '17:30:00' HOUR TO SECOND
FROM DUAL
UNION ALL
SELECT DATE '2022-11-19' + INTERVAL '23:46:00' HOUR TO SECOND,
DATE '2022-11-21' + INTERVAL '12:06:00' HOUR TO SECOND
FROM DUAL
UNION ALL
SELECT DATE '2022-11-18' + INTERVAL '17:30:00' HOUR TO SECOND,
DATE '2022-11-21' + INTERVAL '09:00:00' HOUR TO SECOND
FROM DUAL
UNION ALL
SELECT DATE '2022-11-19' + INTERVAL '12:26:45' HOUR TO SECOND,
DATE '2022-11-21' + INTERVAL '11:02:15' HOUR TO SECOND
FROM DUAL
UNION ALL
SELECT DATE '2022-11-21' + INTERVAL '11:02:15' HOUR TO SECOND,
DATE '2022-11-19' + INTERVAL '12:26:45' HOUR TO SECOND
FROM DUAL;
Outputs:
START_DT
END_DT
WORK_HOURS_DIFF
2022-10-20 09:00:00 (THU)
2022-10-20 17:30:00 (THU)
8.5
2022-10-20 10:00:00 (THU)
2022-10-21 17:30:00 (FRI)
16
2022-11-19 23:46:00 (SAT)
2022-11-21 12:06:00 (MON)
3.1
2022-11-18 17:30:00 (FRI)
2022-11-21 09:00:00 (MON)
0
2022-11-19 12:26:45 (SAT)
2022-11-21 11:02:15 (MON)
2.0375
2022-11-21 11:02:15 (MON)
2022-11-19 12:26:45 (SAT)
-2.0375
Note: the negative value is valid as the start date is after the end date for that row.
fiddle
Related
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
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.
I have two date columns and trying to measure days between the two dates excluding weekends. I'm getting a negative number and need help solving.
Table
CalendarDate DayNumber FirstAssgn FirstCnt DayNumber2 Id BusinessDays
5/21/2017 Sunday 5/21/17 5/21/17 Sunday 1 -1
Query:
TRUNC(TO_DATE(A.FIRST_CONTACT_DT, 'DD/MM/YYYY')) - TRUNC(TO_DATE(A.FIRST_ASSGN_DT, 'DD/MM/YYYY'))
- ((((TRUNC(A.FIRST_CONTACT_DT,'D'))-(TRUNC(A.FIRST_ASSGN_DT,'D')))/7)*2)
- (CASE WHEN TO_CHAR(A.FIRST_ASSGN_DT,'DY','nls_date_language=english') ='SUN' THEN 1 ELSE 0 END)
- (CASE WHEN TO_CHAR(A.FIRST_CONTACT_DT,'DY','nls_date_language=english')='SAT' THEN 1 ELSE 0 END)
- (SELECT COUNT(1) FROM HUM.CALENDAR CAL
WHERE 1=1
AND CAL.CALENDAR_DATE >= A.FIRST_ASSGN_DT
AND CAL.CALENDAR_DATE < A.FIRST_CONTACT_DT
--BETWEEN A.FIRST_ASSGN_DT AND A.FIRST_CONTACT_DT
AND CAL.GRH_HOLIDAY_IND = 'Y'
) AS Business_Days
Looks like below piece needs editing...
- (CASE WHEN TO_CHAR(A.FIRST_ASSGN_DT,'DY','nls_date_language=english')='SUN' THEN 1 ELSE 0 END)
Adapted from my answer here:
Get the number of days between the Mondays of both weeks (using TRUNC( datevalue, 'IW' ) as an NLS_LANGUAGE independent method of finding the Monday of the week) then add the day of the week (Monday = 1, Tuesday = 2, etc., to a maximum of 5 to ignore weekends) for the end date and subtract the day of the week for the start date. Like this:
SELECT ( TRUNC( end_date, 'IW' ) - TRUNC( start_date, 'IW' ) ) * 5 / 7
+ LEAST( end_date - TRUNC( end_date, 'IW' ) + 1, 5 )
- LEAST( start_date - TRUNC( start_date, 'IW' ) + 1, 5 )
AS WeekDaysDifference
FROM your_table
With RANGE_TEMP as (
SELECT
STARTPERIOD start_date,
ENDPERIOD end_date
FROM
TABLE_DATA -- YOUR TABLE WITH ALL DATA DATE
), DATE_TEMP AS (
SELECT
(start_date + LEVEL) DATE_ALL
FROM
RANGE_TEMP
CONNECT BY LEVEL <= (end_date - start_date)
), WORK_TMP as (
SELECT
COUNT(DATE_ALL) WORK_DATE
FROM
DATE_TEMP
WHERE
TO_CHAR(DATE_ALL,'D', 'NLS_DATE_LANGUAGE=ENGLISH') NOT IN ('1','7')
), BUSINESS_TMP as (
SELECT
COUNT(DATE_ALL) BUSINESS_DATE
FROM
DATE_TEMP
WHERE
TO_CHAR(DATE_ALL,'D', 'NLS_DATE_LANGUAGE=ENGLISH') IN ('1','7')
)
SELECT
L.WORK_DATE,
H.BUSINESS_DATE
FROM
BUSINESS_TMP H,
WORK_TMP L
;
I found this query for finding the number of Sundays in a month.
I have been tinkering with it but cannot figure out how to change it to get, say, the number of Wednesdays in a month, for example. Can you show me how?
with
months as (
select add_months(trunc(sysdate,'YEAR'),level-13) month
from dual
connect by level <= 36
)
select to_char(month,'YYYY') year,
to_char(month,'Month') month,
to_char(month,'Day') first_day,
to_char(last_day(month),'Day DD') last_day,
4+
case
when to_char(last_day(month),'DD') - decode(to_char(month,'D'),1,0,8 -to_char(month,'D')) >= 29
then 1
else 0
end nb_sunday
from months
Here's the game: You give me a year (like 2015) and a day of the week, in the form of a three-letter string (like 'Wed'). I will return a table with each month of that year and with the count of days-of-week equal to your input in each month.
Simply implementing here the suggestion from my Comment to MT0's Answer. I am hard-coding the year and the day-of-week (in a CTE) since "how to pass parameters to a query" (through bind variables and such) is not the focus in this thread.
with
inputs ( yr, day_of_week ) as (
select 2015, 'Wed' from dual
),
prep ( dec31 ) as (
select to_date(to_char(yr - 1) || '-12-31', 'yyyy-mm-dd') from inputs
)
select to_char(add_months(dec31, level), 'Mon-yyyy') as mth,
( next_day(add_months(dec31, level) , day_of_week) -
next_day(add_months(dec31, level - 1), day_of_week) ) / 7 as cnt
from inputs cross join prep
connect by level <= 12;
MTH CNT
-------- ----
Jan-2015 4
Feb-2015 4
Mar-2015 4
Apr-2015 5
May-2015 4
Jun-2015 4
Jul-2015 5
Aug-2015 4
Sep-2015 5
Oct-2015 4
Nov-2015 4
Dec-2015 5
12 rows selected.
The last wednesday of the month is given by:
TRUNC( NEXT_DAY( LAST_DAY( :month ) - INTERVAL '7' DAY, 'WEDNESDAY' ) )
The first wednesday of the month is given by:
NEXT_DAY( TRUNC( :month, 'MM' ) - INTERVAL '1' DAY, 'WEDNESDAY' )
Subtracting gives the number of days between them. Divide by 7 and add 1 and you get the number of Wednesdays:
SELECT ( TRUNC( NEXT_DAY( LAST_DAY( :month ) - INTERVAL '7' DAY, 'WEDNESDAY' ) )
- NEXT_DAY( TRUNC( :month, 'MM' ) - INTERVAL '1' DAY, 'WEDNESDAY' )
) / 7 + 1
AS number_of_wednesdays
FROM DUAL;
Or you can just use the difference between the first Wednesday of the month and of the next month as suggested by #mathguy
SELECT ( NEXT_DAY(
ADD_MONTHS( TRUNC( :month, 'MM' ), 1 ) - INTERVAL '1' DAY,
'WEDNESDAY'
)
- NEXT_DAY(
TRUNC( :month, 'MM' ) - INTERVAL '1' DAY,
'WEDNESDAY'
)
) / 7
AS number_of_wednesdays
FROM DUAL;
I worked this statement out
SELECT to_date('30.06.2016', 'dd.mm.yyyy') - (LEVEL-1) DATUM
FROM DUAL
CONNECT BY LEVEL <= 366;
which gives me all dates from 30.06.2016 till 366 days in the past.
So far so good.
What I need to add is that to_date('30.06.2016') is more flexible..
What I mean I always want it to use the last day of June in sysdate + 1 year.
In this case we have 2015 at the moment - so we have 30.06.2016.
If we had 2016 I need it to use 30.06.2017.
If we had 2017 I need it to use 30.06.2018.
..
..
Thanks for your help.
EDIT Solution:
SELECT last_day(add_months(to_date('01.06.' || to_char(sysdate, 'YYYY'), 'dd.mm.yyyy'),12)) - (LEVEL-1) DATUM
FROM DUAL
CONNECT BY LEVEL <= 366
If you want 366 days worth of dates:
SELECT TRUNC( SYSDATE, 'YEAR' ) + INTERVAL '18' MONTH - LEVEL AS DATUM
FROM DUAL
CONNECT BY LEVEL <= 366;
Or if you want a year's worth (365 days or 366 days in a leap year) of dates (1st July this year to 30th June next year):
SELECT TRUNC( SYSDATE, 'YEAR' ) + INTERVAL '18' MONTH - LEVEL AS DATUM
FROM DUAL
CONNECT BY TRUNC( SYSDATE, 'YEAR' ) + INTERVAL '18' MONTH - LEVEL >= TRUNC( SYSDATE, 'YEAR' ) + INTERVAL '6' MONTH;
Is your same code, but get from sysdate the year, using to_char:
select to_date('30.06.'||(to_char(sysdate,'yyyy')+1),'dd.mm.yyyy') from dual;
Here's the steps:
Truncate sysdate to the year, using Trunc().
Add 18 months, using Add_Months().
Subtract one day.