How to put Case in Where Statement for Oracle SQL - sql

For the query below, I'm trying to pull a specific date range depending on the current day of the month. If it's the 20th or less (e.g. "2/7/2020") then I want the date range for January. Otherwise, I want the date range for February. Is it possible to be done with a case statement? Or there is a better way?
SELECT
account,
start_date,
amount
FROM
table1
WHERE
CASE
WHEN (
SELECT
CAST(EXTRACT(DAY FROM sysdate) AS NUMBER)
FROM
dual
) <= 20 THEN
start_date
BETWEEN '2020-01-01' AND '2020-01-31'
ELSE start_date BETWEEN '2020-02-01' AND '2020-02-29'
END

You can do this by avoiding the case statement and using truncate the date - 20 to the month, e.g.:
SELECT account,
start_date,
amount
FROM table1
WHERE start_date >= TRUNC(SYSDATE - 20, 'mm')
AND start_date < add_months(TRUNC(dt - 20, 'mm'), 1);
If you really had to use a CASE expression (you can't use a CASE statement in SQL), you would need to do something like:
SELECT account,
start_date,
amount
FROM table1
WHERE start_date >= CASE WHEN to_char(SYSDATE, 'dd') <= '20' THEN add_months(TRUNC(SYSDATE, 'mm'), -1) ELSE TRUNC(SYSDATE, 'mm') END
AND start_date < CASE WHEN to_char(SYSDATE, 'dd') <= '20' THEN TRUNC(SYSDATE, 'mm') ELSE add_months(TRUNC(SYSDATE, 'mm'), 1) END;
N.B. if you're using a function, you don't need to wrap it in a select .. from dual, you can use it directly in the SQL statement.
I've also assumed that you want a dynamic range, e.g. if the day of the month is 20 or less, the range is for the previous month, otherwise the current month.
ETA: You would use the above two queries if there is an index on the start_date column, otherwise you could simply do:
SELECT account,
start_date,
amount
FROM table1
WHERE TRUNC(start_date, 'mm') = TRUNC(SYSDATE - 20, 'mm');

Case statements return single values. As such you should pull out the start date and you'll need two case statements.
select account, start_date, amount
from table1 where
start_date between
(case
when (select cast(extract(day from sysdate) as number) from dual) <= 20 then '2020-01-01'
else '2020-02-01'
end) and
(case
when (select cast(extract(day from sysdate) as number) from dual) <= 20 then '2020-01-31'
else '2020-02-29'
end)

One method subtracts 20 days and then gets the month boundary:
where start_date >= trunc(sysdate - interval '20' day, 'MON') and
start_date < trunc(sysdate - interval '20' day, 'MON') + interval '1' month
This approach is index (and partition) friendly -- an appropriate index on start_date can be used. It is also safe if start_date has time components.
Note: You can use sysdate without having to use a subquery.

You can use or operator with last_day function as following:
Select * from your_table
Where (
start_date <= trunc(sysdate,'mm') + 20
and start_date between trunc(sysdate,'mm') - interval '1' month and trunc(sysdate,'mm') - 1
)
Or
(
start_date > trunc(sysdate,'mm') + 20
and start_date between trunc(sysdate, 'mm') and last_day(sysdate)
)
This approach will use index on start_date, if any.
Cheers!!

select account, amount, start_date
from table1
where ( ( (select cast (extract (day from sysdate) as number) from dual) <= 20
and start_date between date '2020-01-01' and date '2020-01-31')
or ( (select cast (extract (day from sysdate) as number) from dual) > 20
and start_date between date '2020-02-01' and date '2020-02-29')
);

Using CASE expressions as BETWEEN operands:
SELECT account
, start_date
, amount
FROM table1
WHERE start_date BETWEEN CASE
WHEN extract(day from sysdate) <= 20
THEN trunc(sysdate -interval '1' month, 'month')
ELSE trunc(sysdate, 'month')
END
AND CASE
WHEN extract(day from sysdate) <= 20
THEN last_day(sysdate -interval '1' month)
ELSE last_day(sysdate)
END

Related

ORACLE SQL QUERY SYSDATE

I need help with my query
select distinct count(item_number), creation_date
from EGP_SYSTEM_ITEMS_B ,
all I need to count the item number every month
for example
3-9-2020 count:29700
4-9-2020 count:29600
5-9-2020 Count:30000
and get the all date for the month and the previous month from creation_id or sysdate any of them
thanks
To count the number per month, you would aggregate by the month:
select trunc(creation_date, 'MON') as yyyymm, count(*)
from EGP_SYSTEM_ITEMS_B
group by trunc(creation_date, 'MON');
Your question is not entirely clear; perhaps you're trying to get
select TRUNC(CREATION_DATE), count(item_number)
from EGP_SYSTEM_ITEMS_B
WHERE TRUNC(CREATION_DATE)
IN (ADD_MONTHS(TRUNC(SYSDATE), -1),
TRUNC(SYSDATE),
ADD_MONTHS(TRUNC(SYSDATE), 1))
GROUP BY TRUNC(CREATION_DATE)
EDIT
Apparently OP wants a running monthly count, so something like:
WITH cteLimits (START_DATE, END_DATE)
AS (SELECT ADD_MONTHS(TRUNC(SYSDATE), -2), ADD_MONTHS(TRUNC(SYSDATE), -1) - INTERVAL '1' DAY FROM DUAL UNION ALL
SELECT ADD_MONTHS(TRUNC(SYSDATE), -1), TRUNC(SYSDATE) - INTERVAL '1' DAY FROM DUAL UNION ALL
SELECT TRUNC(SYSDATE), ADD_MONTHS(TRUNC(SYSDATE), 1) - INTERVAL '1' DAY FROM DUAL),
cteDay_totals
AS (SELECT TRUNC(CREATION_DATE) AS CREATION_DATE,
COUNT(*) AS DAY_TOTAL
FROM EGP_SYSTEM_ITEMS_B
GROUP BY TRUNC(CREATION_DATE))
SELECT l.START_DATE,
l.END_DATE,
SUM(d.DAY_TOTAL) AS MONTH_TOTAL
FROM cteLimits l
INNER JOIN cteDay_totals d
ON d.CREATION_DATE BETWEEN l.START_DATE AND l.END_DATE
GROUP BY l.START_DATE,
l.END_DATE

oracle query to report data only for the 1st or 2nd half of the year

I have a report which should display enrollment data only within 2 date ranges Jan-June or July-dec depending on current date.
Scenarios:
If the current date is 042020 then I should display enrollement data between this range: 072019-122019
If the current date is 072020 then I should display enrollement data between this range: 012020-062020
If the current date is 022021 then I should display enrollement data between this range: 072020-122020
Current query reports everything past 6 months with his query.
select * from enrollement where enrollement_dt > add_months(sysdate - 6);
Is there any function available in oracle to do the same or how do i get the logic in a single statement?
Any help with this is highly appreciated.
You may try below query -
select *
from enrollement
WHERE TO_CHAR(enrollement_dt, 'MMYYYY') >= CASE WHEN TO_CHAR(SYSDATE, 'mm') <= '06'
THEN TO_DATE('07' || EXTRACT(YEAR FROM SYSDATE) - 1, 'MMYYYY')
ELSE THEN TO_DATE('01' || EXTRACT(YEAR FROM SYSDATE), 'MMYYYY')
END
AND TO_CHAR(enrollement_dt, 'MMYYYY') <= CASE WHEN TO_CHAR(SYSDATE, 'mm') <= '06'
THEN TO_DATE('12' || EXTRACT(YEAR FROM SYSDATE) - 1, 'MMYYYY')
ELSE THEN TO_DATE('06' || EXTRACT(YEAR FROM SYSDATE), 'MMYYYY')
END
Basically you want to truncate to the half-year. But Oracle doesn't support this.
One method counts half-years and compares them. You want the previous half year from the current date. That would be:
select (extract(year from sysdate) * 2 + floor(extract(month from sysdate) - 1) / 6) - 1
from dual
You can use this same formula:
where (extract(year from enrollement_dt) * 2 + floor(extract(month from enrollement_dt) - 1) / 6) - 1 =
extract(year from sysdate) * 2 + floor(extract(month from sysdate) - 1) / 6) - 1
)
from dual;
Unfortunately that can't use an index on the column. So, we can revisit this. You can get the first day of the current half using some date arithmetic:
select trunc(sysdate, 'Q') - mod(floor((extract(month from sysdate) - 1) / 3), 2) * interval '3' month
from dual
That just needs to be plugged into a where clause:
where enrollement_dt >= trunc(sysdate, 'Q') - mod(floor((extract(month from sysdate) - 1) / 3), 2) * interval '3' month - interval '6' month and
enrollement_dt < trunc(sysdate, 'Q') - mod(floor((extract(month from sysdate) - 1) / 3), 2) * interval '3' month
Voila! An expression that can even use an index.
You can use the below to get the start date and end date for enrollment
WITH data
AS (SELECT TRUNC(SYSDATE) curr_date from dual
),
d2
AS (SELECT curr_date,
To_date('0107'
||( Extract (year FROM curr_date) - 1 ), 'ddmmyyyy')
start_first_half,
To_date('3112'
||( Extract (year FROM curr_date) - 1 ), 'ddmmyyyy')
end_first_half,
To_date('0101'
||Extract (year FROM curr_date), 'ddmmyyyy')
start_second_half,
To_date('3006'
||Extract (year FROM curr_date), 'ddmmyyyy')
end_second_half
FROM data)
SELECT curr_date,
CASE
WHEN To_char(curr_date, 'MM') >= To_char(start_first_half, 'MM')
AND To_char(curr_date, 'MM') <= To_char(end_first_half, 'MM') THEN
start_second_half
ELSE start_first_half
END start_date1,
CASE
WHEN To_char(curr_date, 'MM') >= To_char(start_first_half, 'MM')
AND To_char(curr_date, 'MM') <= To_char(end_first_half, 'MM') THEN
end_second_half
ELSE end_first_half
END end_date1
FROM d2
You can use it in your query like below
Select * from enrollment_table a, (WITH data
AS (SELECT TRUNC(SYSDATE) curr_date from dual
),
d2
AS (SELECT curr_date,
To_date('0107'
||( Extract (year FROM curr_date) - 1 ), 'ddmmyyyy')
start_first_half,
To_date('3112'
||( Extract (year FROM curr_date) - 1 ), 'ddmmyyyy')
end_first_half,
To_date('0101'
||Extract (year FROM curr_date), 'ddmmyyyy')
start_second_half,
To_date('3006'
||Extract (year FROM curr_date), 'ddmmyyyy')
end_second_half
FROM data)
SELECT curr_date,
CASE
WHEN To_char(curr_date, 'MM') >= To_char(start_first_half, 'MM')
AND To_char(curr_date, 'MM') <= To_char(end_first_half, 'MM') THEN
start_second_half
ELSE start_first_half
END start_date1,
CASE
WHEN To_char(curr_date, 'MM') >= To_char(start_first_half, 'MM')
AND To_char(curr_date, 'MM') <= To_char(end_first_half, 'MM') THEN
end_second_half
ELSE end_first_half
END end_date1
FROM d2 ) b
where a.enrollment_date >=b.start_date1
and a.enrollment_date <=b.end_date1

