Get Max Date for prev months based on a given date - sql

I am trying to write a query that will give me the max date form Table_A based on the following condition.
If the given date (input parameter) falls between 01 April and 30 Sept of that year then return MAX(date) prior to April 1.
OR
If the given date (input parameter) falls between 01 Oct and 31 March of the following year then return MAX(date) prior to Oct 1.
For example :
Given date is 27th Dec, 2012 (date between 01 Oct and 31 March of the following year)
return 29th Sept, 2012
Given Date is 17th Aug, 2012 (date between 01 April and 30 Sept of the following year)
return date (MAX DATE prior to is 01 April is 29th March)

try this:
select max(case
when trunc(the_date) between to_date('01-apr-'||the_year, 'dd-mon-yyyy')
and to_date('30-sep-'||the_year, 'dd-mon-yyyy')
and dte < to_date('01-apr-'||the_year, 'dd-mon-yyyy')
then
dte
when trunc(the_date) between to_date('01-oct-'||the_year, 'dd-mon-yyyy')
and to_date('31-mar-'||(the_year+1), 'dd-mon-yyyy')
and dte < to_date('01-oct-'||the_year, 'dd-mon-yyyy')
then
dte
end) max_date
from table_a a
cross join (select v_inp_date the_date,
to_char(add_months(v_inp_date,-3), 'yyyy') the_year
from dual) dte;
so you input date is in the dte part
v_inp_date
and
to_char(add_months(v_inp_date,-3), 'yyyy') the_year
eg a small test:
SQL> create table table_a(dte date);
Table created.
SQL> insert into table_a values(to_date('28-sep-2012', 'dd-mon-yyyy'));
1 row created.
SQL> insert into table_a values(to_date('29-sep-2012', 'dd-mon-yyyy'));
1 row created.
SQL> insert into table_a values(to_date('28-mar-2012', 'dd-mon-yyyy'));
1 row created.
SQL> insert into table_a values(to_date('29-mar-2012', 'dd-mon-yyyy'));
1 row created.
SQL> insert into table_a values(to_date('28-sep-2011', 'dd-mon-yyyy'));
1 row created.
SQL> insert into table_a values(to_date('27-mar-2011', 'dd-mon-yyyy'));
1 row created.
SQL> commit;
Commit complete.
SQL> var inpdate varchar2(20);
SQL> exec :inpdate := '27-dec-2012';
PL/SQL procedure successfully completed.
SQL> select max(case
2 when trunc(the_date) between to_date('01-apr-'||the_year, 'dd-mon-yyyy')
3 and to_date('30-sep-'||the_year, 'dd-mon-yyyy')
4 and dte < to_date('01-apr-'||the_year, 'dd-mon-yyyy')
5 then
6 dte
7 when trunc(the_date) between to_date('01-oct-'||the_year, 'dd-mon-yyyy')
8 and to_date('31-mar-'||(the_year+1), 'dd-mon-yyyy')
9 and dte < to_date('01-oct-'||the_year, 'dd-mon-yyyy')
10 then
11 dte
12 end) max_date
13 from table_a a
14 cross join (select to_date(:inpdate,'dd-mon-yyyy') the_date,
15 to_char(add_months(to_date(:inpdate,'dd-mon-yyyy'), -3), 'yyyy') the_year
16 from dual) dte;
MAX_DATE
---------
29-SEP-12
SQL> exec :inpdate := '17-aug-2012';
PL/SQL procedure successfully completed.
SQL> /
MAX_DATE
---------
29-MAR-12
SQL> exec :inpdate := '01-oct-2011';
PL/SQL procedure successfully completed.
SQL> /
MAX_DATE
---------
28-SEP-11
SQL> exec :inpdate := '01-apr-2011';
PL/SQL procedure successfully completed.
SQL> /
MAX_DATE
---------
27-MAR-11
SQL>

The end date in my example is Dec-31-2012, you can extend it to 2013+. I'm building table on the fly-the max_date is printed for every row. Also, I hardcode max dates. You do not need to do any of this, this is just for example. Replace &1 and &2 with values w/out quotes...:
SELECT date_table --, start_date, end_date
, (CASE WHEN date_table BETWEEN To_Date('01-APR-2012') And To_Date('30-SEP-2012') Then max_apr_date
WHEN date_table BETWEEN To_Date('01-OCT-2012') And To_Date('31-DEC-2012') Then max_oct_date
END) max_date
FROM
(
SELECT *
FROM
(
SELECT TRUNC(SYSDATE,'Y')+LEVEL-1 date_table
, To_Date('&1') start_date
, To_Date('&2') end_date
, '29-MAR-2012' max_apr_date
, '29-Sep-2012' max_oct_date
FROM dual
CONNECT BY LEVEL <= (ADD_MONTHS(TRUNC(SYSDATE,'Y'),12)-TRUNC(SYSDATE,'Y') )
)
WHERE date_table BETWEEN start_date AND end_date
)
/

