Oracle IF statement for Fixed Year - sql

I have a weird condition while filtering oracle data:
if there are >=3 month already in the latest year, then only show data from this year and the last year. eg: now is May 2021, show 2020 Jan-Dec data and 2021 Jan-May data
if there are <3 month in the the latest year, then show data from the year before last year to now. eg: now is Jan 2021, show 2019 Jan-Dec data, 2020 Jan-Dec data and 2021 Jan
I have code like
SELECT * FROM DB
CASE
WHEN MDY_TIME BETWEEN
SELECT MAX(MDY_TIME, YEAR) AS MAX_DATE FROM DB)
WHERE MAX(MDY_TIME, MONTH)>='3'
THEN
I' sure I'm not using it right and I don't know if I'm even using the right field and don't know how to complete the conditions. This is my first time using SQL.
Data looks like:

I am assuming that the MDY_TIME column is a DATE data-type (if it is not then you should change it to be a DATE data-type.)
You want:
SELECT *
FROM DB
WHERE MDY_TIME >= CASE
WHEN EXTRACT( MONTH FROM SYSDATE ) < 3
THEN ADD_MONTHS( TRUNC( SYSDATE, 'YY' ), -24 )
ELSE ADD_MONTHS( TRUNC( SYSDATE, 'YY' ), -12 )
END
AND MDY_TIME < ADD_MONTHS( TRUNC( SYSDATE, 'MM' ), 1 )
Then for the sample data:
CREATE TABLE db (
mdy_time DATE,
year_month VARCHAR2(7)
GENERATED ALWAYS AS ( TO_CHAR( mdy_time, 'YYYY_MM' ) ),
year INTEGER
GENERATED ALWAYS AS ( EXTRACT(YEAR FROM mdy_time) ),
month INTEGER
GENERATED ALWAYS AS ( EXTRACT(MONTH FROM mdy_time) )
);
INSERT INTO db ( mdy_time )
SELECT ADD_MONTHS( TRUNC( SYSDATE, 'MM' ), 1 - LEVEL )
FROM DUAL
CONNECT BY LEVEL <= 36;
The output is:
MDY_TIME
YEAR_MONTH
YEAR
MONTH
2021-05-01 00:00:00
2021_05
2021
5
2021-04-01 00:00:00
2021_04
2021
4
2021-03-01 00:00:00
2021_03
2021
3
2021-02-01 00:00:00
2021_02
2021
2
2021-01-01 00:00:00
2021_01
2021
1
2020-12-01 00:00:00
2020_12
2020
12
2020-11-01 00:00:00
2020_11
2020
11
2020-10-01 00:00:00
2020_10
2020
10
2020-09-01 00:00:00
2020_09
2020
9
2020-08-01 00:00:00
2020_08
2020
8
2020-07-01 00:00:00
2020_07
2020
7
2020-06-01 00:00:00
2020_06
2020
6
2020-05-01 00:00:00
2020_05
2020
5
2020-04-01 00:00:00
2020_04
2020
4
2020-03-01 00:00:00
2020_03
2020
3
2020-02-01 00:00:00
2020_02
2020
2
2020-01-01 00:00:00
2020_01
2020
1
db<>fiddle here

I believe you're looking for something like the below
with max_date_sel as (select max(mdy_time) max_dt from DB)
select * from DB,
max_date_sel
where
case when extract(MONTH from max_dt) >= 3
and extract(YEAR from max_dt) - Year < 2 then 1
when extract(MONTH from max_dt) < 3
and extract(YEAR from max_dt) - Year < 3 then 1 end = 1

I would suggest:
where (extract(month from sysdate) < 3 and
mdy_time >= trunc(sysdate, 'YYYY') - interval '1' year
) or
mdy_time >= trunc(sysdate, 'YYYY')
If you want this based on the most recent year in the table, you can use window functions:
from (select db.*, max(mdy_time) over() as max_mdy_time
from db
) db
where (extract(month from max_mdy_time) < 3 and
mdy_time >= trunc(max_mdy_time, 'YYYY') - interval '1' year
) or
mdy_time >= trunc(max_mdy_time, 'YYYY')

Related