Oracle SQL - How to retrieve the ID Count difference between today vs yesterday

I have a table that captures when a customer purchases a product. It captures a unique purchase id along with a timestamp of when the purchase was made.
I want to be able to query, the difference between how many purchases were taken today vs yesterday?
Not sure how to query this on oracle?
You can use conditional aggregation:
select sum(case when trunc(datecol) = trunc(sysdate - 1) then 1 else 0 end) as num_yesterday,
sum(case when trunc(datecol) = trunc(sysdate) then 1 else 0 end) as num_today,
sum(case when trunc(datecol) = trunc(sysdate) then 1
when trunc(datecol) = trunc(sysdate - 1) then -1
end) as diff
from t
where datecol >= trunc(sysdate - 1);
you can use the Group function to grouping the purchase day with timestamp information and count the purchase id.
select trunc(purchase_ts) Day, count(purchase_id) Count
from purchase
group by trunc(purchase_ts)
order by 1
Using TRUNC on the column will prevent Oracle from using an index on that column (instead you would need a separate function-based index); instead use a CASE statement to test whether the date is between the start of the day and the start of the next day and then COUNT the values between those ranges:
SELECT COUNT(
CASE
WHEN TRUNC( SYSDATE ) - INTERVAL '1' DAY <= your_date_column
AND your_date_coumn < TRUNC( SYSDATE )
THEN 1
END
) AS count_for_yesterday,
COUNT(
CASE
WHEN TRUNC( SYSDATE ) <= your_date_column
AND your_date_coumn < TRUNC( SYSDATE ) + INTERVAL '1' DAY
THEN 1
END
) AS count_for_today
FROM your_table
WHERE TRUNC( SYSDATE ) - INTERVAL '1' DAY <= your_date_column
AND your_date_coumn < TRUNC( SYSDATE ) + INTERVAL '1' DAY