Related

(Oracle SQL) Need to get the startdate and enddate of a date column and return dates for each day

I have a date column and I need to return each day from the start date to the end date. For example if I have a date column that starts at 01-01-2020 till 22/03/2022. Then I want to return data such as:
DateColumn
01-JAN-20
02-JAN-20
03-JAN-20
and so on......
until 22-MAR-22
From Oracle 12:
SELECT t.id, d.day
FROM table_name t
CROSS JOIN LATERAL (
SELECT t.start_date + LEVEL - 1 AS day
FROM DUAL
CONNECT BY t.start_date + LEVEL - 1 <= t.end_date
) d
WHERE t.start_date <= t.end_date
Before Oracle 12, you can use:
SELECT t.id, d.COLUMN_VALUE AS day
FROM table_name t
CROSS JOIN TABLE(
CAST(
MULTISET(
SELECT t.start_date + LEVEL - 1
FROM DUAL
CONNECT BY t.start_date + LEVEL - 1 <= t.end_date
)
AS SYS.ODCIDATELIST
)
) d
WHERE t.start_date <= t.end_date
Or:
WITH dates (id, day, end_date) AS (
SELECT id, start_date, end_date
FROM table_name
WHERE start_date <= end_date
UNION ALL
SELECT id, day + 1, end_date
FROM dates
WHERE day + 1 <= end_date
)
SEARCH DEPTH FIRST BY id, day SET ord
SELECT id, day
FROM dates;
Which, for the sample data:
CREATE TABLE table_name (id, start_date, end_date) AS
SELECT 1, DATE '2021-01-01', DATE '2022-03-22' FROM DUAL UNION ALL
SELECT 2, DATE '2022-01-01', DATE '2022-01-10' FROM DUAL;
All output:
ID
DAY
1
01-JAN-21
1
02-JAN-21
...
...
1
21-MAR-22
1
22-MAR-22
2
01-JAN-22
2
02-JAN-22
...
...
2
09-JAN-22
2
10-JAN-22
db<>fiddle here
One option might also be
SQL> WITH
2 test (id, start_date, end_date)
3 AS
4 (SELECT 1, DATE '2020-01-01', DATE '2022-03-22' FROM DUAL)
5 SELECT start_date + LEVEL - 1 datecolumn
6 FROM test
7 CONNECT BY LEVEL <= end_date - start_date + 1
8 ORDER BY datecolumn;
DATECOLUMN
----------
01.01.2020
02.01.2020
03.01.2020
04.01.2020
<snip>
14.03.2022
15.03.2022
16.03.2022
17.03.2022
18.03.2022
19.03.2022
20.03.2022
21.03.2022
22.03.2022
812 rows selected.
SQL>
If there - as MT0 commented - is more than a single row in that table that contains start/end dates, then
WITH
test (id, start_date, end_date)
AS
(SELECT 1, DATE '2020-01-01', DATE '2022-03-22' FROM DUAL
UNION ALL
SELECT 2, DATE '2022-03-15', DATE '2022-03-23' FROM DUAL)
SELECT id, start_date + COLUMN_VALUE - 1 datecolumn
FROM test
CROSS JOIN
TABLE (
CAST (
MULTISET ( SELECT LEVEL
FROM DUAL
CONNECT BY LEVEL <= end_date - start_date + 1)
AS SYS.odcinumberlist))
ORDER BY id, datecolumn;
You can create a PIPELINED function, where you can just plug in the dates you want
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;
/
SELECT
c.COLUMN_VALUE
FROM
TABLE(generate_dates_pipelined(DATE '2022-01-01',
DATE '2022-01-30')) c

Calculate calendar days by month for date ranges