SQL query to check if there are records in the database of 6 consecutive 'Sundays'

I need to build a query to check if there are records in the database of 6 consecutive 'Sundays'
SELECT DISTINCT ST1.DATAPU, ST1.NUMCAD, TO_CHAR(ST1.DATAPU, 'DAY') AS DIA
FROM SENIOR.R066SIT ST1
WHERE ST1.DATAPU BETWEEN '01/01/22' AND '23/11/22'
AND ST1.NUMCAD = 10
AND TO_CHAR(ST1.DATAPU, 'FMDAY') = 'DOMINGO' -->which is SUNDAY in English
ORDER BY ST1.DATAPU ASC
With this query above, I get the result of the records as shown in the image below
From Oracle 12, you can use MATCH_RECOGNIZE to perform row-by-row pattern analysis:
SELECT *
FROM (
SELECT DISTINCT
TRUNC(DATAPU) AS datapu,
NUMCAD,
TO_CHAR(DATAPU,'DAY') AS DIA
FROM SENIOR.R066SIT
WHERE DATAPU BETWEEN DATE '2022-01-01' AND DATE '2022-11-23'
AND NUMCAD = 10
AND TRUNC(DATAPU) - TRUNC(DATAPU, 'IW') = 6 -- Sunday
)
MATCH_RECOGNIZE(
ORDER BY datapu
ALL ROWS PER MATCH
PATTERN (first_week consecutive_week{5,})
DEFINE
consecutive_week AS PREV(datapu) + INTERVAL '7' DAY = datapu
)
Which, for the sample data:
CREATE TABLE senior.r066sit(numcad, datapu) AS
SELECT 10, DATE '2022-01-01' + LEVEL - 1 FROM DUAL CONNECT BY LEVEL <= 5*7
UNION ALL
SELECT 10, DATE '2022-04-01' + LEVEL - 1 FROM DUAL CONNECT BY LEVEL <= 7*7
UNION ALL
SELECT 10, DATE '2022-08-01' + LEVEL - 1 FROM DUAL CONNECT BY LEVEL <= 7*7;
Outputs:
DATAPU
NUMCAD
DIA
2022-04-03 00:00:00
10
SUNDAY
2022-04-10 00:00:00
10
SUNDAY
2022-04-17 00:00:00
10
SUNDAY
2022-04-24 00:00:00
10
SUNDAY
2022-05-01 00:00:00
10
SUNDAY
2022-05-08 00:00:00
10
SUNDAY
2022-05-15 00:00:00
10
SUNDAY
2022-08-07 00:00:00
10
SUNDAY
2022-08-14 00:00:00
10
SUNDAY
2022-08-21 00:00:00
10
SUNDAY
2022-08-28 00:00:00
10
SUNDAY
2022-09-04 00:00:00
10
SUNDAY
2022-09-11 00:00:00
10
SUNDAY
2022-09-18 00:00:00
10
SUNDAY
Before Oracle 12, you can use multiple analytic functions in nested sub-queries:
SELECT datapu, numcad,
TO_CHAR(datapu, 'fmDAY') AS dia
FROM (
SELECT datapu, numcad,
COUNT(*) OVER (PARTITION BY grp) AS grp_size
FROM (
SELECT datapu, numcad,
SUM(consecutive) OVER (ORDER BY datapu) AS grp
FROM (
SELECT datapu, numcad,
CASE datapu - LAG(datapu) OVER (ORDER BY datapu)
WHEN 7
THEN 0
ELSE 1
END AS consecutive
FROM (
SELECT DISTINCT
TRUNC(DATAPU) AS datapu,
NUMCAD
FROM SENIOR.R066SIT
WHERE DATAPU BETWEEN DATE '2022-01-01' AND DATE '2022-11-23'
AND NUMCAD = 10
AND TRUNC(DATAPU) - TRUNC(DATAPU, 'IW') = 6 -- Sunday
)
)
)
)
WHERE grp_size >= 6;
fiddle

How do I get 1 week data from year 2018 based on 7 days based on this year in oracle?