Current status based on week number ORACLE

I got this query on order to get all the days from the first day of the year (01/01/2018) to the end of next year (31/12/2019).
SELECT MYDATE,
TO_CHAR(NR_OF_SUNDAYS + 1,'FM09') WEEK_NUM,
FROM
(
SELECT MYDATE,
( (TRUNC(MYDATE,'DAY') - TRUNC(TRUNC(MYDATE,'YYYY'),'DAY')) / 7 ) +
CASE WHEN TO_CHAR(TRUNC(MYDATE,'YYYY'),'DAY') = 'SUN' THEN 1 ELSE 0 END AS NR_OF_SUNDAYS
FROM
( SELECT TRUNC (SYSDATE, 'YY') - 1 + LEVEL AS MYDATE
FROM DUAL
CONNECT BY LEVEL <= TRUNC (ADD_MONTHS (SYSDATE, 24), 'YY') -
TRUNC (SYSDATE, 'YY')
)
)
I need a column that specifies the following cases:
1) CASE WHEN MYDATE < TO_CHAR(SYSDATE, 'DD/MM/YYYY') THEN 'PAST DUE'
(this works its easy and no problem)
2) if my current =< mydate
week_num then 'CURRENT WEEK'(Excluding PAST DUE)
3) if my current week + one week then
'NEXT WEEK' (Excluding PAST DUE)
4) else FUTURE
Thanks a lot for your help.
So, in my answer I tried retain the logic behind your week number calculation.
However keep in mind that you could calculate week number using oracle to_char(date,'WW'), to_char(date,'IW'), to_char(date,'W') functions and then your life would be easier.
WW Week of year (1-53) where week 1 starts on the first day of the year and continues to the seventh day of the year.
W Week of month (1-5) where week 1 starts on the first day of the month and ends on the seventh.
IW Week of year (1-52 or 1-53) based on the ISO standard.
Having said all that here is my solution that uses only sql (note that defining and using a function would be a lot easier), based on your calculation method.
with date_table as (
SELECT MYDATE, to_number(TO_CHAR(NR_OF_SUNDAYS + 1,'FM09')) WEEK_NUM, to_number(to_char(MYDATE+1,'IW')) as nu
FROM
(
SELECT MYDATE,
( (TRUNC(MYDATE,'DAY') - TRUNC(TRUNC(MYDATE,'YYYY'),'DAY')) / 7 ) +
CASE WHEN TO_CHAR(TRUNC(MYDATE,'YYYY'),'DY', 'NLS_DATE_LANGUAGE = american') = 'SUN' THEN 1 ELSE 0 END AS NR_OF_SUNDAYS
FROM
( SELECT TRUNC (SYSDATE, 'YY') - 1 + LEVEL AS MYDATE
FROM DUAL
CONNECT BY LEVEL <= TRUNC (ADD_MONTHS (SYSDATE, 24), 'YY') -TRUNC (SYSDATE,'YY')
)
)
),
todays_week as
(
select distinct WEEK_NUM from date_table
where trunc(sysdate)=trunc(mydate)
),
pre_final as (
select MYDATE,WEEK_NUM, (select WEEK_NUM from todays_week) as todaysweek from date_table)
select MYDATE,sysdate,WEEK_NUM,todaysweek,
case when trunc(MYDATE) < trunc(sysdate) then 'PAST DUE'
when todaysweek = WEEK_NUM and abs(MYDATE-sysdate)<=7 then 'CURRENT WEEK'
when todaysweek +1 = WEEK_NUM and abs(MYDATE-sysdate)<=14 then 'Next Week'
else 'Future' end as description
from pre_final;
The main idea is to find today's week number and then use case when.
Here is my fiddle link with the results.
http://sqlfiddle.com/#!4/3149e4/148
EDIT 1:
Now, similar results one could achive with something like this:
select res.*,
case when trunc(MYDATE) < trunc(sysdate) then 'PAST DUE'
when todaysweek = WEEK_NUM and abs(MYDATE-sysdate)<=7 then 'CURRENT WEEK'
when todaysweek +1 = WEEK_NUM and abs(MYDATE-sysdate)<=14 then 'Next Week'
else 'Future' end as description
from (
SELECT MYDATE, to_number(to_char(MYDATE,'IW')) as WEEK_NUM,to_number(to_char(sysdate,'IW')) as todaysweek
FROM
( SELECT TRUNC (SYSDATE, 'YY') - 1 + LEVEL AS MYDATE
FROM DUAL
CONNECT BY LEVEL <= TRUNC (ADD_MONTHS (SYSDATE, 24), 'YY') -TRUNC (SYSDATE,'YY')
)) res