I have a table with date ranges:
CREATE TABLE REQUEST (
REQUEST_ID NUMBER(*,0) NOT NULL ENABLE,
EMPLOYEE_ID NUMBER(*,0) NOT NULL ENABLE,
START_DATE DATE NOT NULL ENABLE,
END_DATE DATE NOT NULL ENABLE,
CONSTRAINT REQUEST_PK PRIMARY KEY (REQUEST_ID)
);
The're also a couple of constraints (omitted for brevity) that ensure they're valid (end date cannot be less then start date) and force time to be 00:00:00. (Runnable fiddle with sample data).
Is there a way to split my data set by year/month into a result set based on the date ranges? The rules are:
A request splits in as many rows as months its range covers.
Each month has a column stating how many calendar days the range has in such month.
For example, a request with [2020-12-28, 2021-02-10] would produce three rows:
request_id year month days
========== ==== ===== ====
1 2020 12 4
1 2021 1 31
1 2021 2 10
I've been playing with CONNECT BY but I've been unable to adapt it to my use case. Is that the right tool?
A more direct way to do the computation is to use connect by to generate just the needed months (not every day in every interval) - and then to do the day computation directly, rather than by counting. Something like this:
Adding data for testing:
insert into request (request_id, employee_id, start_date, end_date)
select 1, 1001, date '2020-12-28', date '2021-02-10' from dual union all
select 2, 4002, date '2021-02-10', date '2021-02-20' from dual union all
select 3, 6004, date '2020-12-15', date '2021-03-31' from dual
;
commit;
Query and output:
with
prep (request_id, start_date, end_date, mth) as (
select request_id, start_date, end_date,
add_months(trunc(start_date, 'mm'), level - 1)
from request
connect by level <= months_between(trunc(end_date, 'mm'),
trunc(start_date, 'mm')) + 1
and prior request_id = request_id
and prior sys_guid() is not null
)
select request_id, extract(year from mth) as year_,
extract(month from mth) as month_,
least(last_day(mth), end_date) - greatest(mth, start_date) + 1 as days
from prep
order by request_id, mth -- if needed
;
REQUEST_ID YEAR_ MONTH_ DAYS
---------- ---------- ---------- ----------
1 2020 12 4
1 2021 1 31
1 2021 2 10
2 2021 2 11
3 2020 12 17
3 2021 1 31
3 2021 2 28
3 2021 3 31
For example this one:
WITH t AS (
SELECT DATE '2020-12-28' +LEVEL-1 AS ts
FROM dual
CONNECT BY DATE '2020-12-28' +LEVEL-1 <= DATE '2021-02-10')
SELECT
EXTRACT(YEAR FROM TRUNC(ts, 'Month')) AS YEAR,
EXTRACT(MONTH FROM TRUNC(ts, 'Month')) AS MONTH,
COUNT(ts) AS DAYS
FROM t
GROUP BY TRUNC(ts, 'Month')
ORDER BY YEAR, MONTH;

Problem with getting the quarter from a date in Oracle

I've written a query to get the start date of the quarters from current year and previous year by using the sysdate.
eg. Today falls in the 1st quarter of the year, therefore I only want to get the start date of 1st quarter of last year and this year.
If I'm on December (which is in the 4th quarter), I want to get the start dates of 8 quarters (4 from last year, 4 from this year.)
select b.dt,
to_number(to_char(SYSDATE, 'Q')),
to_number(to_char(b.dt, 'Q'))
from dual a,
(select add_months(trunc(ADD_MONTHS(TRUNC(SYSDATE, 'MM'), -12),
'yyyy'),
(rownum - 1) * 3) dt
from all_objects
where rownum <= 8
and add_months(trunc(ADD_MONTHS(TRUNC(SYSDATE, 'MM'), -12),
'yyyy'),
(rownum - 1) * 3) <= SYSDATE
and to_number(to_char(SYSDATE, 'Q')) >=
to_number(to_char(add_months(trunc(ADD_MONTHS(TRUNC(SYSDATE,
'MM'),
-12),
'yyyy'),
(rownum - 1) * 3),
'Q'))) b
This query only returns the start date of 1st quarter of last year. I expect to get the start date of the 1st quarter of this year as well.
Here's one option; see comments within the code.
SQL> alter session set nls_date_format = 'dd.mm.yyyy';
Session altered.
SQL> with
2 -- this mimics SYSDATE
3 today (datum) as
4 (select date '&date_literal' from dual),
5 -- which quarter does DATUM belong to? Find 1st day in "this" and "previous" year
6 quart as
7 (select trunc(datum, 'yyyy') this,
8 trunc(add_months(datum, -12), 'yyyy') previous,
9 to_char(datum, 'q') quart from today)
10 -- the fina result
11 select add_months(this, (level - 1) * 3) result
12 from quart
13 connect by level <= quart
14 union all
15 select add_months(previous, (level - 1) * 3) result
16 from quart
17 connect by level <= quart;
Enter value for date_literal: 2019-03-24
RESULT
----------
01.01.2019
01.01.2018
SQL> /
Enter value for date_literal: 2019-08-13
RESULT
----------
01.01.2019
01.04.2019
01.07.2019
01.01.2018
01.04.2018
01.07.2018
6 rows selected.
SQL>