I am trying to dynamically retrieve data based on dates. For example I want data from 7 days before today and same 7 days from 2020 dynamically.
I tried
SELECT *
FROM table_1
WHERE insert_date > TRUNC(SYSDATE) - 7 or ((insert_date< trunc(sysdate)-365) and (insert_date> trunc(sysdate)-372))
order by insert_date
The problem with this query is if I were to run this query now, this will give me correct data for 2020 and 2021. However if I were to run this same query in 2022, it will give me data from 2022 and 2021 when I want is data based on 2022 and 2020. I was hoping if someone could help me with this issue. I was able to figure out if I want to compare month to month but not week..
Thank you,
Sam
Presume that
today is (yyyy-mm-dd) 2021-04-08 which means that you'd want to return rows whose insert_date is
if you run the query "today", between
2021-04-01 and 2021-04-08
2020-04-01 and 2020-04-08
if you run the query "today next year" (i.e. 2022-04-08)
2022-04-01 and 2022-04-08
2020-04-01 and 2020-04-08
this is contents of your table:
SQL> select * from table_1 order by id;
ID INSERT_DAT Return in
---------- ----------
1 2021-04-08 -- 2021
2 2021-04-03 -- 2021
3 2021-04-02 -- 2021
4 2021-03-31
5 2020-04-05 -- 2021 and 2022
6 2020-04-04 -- 2021 and 2022
7 2020-04-12
8 2022-04-08 -- 2022
9 2022-03-30
9 rows selected.
Query; lines #4 and 5 make sure that query returns rows in year 2020:
SQL> select sysdate from dual;
SYSDATE
----------
2021-04-08
SQL> select a.*
2 from table_1 a
3 where a.insert_date between trunc(sysdate) - 7 and trunc(sysdate)
4 or a.insert_date between add_months(trunc(sysdate), -12 * (extract(year from sysdate) - 2020)) - 7
5 and add_months(trunc(sysdate), -12 * (extract(year from sysdate) - 2020))
6 order by a.insert_date desc;
ID INSERT_DAT
---------- ----------
1 2021-04-08
2 2021-04-03
3 2021-04-02
5 2020-04-05
6 2020-04-04
SQL>
Next year, on 2022-04-08, query would return
SQL> select sysdate from dual;
SYSDATE
----------
2022-04-08
SQL> select a.*
2 from table_1 a
3 where a.insert_date between trunc(sysdate) - 7 and trunc(sysdate)
4 or a.insert_date between add_months(trunc(sysdate), -12 * (extract(year from sysdate) - 2020)) - 7
5 and add_months(trunc(sysdate), -12 * (extract(year from sysdate) - 2020))
6 order by a.insert_date desc;
ID INSERT_DAT
---------- ----------
8 2022-04-08
5 2020-04-05
6 2020-04-04
SQL>
Didn't quite got the requirement. But, assuming you want to take 2020 as a standard date and get 7 days of data from today and the same 7 days from 2020.
I had used the substr to remove the year part and replace it with the standard year. As I had difficulty testing with 2022 date, took 2019 as my standard date.
select distinct cast(to_date(p.insert_date, 'DD-MON-YYYY') as timestamp) as insert_date,
cast(to_date(SYSDATE-7, 'DD-MON-YYYY') as timestamp) as tst_date
from table_1 p
where 1=1 and ( (to_date(p.insert_date, 'DD-MON-YYYY') > to_date(SYSDATE-7, 'DD-MON-YYYY') )
or (to_date(p.insert_date, 'DD-MON-YYYY')> trunc(to_date(substr(to_date(SYSDATE, 'DD-MON-YYYY'),1,7)||'19', 'DD-MON-YYYY')-7)
and to_date(p.insert_date, 'DD-MON-YYYY') <= trunc(to_date(substr(to_date(SYSDATE, 'DD-MON-YYYY'),1,7)||'19', 'DD-MON-YYYY')))
)
order by create_date desc;
-- Standard date for 2020
select substr(to_date(SYSDATE, 'DD-MON-YYYY'),1,7)||'20' from dual;

How to find out which months in a year range that's having 5 Saturdays between two given dates as per the inputs?