Current Financial Year to sysdate

I can't seem to find a straightforward sql without delving into PL SQL for always bringing current financial year in which case 01-04-2015 to sysdate. I want this to always update automatically so when it comes next financial year in 01/04/2016 it will bring whatever is held from that date to whenever the report is being run.
If anyone can please shed some light for me. thanks
sql is:
SELECT
PROPERTY.PRO_MANAGINGCOMPANY_DESCR,
PROPERTY.PRO_SCHEME_DESCR,
PROPERTY.PRO_SCHEME,
SUM(REPAIR_CURRENT.REP_ESTIMATED_COST) as "Estimated Cost",
nvl(SUM(REPAIR_CURRENT.REP_INVOICED_COST),SUM(REPAIR_CURRENT.REP_ESTIMATED_COST)) as "Estimated Cost Invoiced",
SUM(REPAIR_CURRENT.REP_INVOICED_COST) as "Invoice Cost",
to_char(REPAIR_CURRENT.REP_RAISED_DATE,'Mon') as "Month",
to_number(to_char(to_date(REPAIR_CURRENT.REP_RAISED_DATE,'dd-mon-yy'),'mm')) as "Month No."
FROM
PROPERTY,
REPAIR_CURRENT,
SERVICE_REQUEST
WHERE
( SERVICE_REQUEST.SRQ_PRO_REFNO=PROPERTY.PRO_REFNO )
AND ( REPAIR_CURRENT.REP_SRQ_NO=SERVICE_REQUEST.SRQ_NO )
AND
(
--PROPERTY.PRO_SCHEME = ( '00054' )
--AND
REPAIR_CURRENT.REP_RAISED_DATE BETWEEN '01-APR-2015' AND sysdate
AND
REPAIR_CURRENT.REP_STATUS <> 'CAN'
)
GROUP BY
PROPERTY.PRO_MANAGINGCOMPANY_DESCR,
PROPERTY.PRO_SCHEME_DESCR,
PROPERTY.PRO_SCHEME,
to_char(REPAIR_CURRENT.REP_RAISED_DATE,'Mon'),
to_number(to_char(to_date(REPAIR_CURRENT.REP_RAISED_DATE,'dd-mon-yy'),'mm'))
If you just want to get the beginning of the fiscal year for the current date:
SELECT TO_DATE('01-04' || CASE
WHEN EXTRACT(MONTH FROM SYSDATE) > 4 THEN
EXTRACT(YEAR FROM SYSDATE)
ELSE
EXTRACT(YEAR FROM SYSDATE)-1
END, 'DD-MM-RRRR') FISCAL_YEAR
FROM DUAL
This works for any date:
REPAIR_CURRENT.REP_RAISED_DATE
BETWEEN Add_Months(Trunc(Add_Months(sysdate,-3),'YYYY'),3)
AND Sysdate
Basically, subtract three months, truncate to the year, and add three months back on.
To just get the financial year for a date, use:
Extract(Year from Add_Months(Trunc(Add_Months(sysdate,-3),'YYYY'),3))
SELECT *
FROM your_table
WHERE datetime >= CASE
WHEN SYSDATE < TRUNC( SYSDATE, 'YEAR' ) + INTERVAL '3' MONTH
THEN TRUNC( SYSDATE, 'YEAR' ) - INTERVAL '9' MONTH
ELSE TRUNC( SYSDATE, 'YEAR' ) + INTERVAL '3' MONTH
END;
Thank you, the following worked! add_months(trunc(sysdate,'year'),3) AND sysdate
thank you all for your input :)
REPAIR_CURRENT.REP_RAISED_DATE BETWEEN '01-APR-2015' AND sysdate
Firstly, '01-APR-2015' is not a DATE it is a string. You must always use TO_DATE along with proper format model to explicitly convert the string into DATE. Or, use the ANSI Date literal as you are not concerned with the time portion. It uses a fixed format 'YYYY-MM-DD'.
Now, coming to your date arithmetic, you could use a CASE expression to evaluate the financial date depending on the year.
REP_RAISED_DATE
BETWEEN
CASE
WHEN
SYSDATE < ADD_MONTHS(TRUNC(SYSDATE, 'YEAR'), 3)
THEN
ADD_MONTHS(TRUNC(SYSDATE, 'YEAR') , -9)
ELSE
ADD_MONTHS(TRUNC(SYSDATE, 'YEAR'), 3)
END
AND SYSDATE
Basically, SYSDATE >= ADD_MONTHS(TRUNC(SYSDATE, 'YEAR'), 3) is to check whether SYSDATE is greater than 1-APR of current year. And, SYSDATE < ADD_MONTHS(TRUNC(SYSDATE, 'YEAR'), 15) is to check whether it is between JAN and MARCH of next year.
For example,
SQL> SELECT
2 CASE
3 WHEN
4 SYSDATE < ADD_MONTHS(TRUNC(SYSDATE, 'YEAR'), 3)
5 THEN
6 ADD_MONTHS(TRUNC(SYSDATE, 'YEAR') ,-9)
7 ELSE
8 ADD_MONTHS(TRUNC(SYSDATE, 'YEAR'), 3)
9 END FINANCIAL_YEAR
10 FROM dual;
FINANCIAL
---------
01-APR-15
For date between JAN and MAR of next year:
SQL> SELECT
2 CASE
3 WHEN
4 DATE '2016-02-01' < ADD_MONTHS(TRUNC(DATE '2016-02-01', 'YEAR'), 3)
5 THEN
6 ADD_MONTHS(TRUNC(DATE '2016-02-01', 'YEAR') ,-9)
7 ELSE
8 ADD_MONTHS(TRUNC(DATE '2016-02-01', 'YEAR'), 3)
9 END FINANCIAL_YEAR
10 FROM dual;
FINANCIAL
---------
01-APR-15
Following SQLreturns start and end date for Financial Year of current date.
SELECT
TO_DATE('01-04' || EXTRACT(YEAR FROM add_months(sysdate, -3)),'DD-MM-RRRR') from_dt ,
TO_DATE('31-03' || EXTRACT(YEAR FROM add_months(sysdate, 9)),'DD-MM-RRRR') to_dt from dual;
For any random date, you can use the following SQL: example for 01-04-2020
SELECT
TO_DATE('01-04' || EXTRACT(YEAR FROM add_months(to_date('01-04-2020','DD-MM-RRRR'), -3)),'DD-MM-RRRR') from_dt ,
TO_DATE('31-03' || EXTRACT(YEAR FROM add_months(to_date('01-04-2020','DD-MM-RRRR'), 9)),'DD-MM-RRRR') to_dt from dual;