Extract function not working as I want in Oracle Db - oracle

I have tried many ways of getting the day,month and year in PLSQL but it keep throw me the error.
ORA-01841: (full) year must be between -4713 and +9999, and not be 0
Here is the query I run (The mfgdate and receivingdate are timestamp(6) column, and the receivingdate has null value)
select
IC_MST_CodeDateData.mstid AS mstid,
from IC_MST_CodeDateData
where ( ( IC_MST_CodeDateData.consprtydatecode = 'M' )
and ( IC_MST_CodeDateData.mfgdatereqd = 0 )
and ( EXTRACT(day from cast(( IC_MST_CodeDateData.mfgdate - interval '5' hour )as DATE)) = extract(day from cast(( IC_MST_CodeDateData.receivingdate - interval '5' hour )as Date)) )
and ( extract(month from cast(( IC_MST_CodeDateData.mfgdate - interval '5' hour )as DATE) )= EXTRACT( Month from cast(( IC_MST_CodeDateData.receivingdate - interval '5' hour )as DATE)) )
and ( extract(year from cast(( IC_MST_CodeDateData.mfgdate - interval '5' hour )as DATE)) = extract(year from cast(( IC_MST_CodeDateData.receivingdate - interval '5' hour )as DATE)) )
I check the mfgdate column and there are no NULL value.
If anyone can help would be very appreciate!

Related

(Oracle 11g DB) Calculate Number of buisiness days between current time and a date while excluding holidays in a view

So I have this working SQL script that take a date and returns the age from current time to the given date excluding dates defined in a table called exclude dates
SELECT
COUNT(*)
FROM
(
SELECT
ROWNUM rnum
FROM
all_objects
WHERE
ROWNUM <= CAST(current_timestamp AS DATE) - to_date('&2') + 1
)
WHERE
to_char(to_date('&2') + rnum - 1, 'DY') NOT IN ( 'SAT', 'SUN' )
AND NOT EXISTS (
SELECT
NULL
FROM
exclude_dates
WHERE
no_work = trunc(to_date('&2') + rnum - 1)
);
I have a table called
TICKETS
that contains columns named
ID, UPDATED_AT
I want to create a view that uses the above script to return
ID, AGE
where age is the output of the script above.
You code has a few weaknesses.
There is no need for CAST(current_timestamp AS DATE).
If you need the current DATE then simply use TRUNC(SYSDATE)
You don't need to select from all_objects. Better use hierarchical query
SELECT LEVEL as rnum FROM dual CONNECT BY LEVEL <= ...
Using to_date('&2') without a format is usually bad. Either your input value is a string, then you should include the format, e.g. to_date('&2', 'YYYY-MM-DD') or your input value is a DATE, then simply use &2 - never use TO_DATE() on a value which is already a DATE!
Final query could be this one - assuming input parameter is a DATE value:
WITH t AS (
SELECT LEVEL as d
FROM dual
CONNECT BY LEVEL <= TRUNC(SYSDATE) - the_day)
SELECT COUNT(*) AS buisiness_days
FROM t
WHERE TO_CHAR(the_day + d - 1, 'DY', 'NLS_DATE_LANGUAGE = american') NOT IN ('SAT', 'SUN')
AND NOT EXISTS (
SELECT 'x'
FROM exclude_dates
WHERE no_work = TRUNC(the_day + d - 1)
)
However, for me it is not clear how you want to provide this as a view! You would need to create a separate view for each input date, or at least create a new view every day.
I would suggest to create a function:
CREATE OR REPLACE FUNCTION buisiness_days(the_date IN DATE) RETURN INTEGER AS
ret INTEGER;
BEGIN
WITH t AS (
SELECT LEVEL as d
FROM dual
CONNECT BY LEVEL <= TRUNC(SYSDATE) - the_date)
SELECT COUNT(*) AS buisiness_days
INTO ret
FROM t
WHERE TO_CHAR(the_date + d - 1, 'DY', 'NLS_DATE_LANGUAGE = american') NOT IN ('SAT', 'SUN')
AND NOT EXISTS (
SELECT 'x'
FROM exclude_dates
WHERE no_work = TRUNC(the_date + d - 1)
);
RETURN ret;
END;
The function will return a list of dates between the date range you provide so the dates don't have to be stored in a table.
CREATE OR REPLACE TYPE nt_date IS TABLE OF DATE;
/
CREATE OR REPLACE FUNCTION generate_dates_pipelined(
p_from IN DATE,
p_to IN DATE
)
RETURN nt_date PIPELINED DETERMINISTIC
IS
v_start DATE := TRUNC(LEAST(p_from, p_to));
v_end DATE := TRUNC(GREATEST(p_from, p_to));
BEGIN
LOOP
PIPE ROW (v_start);
EXIT WHEN v_start >= v_end;
v_start := v_start + INTERVAL '1' DAY;
END LOOP;
RETURN;
END generate_dates_pipelined;
/
To exclude holidays you need to know what dates they fall on so there needs to be a holiday table.
create table holidays(
holiday_date DATE not null,
holiday_name VARCHAR2(20),
constraint holidays_pk primary key (holiday_date),
constraint is_midnight check ( holiday_date = trunc ( holiday_date ) )
);
INSERT into holidays (HOLIDAY_DATE,HOLIDAY_NAME)
WITH dts as (
select to_date('25-NOV-2021 00:00:00','DD-MON-YYYY HH24:MI:SS'), 'Thanksgiving 2021' from dual union all
select to_date('29-NOV-2021 00:00:00','DD-MON-YYYY HH24:MI:SS'), 'Hanukkah 2021' from dual
)
SELECT * from dts;
This query will provide the count of days between the range, number of working days and number of holidays in the range.
SELECT COUNT (*) AS total_days
, COUNT ( CASE
WHEN h.holiday_date IS NULL
AND d.column_value - TRUNC (d.column_value, 'IW') < 5
THEN 'Business Day'
END
) AS business_days
, COUNT (h.holiday_date) AS holidays
FROM generate_dates_pipelined (DATE '2021-11-01', DATE '2021-11-30') d
LEFT JOIN holidays h ON h.holiday_date = d.column_value;
This query will provide a list of dates excluding sat, sun and holidays that fall between the range.
SELECT
COLUMN_VALUE
FROM
TABLE(generate_dates_pipelined(DATE '2021-11-01',
DATE '2021-11-30')) c
where
to_char(COLUMN_VALUE, 'DY') NOT IN ('SAT', 'SUN')
AND NOT EXISTS (
SELECT 1
FROM holidays h
WHERE c.COLUMN_VALUE = h.holiday_date
);
You don't need a function or to use a row generator function and can calculate the number of business days:
CREATE VIEW business_day_ages (ID, AGE) AS
SELECT id,
( TRUNC( SYSDATE, 'IW' ) - TRUNC( updated_at, 'IW' ) ) * 5 / 7
-- Number of full weeks.
+ LEAST( SYSDATE - TRUNC( SYSDATE, 'IW' ), 5 )
-- Add part weeks at the end.
- LEAST( updated_at - TRUNC( updated_at, 'IW' ), 5 )
-- Subtract part weeks at the start.
- COALESCE(
( SELECT SUM(
LEAST(no_work + INTERVAL '1' DAY, SYSDATE)
- GREATEST(no_work, updated_at)
)
FROM exclude_dates
WHERE no_work BETWEEN TRUNC(updated_at) AND SYSDATE
),
0
)
-- Subtract the holiday days.
FROM tickets;
Or, if you are not calculating using part days then you can simplify it to:
CREATE OR REPLACE VIEW business_day_ages (ID, AGE) AS
SELECT id,
( TRUNC( SYSDATE, 'IW' ) - TRUNC( updated_at, 'IW' ) ) * 5 / 7
-- Number of full weeks.
+ LEAST( TRUNC(SYSDATE) - TRUNC( SYSDATE, 'IW' ), 5 )
-- Add part weeks at the end.
- LEAST( updated_at - TRUNC( updated_at, 'IW' ), 5 )
-- Subtract part weeks at the start.
- COALESCE(
( SELECT 1
FROM exclude_dates
WHERE no_work BETWEEN TRUNC(updated_at) AND TRUNC(SYSDATE)
),
0
)
-- Subtract the holiday days.
FROM tickets;
db<>fiddle here

How to give a client's state by time

Table t_customer_statistics
trx_date - transaction date
cuid - id person(divide prospect and client)
lifecycle_status - this column must be filled
I need to give status to a client based on his condition
acquired - this month was the very first transaction
existing - there was a transaction last month
reactivated - there was no transaction last month
sleeping - there has been no transaction for the last 90 days (there have been no subsequent ones since the last transaction, more than 90 days)
I roughly made a code like this
UPDATE t_customer_statistics
SET Lifecycle_status =
case
when to_char (trunc (trx_date, 'mm'), 'mm.yyyy') = to_char (trunc (sysdate, 'mm'), 'mm.yyyy') then 'acquired'
when to_char (trunc (trx_date, 'mm'), 'mm.yyyy') = to_char (trunc (sysdate, 'mm') - 1, 'mm.yyyy') then 'existing'
when to_char (trunc (trx_date, 'mm'), 'mm.yyyy') = to_char (trunc (sysdate, 'mm') - 40, 'mm.yyyy') then 'reactivated'
when to_char (trunc (trx_date, 'mm'), 'mm.yyyy') <to_char (trunc (sysdate, 'mm') - 90, 'mm.yyyy') then 'sleeping'
end
but when they gave me an example, if the client made the first transaction and then fell asleep, then he has two states in the end, and sleeping must be separated so that there is a separate
PS. must be considered by transaction from the first and last
You could use a MERGE statement like this:
MERGE INTO clients dst
USING (
SELECT rowid rid,
LEAD(dt, 1) OVER (PARTITION BY id ORDER BY dt DESC) AS prev_dt,
LAG(dt, 1) OVER (PARTITION BY id ORDER BY dt DESC) AS next_dt
FROM clients
) src
ON ( dst.ROWID = src.rid )
WHEN MATCHED THEN
UPDATE
SET status = CASE
WHEN prev_dt IS NULL
THEN 'acquired'
WHEN MONTHS_BETWEEN(TRUNC(dst.dt, 'MM'), TRUNC(src.prev_dt)) <= 1
THEN 'existing'
ELSE 'reactivated'
END
||
CASE
WHEN COALESCE(src.next_dt, SYSDATE) >= dst.dt + INTERVAL '90' DAY
THEN ', sleeping'
END;
Which, for the sample data:
CREATE TABLE clients (id, dt, status ) AS
SELECT 1, DATE '2020-01-01', CAST( NULL AS VARCHAR2(20) ) FROM DUAL UNION ALL
SELECT 1, DATE '2020-02-01', CAST( NULL AS VARCHAR2(20) ) FROM DUAL UNION ALL
SELECT 1, DATE '2020-03-01', CAST( NULL AS VARCHAR2(20) ) FROM DUAL UNION ALL
SELECT 1, DATE '2020-05-01', CAST( NULL AS VARCHAR2(20) ) FROM DUAL UNION ALL
SELECT 1, DATE '2020-09-01', CAST( NULL AS VARCHAR2(20) ) FROM DUAL UNION ALL
SELECT 1, DATE '2020-10-01', CAST( NULL AS VARCHAR2(20) ) FROM DUAL UNION ALL
SELECT 1, DATE '2020-10-01' + INTERVAL '91' DAY, CAST( NULL AS VARCHAR2(20) ) FROM DUAL;
Then the result of the MERGE is:
ID
DT
STATUS
1
01-JAN-20
acquired
1
01-FEB-20
existing
1
01-MAR-20
existing
1
01-MAY-20
reactivated, sleeping
1
01-SEP-20
reactivated
1
01-OCT-20
existing, sleeping
1
31-DEC-20
reactivated, sleeping
db<>fiddle here

Get percentage of increase/decrease from two results base on their WHERE clause

I don't know if my Title is understandable but what I am really trying to achieve is get the percentage increase/decrease of Result 2 from Result 1.
I have a SALES table with these sample data:
DATE. AMOUNT
01-JAN-20. 500
02-JAN-20. 400
...
15-MAR-20. 1000
Assume that the table is filled with daily sales.
Now, with the Dashboard app I'm trying to make in APEX, I want to display the TOTAL AMOUNT base from the DATE the user will choose on a SELECT list (Today, This Week, This Month).
So basically, these would be the queries:
SELECT SUM(AMOUNT) TOTAL_SALES FROM TB_SALES WHERE DATE = TRUNC(SYSDATE); - Today
SELECT SUM(AMOUNT) TOTAL_SALES FROM TB_SALES WHERE DATE BETWEEN TRUNC(SYSDATE-7,'SUNDAY') AND TRUNC(SYSDATE); - This Week
SELECT SUM(AMOUNT) TOTAL_SALES FROM TB_SALES WHERE DATE BETWEEN TRUNC(SYSDATE, 'MM' AND TRUNC(SYSDATE); - This Month
To get the percentage of increase or decrease, I now then need to get the result of same queries but different DATE from the WHERE clause.
So with Yesterday, I should query
TRUNC(SYSDATE -1);
And so on. Actually I still don't know how to get the This week part.
With the two results, I can now calculate the percentage.
What I can't figure out is how can I query this two WHERE clause of two DATEs. My current temporary solution is to query the second query and just use JavaScript to update my Card.
I found something about LAG functions but cannot find any sample which uses WHERE clause to compare the results.
EDIT:
To be more clear, this is what I want to compare and get the percentage.
Today VS Yesterday
This week VS Last week
This month VS Last month
You can use conditional aggregation to find the totals:
SELECT SUM(
CASE
WHEN "DATE" >= TRUNC( SYSDATE )
AND "DATE" <= SYSDATE
THEN AMOUNT
ELSE NULL
END
) AS total_today,
SUM(
CASE
WHEN "DATE" >= TRUNC( SYSDATE ) - INTERVAL '1' DAY
AND "DATE" < TRUNC( SYSDATE )
THEN AMOUNT
ELSE NULL
END
) AS total_yesterday,
SUM(
CASE
WHEN "DATE" >= NEXT_DAY( TRUNC( SYSDATE ) - INTERVAL '7' DAY, 'SUNDAY' )
AND "DATE" <= SYSDATE
THEN AMOUNT
ELSE NULL
END
) AS total_this_week,
SUM(
CASE
WHEN "DATE" >= NEXT_DAY( TRUNC( SYSDATE ) - INTERVAL '14' DAY, 'SUNDAY' )
AND "DATE" < NEXT_DAY( TRUNC( SYSDATE ) - INTERVAL '7' DAY, 'SUNDAY' )
THEN AMOUNT
ELSE NULL
END
) AS total_last_week,
SUM(
CASE
WHEN "DATE" >= TRUNC( SYSDATE, 'IW' ),
AND "DATE" <= SYSDATE
THEN AMOUNT
ELSE NULL
END
) AS total_this_iso_week,
SUM(
CASE
WHEN "DATE" >= TRUNC( SYSDATE, 'IW' ) - INTERVAL '7' DAY,
AND "DATE" < TRUNC( SYSDATE, 'IW' )
THEN AMOUNT
ELSE NULL
END
) AS total_last_iso_week,
SUM(
CASE
WHEN "DATE" >= TRUNC( SYSDATE ) - INTERVAL '6' DAY,
AND "DATE" <= SYSDATE
THEN AMOUNT
ELSE NULL
END
) AS total_this_7_days,
SUM(
CASE
WHEN "DATE" >= TRUNC( SYSDATE ) - INTERVAL '13' DAY,
AND "DATE" < TRUNC( SYSDATE ) - INTERVAL '6' DAY
THEN AMOUNT
ELSE NULL
END
) AS total_last_7_days,
SUM(
CASE
WHEN "DATE" >= TRUNC( SYSDATE, 'MM' )
AND "DATE" <= SYSDATE
THEN AMOUNT
ELSE NULL
END
) AS total_this_month
SUM(
CASE
WHEN "DATE" >= ADD_MONTHS( TRUNC( SYSDATE, 'MM' ), -1 )
AND "DATE" < TRUNC( SYSDATE, 'MM' )
THEN AMOUNT
ELSE NULL
END
) AS total_this_month
FROM tb_sales
WHERE "DATE" >= ADD_MONTHS( TRUNC( SYSDATE, 'MM' ), -1 )
AND "DATE" <= SYSDATE;
This will find the totals for:
Today and yesterday;
This week and last week (starting from midnight Sunday until before the midnight of the next Sunday or until this instant, whichever is earlier);
This ISO 8601 week and last ISO 8601 week (starting midnight Monday);
The last 7 days (today and the previous 6 days) and 7 days before that; and
This month and last month (from midnight of the first day of the month until this instant).
If you want the percentage increases compared to a previous period then:
SELECT 100 * ( total_this_today / total_yesterday - 1 ) AS percent_increase_this_today,
100 * ( total_this_week / total_last_week - 1 ) AS percent_increase_this_week,
100 * ( total_this_iso_week / total_last_iso_week - 1 ) AS percent_increase_this_iso_week,
100 * ( total_this_7_days / total_last_7_days - 1 ) AS percent_increase_this_7_days,
100 * ( total_this_month / total_last_month - 1 ) AS percent_increase_this_month
FROM (
SELECT SUM(
CASE
WHEN "DATE" >= TRUNC( SYSDATE )
AND "DATE" <= SYSDATE
THEN AMOUNT
ELSE NULL
END
) AS total_today,
SUM(
CASE
WHEN "DATE" >= TRUNC( SYSDATE ) - INTERVAL '1' DAY
AND "DATE" < TRUNC( SYSDATE )
THEN AMOUNT
ELSE NULL
END
) AS total_yesterday,
SUM(
CASE
WHEN "DATE" >= NEXT_DAY( TRUNC( SYSDATE ) - INTERVAL '7' DAY, 'SUNDAY' )
AND "DATE" <= SYSDATE
THEN AMOUNT
ELSE NULL
END
) AS total_this_week,
SUM(
CASE
WHEN "DATE" >= NEXT_DAY( TRUNC( SYSDATE ) - INTERVAL '14' DAY, 'SUNDAY' )
AND "DATE" < NEXT_DAY( TRUNC( SYSDATE ) - INTERVAL '7' DAY, 'SUNDAY' )
THEN AMOUNT
ELSE NULL
END
) AS total_last_week,
SUM(
CASE
WHEN "DATE" >= TRUNC( SYSDATE, 'IW' ),
AND "DATE" <= SYSDATE
THEN AMOUNT
ELSE NULL
END
) AS total_this_iso_week,
SUM(
CASE
WHEN "DATE" >= TRUNC( SYSDATE, 'IW' ) - INTERVAL '7' DAY,
AND "DATE" < TRUNC( SYSDATE, 'IW' )
THEN AMOUNT
ELSE NULL
END
) AS total_last_iso_week,
SUM(
CASE
WHEN "DATE" >= TRUNC( SYSDATE ) - INTERVAL '6' DAY,
AND "DATE" <= SYSDATE
THEN AMOUNT
ELSE NULL
END
) AS total_this_7_days,
SUM(
CASE
WHEN "DATE" >= TRUNC( SYSDATE ) - INTERVAL '13' DAY,
AND "DATE" < TRUNC( SYSDATE ) - INTERVAL '6' DAY
THEN AMOUNT
ELSE NULL
END
) AS total_last_7_days,
SUM(
CASE
WHEN "DATE" >= TRUNC( SYSDATE, 'MM' )
AND "DATE" <= SYSDATE
THEN AMOUNT
ELSE NULL
END
) AS total_this_month
SUM(
CASE
WHEN "DATE" >= ADD_MONTHS( TRUNC( SYSDATE, 'MM' ), -1 )
AND "DATE" < TRUNC( SYSDATE, 'MM' )
THEN AMOUNT
ELSE NULL
END
) AS total_this_month
FROM tb_sales
WHERE "DATE" >= ADD_MONTHS( TRUNC( SYSDATE, 'MM' ), -1 )
AND "DATE" <= SYSDATE
)

Oracle: Days between two date and Exclude weekdays how to handle negative numbers

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
;

Find number of Wednesdays (or other weekday) in a month - Oracle SQL

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;

Resources