Only through Query mode, no execution block method.
i have tried the following but no luck
SELECT
TO_CHAR(dt, 'mon'),
COUNT(*) AS noofsat
FROM
(
SELECT
from_date + 1 AS dt
FROM
dual
CONNECT BY
level <= (
SELECT
TO_DATE - from_date
FROM
dual
)
)
WHERE
TO_CHAR(dt, 'd') = 7
GROUP BY
TO_CHAR(dt, 'mon')
HAVING
COUNT(*) = 5
i need only the month and year which has five Saturdays like August 2019
Change your date source to:
SELECT
TO_DATE('20190101','YYYYMMDD') + (LEVEL - 1) AS dt
FROM
dual
CONNECT BY
level <= 365
This is a very good question, In my situation I needed exactly the same thing but in SQL Server, and this is how I did it, I will post the answer for those that end up here looking for a SQL Server solution.
I ended up building a Date Dimension in my Data Warehouse and querying it:
DROP TABLE IF EXISTS ##Dates
CREATE TABLE ##Dates(DateValue Date,
YearValue INT,
MonthValue INT,
DayValue INT,
WeekDayValue INT)
DECLARE #start DATE = '2019-01-01'
WHILE #start < '2019-12-31'
BEGIN
INSERT INTO ##Dates(DateValue,
YearValue,
MonthValue,
DayValue,
WeekDayValue)
VALUES(#start,
DATEPART(YY,#start),
DATEPART(mm,#start), -- In oracle TO_CHAR(dt, 'mon')
DATEPART(dd,#start),
DATEPART(dw,#start)) --In oracle TO_CHAR(dt, 'd')
SET #start = DATEADD(dd,1,#start)
END
SELECT MonthValue, SUM(CASE WHEN WeekDayValue = 7 THEN 1 ELSE 0 END) AS NumOfSaturdays FROM ##Dates
WHERE DateValue BETWEEN '2019-01-01' AND '2019-12-01'
GROUP BY MonthValue
HAVING SUM(CASE WHEN WeekDayValue = 7 THEN 1 ELSE 0 END) > 4
Output:
MonthValue NumOfSaturdays
3 5
6 5
8 5
11 5
You could get the start of each month covered by your date range; this uses a pair of made-up fixed values as it isn't clear where you are getting your range from:
select add_months(trunc(date '2019-03-15', 'MM'), level - 1) as month_start
from dual
connect by level <= ceil(months_between(date '2019-11-21', date '2019-03-15'));
MONTH_STAR
----------
2019-03-01
2019-04-01
2019-05-01
2019-06-01
2019-07-01
2019-08-01
2019-09-01
2019-10-01
2019-11-01
9 rows selected.
You can then manipulate those dates in various ways. You can use the next_day() function to find the first Saturday of the month - based on the previous day, in case the 1st of the month is itself a Saturday. If you then add 28 days to that, you get the fifth Saturday of that month - or, for months with only four Saturdays, the first Saturday in the following month. So if you compare those generated Saturdays, if they are in the same month then that month must actually have five Saturdays.
select month_start,
next_day(month_start - 1, 'SATURDAY') as first_sat,
next_day(month_start - 1, 'SATURDAY') + 28 as poss_fifth_sat,
extract(month from month_start) as month_num,
extract(month from (next_day(month_start - 1, 'SATURDAY') + 28)) as poss_fifth_sat_month
from (
select add_months(trunc(date '2019-03-15', 'MM'), level - 1) as month_start
from dual
connect by level <= ceil(months_between(date '2019-11-21', date '2019-03-15'))
);
MONTH_STAR FIRST_SAT POSS_FIFTH MONTH_NUM POSS_FIFTH_SAT_MONTH
---------- ---------- ---------- ---------- --------------------
2019-03-01 2019-03-02 2019-03-30 3 3 -- same
2019-04-01 2019-04-06 2019-05-04 4 5
2019-05-01 2019-05-04 2019-06-01 5 6
2019-06-01 2019-06-01 2019-06-29 6 6 -- same
2019-07-01 2019-07-06 2019-08-03 7 8
2019-08-01 2019-08-03 2019-08-31 8 8 -- same
2019-09-01 2019-09-07 2019-10-05 9 10
2019-10-01 2019-10-05 2019-11-02 10 11
2019-11-01 2019-11-02 2019-11-30 11 11 -- same
9 rows selected.
You don't actually need to see those values, I'm just showing the working. But you can use the last two as a filter instead:
select month_start
from (
select add_months(trunc(date '2019-03-15', 'MM'), level - 1) as month_start
from dual
connect by level <= ceil(months_between(date '2019-11-21', date '2019-03-15'))
)
where extract(month from month_start)
= extract(month from (next_day(month_start - 1, 'SATURDAY') + 28));
MONTH_STAR
----------
2019-03-01
2019-06-01
2019-08-01
2019-11-01
You can then format those dates however you wish.
One possible issue (I'm sure there are others) is mentioned in the documentation:
The argument char must be a day of the week in the date language of your session, either the full name or the abbreviation.
So the code above will work as long as your session date language is English. If you can be 100% sure that everyone running this will always be using the same session settings then you use that fixed value (or whatever language is appropriate).
If you can't control that then you can't override the date language as you can with some other functions. As a workaround you could use a known Saturday date to obtain the current date language's name or abbreviation for Saturday, without you needing to know what the language actually is:
alter session set nls_date_language = 'Spanish';
select month_start
from (
select add_months(trunc(date '2019-03-15', 'MM'), level - 1) as month_start
from dual
connect by level <= ceil(months_between(date '2019-11-21', date '2019-03-15'))
)
where extract(month from month_start)
= extract(month from (next_day(month_start - 1, to_char(date '2000-01-01', 'Dy')) + 28));
MONTH_STAR
----------
2019-03-01
2019-06-01
2019-08-01
2019-11-01

Oracle count days per month

I wrote this SQL statement to calculate the days for each month
(select count(*) DAYs FROM
(
select trunc(ADD_MONTHS(sysdate,-1),'MM') + level -1 Dates from dual connect by
level <= ADD_MONTHS(trunc(sysdate,'MM'),1)-1 - trunc(sysdate,'MM')+1
) Where To_char(dates,'DY') NOT IN ('SA','SO'))
At the moment this statement ignores Saturdays and Sundays and it calculates the days from the month before the sysdate (June).
June has 22 days without weekends but sadly my statement says it has 23. I found out it includes the 1st July, which is wrong.
Do you know how I can tell my little statement it only calculates the days from the month I want to get not including days from another month?
Doing this sort of thing is always going to look not pretty... here's one way, which does it for the entire current year. You can restrict to a single month by adding an additional statement to the where clause:
select to_char(trunc(sysdate, 'y') + level - 1, 'fmMON') as month, count(*)
from dual
where to_char(trunc(sysdate, 'y') + level - 1, 'fmDY', 'nls_date_language=english') not in ('SAT','SUN')
connect by level <= trunc(add_months(sysdate, 12), 'y') - trunc(sysdate, 'y')
group by to_char(trunc(sysdate, 'y') + level - 1, 'fmMON')
As I said, not pretty.
Note a couple of things:
Use of the fm format model modifier to remove leading spaces
Explicit use of nls_date_language to ensure it'll work in all environments
I've added 12 months to the current date and then truncated it to the first of January to get the first day of the new year for simplicity
If you want to do this by month it might be worth looking at the LAST_DAY() function
The same statement (using LAST_DAY()) for the previous month only would be:
select count(*)
from dual
where to_char(trunc(sysdate, 'y') + level - 1, 'fmDY', 'nls_date_language=english') not in ('SAT','SUN')
connect by level <= last_day(add_months(trunc(sysdate, 'mm'), -1)) - add_months(trunc(sysdate, 'mm'), -1) + 1
Firstly, your inner query (select trunc(ADD_MONTHS(sysdate,-1),'MM') + level -1 Dates from dual connect by level <= ADD_MONTHS(trunc(sysdate,'MM'),1)-1 - trunc(sysdate,'MM')+1) returns the days of the month plus one extra day from the next month.
Secondly, a simpler query could use the LAST_DAY function which gets the last day of the month.
Finally, use the 'D' date format to get the day of the week as a number.
SELECT COUNT(*) FROM (
SELECT TO_CHAR(TRUNC(SYSDATE,'MM') + ROWNUM - 1, 'D') d
FROM dual CONNECT BY LEVEL <= TO_NUMBER(TO_CHAR(LAST_DAY(SYSDATE),'DD'))
) WHERE d BETWEEN 1 AND 5;
Without having to generate all days of the month and then count them:
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE FUNCTION WORK_DAYS_IN_MONTH(
dt DATE
) RETURN NUMBER DETERMINISTIC
AS
first_day DATE := TRUNC( dt, 'MM' );
remainder NUMBER := LAST_DAY( dt ) - ( first_day + INTERVAL '27' DAY );
BEGIN
RETURN 20 + CASE first_day - TRUNC( first_day, 'IW' )
WHEN 0 THEN remainder -- Monday
WHEN 1 THEN remainder -- Tuesday
WHEN 2 THEN remainder -- Wednesday
WHEN 3 THEN LEAST( remainder, 2 ) -- Thursday
WHEN 4 THEN LEAST( remainder, 1 ) -- Friday
WHEN 5 THEN GREATEST( remainder-2, 0 ) -- Saturday
ELSE GREATEST( remainder-1, 0 ) -- Sunday
END;
END;
//
Query 1:
SELECT ADD_MONTHS( DATE '2014-12-01', LEVEL ) AS "Month",
WORK_DAYS_IN_MONTH( ADD_MONTHS( DATE '2014-12-01', LEVEL ) ) AS "# Work Days"
FROM DUAL
CONNECT BY LEVEL <= 12
Results:
| Month | # Work Days |
|-----------------------------|-------------|
| January, 01 2015 00:00:00 | 22 |
| February, 01 2015 00:00:00 | 20 |
| March, 01 2015 00:00:00 | 22 |
| April, 01 2015 00:00:00 | 22 |
| May, 01 2015 00:00:00 | 21 |
| June, 01 2015 00:00:00 | 22 |
| July, 01 2015 00:00:00 | 23 |
| August, 01 2015 00:00:00 | 21 |
| September, 01 2015 00:00:00 | 22 |
| October, 01 2015 00:00:00 | 22 |
| November, 01 2015 00:00:00 | 21 |
| December, 01 2015 00:00:00 | 23 |

fiscal year date sequence generation

I have a table called fiscal year with column start_date,end_date(empty table), I want to insert records for each fiscal year till 2060
FISCAL_YEAR Start dt is Jul 1st, End dt is Jun 31st of next year
what i tried
select add_months(start_date ,-6),add_months(start_date ,6)-1 from (
select to_date('20000101','yyyymmdd') start_date from dual )
basis
how do i generate this sequence till 2060
Decription start_date end_date
FISCAL YEAR 2000 7/1/1999 6/30/2000
SQL> select
2 to_date('01-07-' || (1999 + rownum), 'dd.mm.yyyy') start_date,
3 to_date('30-06-' || (2000 + rownum), 'dd.mm.yyyy') finish_date
4 from dual
5 connect by level <= 10;
START_DATE FINISH_DATE
----------- -----------
01.07.2000 30.06.2001
01.07.2001 30.06.2002
01.07.2002 30.06.2003
01.07.2003 30.06.2004
01.07.2004 30.06.2005
01.07.2005 30.06.2006
01.07.2006 30.06.2007
01.07.2007 30.06.2008
01.07.2008 30.06.2009
01.07.2009 30.06.2010
10 rows selected
You can do it like this:
SELECT
ADD_MONTHS(DATE '1999-07-01', 12*(LEVEL-1)) as fiscal_year_begin,
ADD_MONTHS(DATE '1999-07-01', 12*LEVEL) - INTERVAL '1' DAY AS fiscal_year_end
FROM dual
CONNECT BY LEVEL < 60;
FISCAL_YEAR_BEGIN FISCAL_YEAR_END
1999-07-01 2000-06-30
2000-07-01 2001-06-30
2001-07-01 2002-06-30
2002-07-01 2003-06-30
2003-07-01 2004-06-30
2004-07-01 2005-06-30
2005-07-01 2006-06-30
2006-07-01 2007-06-30
2007-07-01 2008-06-30
2008-07-01 2009-06-30
...