Execute a oracle pl/sql query and return the result set based on the run time date value

I have below data
empid date amount
1 12-FEB-2017 10
1 12-FEB-2017 10
1 13-FEB-2017 10
1 14-FEB-2017 10
I need a query to return the total amount for a given id and date i.e, below result set
empid date amount
1 12-FEB-2017 20
1 13-FEB-2017 10
1 14-FEB-2017 10
but the think is, from the UI i will be getting the date as input.. if they pass the date return the result for that date .. if they dont pass the date return the result for most recent date.
below is the query that I wrote .. but it is working partially..
SELECT sum(amount),empid,date
FROM employee emp,
where
((date= :ddd) OR aum_valutn_dt = (select max(date) from emp))
AND emp.id = '1'
group by (empid,date)
Please help..
I think you could do something like this
but it is pretty bad you should try to do it some other way
it is doing extra work to get the most recent date
select amt, empid, date
from
(
select amt, empid, date, rank() over (order by date desc) date_rank
from
(SELECT sum(amount) amt,empid,date
FROM employee emp
where emp.id = '1'
and (date = :ddd or :ddd is null)
group by empid, date)
)
where date = :ddd or (:ddd is null and date_rank=1)
Here's another option; scans TEST table twice so ... mind the performance.
SQL> with test (empid, datum, amount) as
2 (select 1, date '2017-02-12', 10 from dual union all
3 select 1, date '2017-02-12', 10 from dual union all
4 select 1, date '2017-02-13', 10 from dual union all
5 select 1, date '2017-02-14', 10 from dual
6 )
7 select t.empid, t.datum, sum(t.amount) sum_amount
8 from test t
9 where t.datum = (select max(t1.datum)
10 from test t1
11 where t1.empid = t.empid
12 and (t1.datum = to_date('&&par_datum', 'dd.mm.yyyy')
13 or '&&par_datum' is null)
14 )
15 group by t.empid, t.datum;
Enter value for par_datum: 13.02.2017
EMPID DATUM SUM_AMOUNT
---------- ---------- ----------
1 13.02.2017 10
SQL> undefine par_datum
SQL> /
Enter value for par_datum:
EMPID DATUM SUM_AMOUNT
---------- ---------- ----------
1 14.02.2017 10
SQL>
SELECT sum(amount),empid,date
FROM employee emp,
where date =nvl((:ddd ,(select max(date) from emp))
AND emp.id = '1'
group by (empid,date)
My solution is following:
with t (empid, datum, amount) as
(select 1, date '2017-02-12', 10 from dual union all
select 1, date '2017-02-12', 10 from dual union all
select 1, date '2017-02-13', 10 from dual union all
select 1, date '2017-02-14', 10 from dual
)
select empid, datum, s
from (select empid, datum, sum(amount) s, max(datum) over (partition by empid) md
from t
group by empid, datum)
where datum = nvl(to_date(:p, 'yyyy-mm-dd'), md);
Calculate maximal date in the subquery and then, in outer subquery, compare the date with nvl(to_date(:p, 'yyyy-mm-dd'), md). If the paremeter is null, then the date field is compared with maximal date.

List of dates between two tables in oracle

Problem
Input:
START_DATE END_DATE
01-FEB-16 03-FEB-16
01-FEB-16 02-FEB-16
10-FEB-16 11-FEB-16
I want to generate all the dates between the start_day and end_day as
Output
01-FEB-16
02-FEB-16
03-FEB-16
10-FEB-16
11-FEB-16
You could do it using Row Generator technique.
Setup
SQL> CREATE TABLE t
2 (START_DATE DATE, END_DATE DATE
3 );
Table created.
SQL> INSERT INTO t VALUES(DATE '2016-02-01', DATE '2016-02-03');
1 row created.
SQL> INSERT INTO t VALUES(DATE '2016-02-01', DATE '2016-02-02');
1 row created.
SQL> INSERT INTO t VALUES(DATE '2016-02-10', DATE '2016-02-11');
1 row created.
SQL> COMMIT;
Commit complete.
Query
SQL> SELECT DISTINCT TO_CHAR(START_DATE+LEVEL-1, 'DD-MON-YYYY') the_date
2 FROM t
3 CONNECT BY LEVEL <= END_DATE-START_DATE+1
4 ORDER BY the_date
5 /
THE_DATE
-----------
01-FEB-2016
02-FEB-2016
03-FEB-2016
10-FEB-2016
11-FEB-2016
SQL>