return the month in between sql - sql

I want to ask is there any way in PL/SQL that I can return the months in between user input.
For example, the user put in
DateStart: 01/2019 and
DateEnd: 05/2019
The query should return
01/2019, 02/2019, 03/2019, 04/2019,05/2019.

You can do this in sql, no pl/sql needed for this.
Convert month/year to a date, 1st of the month.
Then count the months between the first and last argument.
Then select doing a connect by level + 2 (because you're including the boundary values). All put together this gives:
WITH test_data (monthyear_start, monthyear_end) AS
(
SELECT '01/2019', '05/2019' FROM DUAL
),
monthcount(months) AS
(
SELECT MONTHS_BETWEEN(TO_DATE('01/'||monthyear_end,'DD/MM/YYYY'),TO_DATE('01/'||monthyear_start,'DD/MM/YYYY')) FROM test_data
)
SELECT TO_CHAR(ADD_MONTHS(TO_DATE('01/'||monthyear_start,'DD/MM/YYYY'),LEVEL - 1),'MM/YYYY') FROM test_data, monthcount
CONNECT BY LEVEL < months + 2;
01/2019
02/2019
03/2019
04/2019
05/2019

You can firstly use connect by level <= syntax, and then apply listagg() function to concatenate the strings :
with t2 as
(
select distinct to_char( to_date(DateStart,'mm/yyyy') + level - 1 , 'mm/yyyy') as mnt
from t
connect by level <= to_date(DateEnd,'mm/yyyy') - to_date(DateStart,'mm/yyyy') + 1
)
select listagg(mnt,',') within group
(order by to_number(substr(mnt,-4)||substr(mnt,1,2))) as "Months"
from t2
Demo

Similar, but yet different, maybe even simpler than previous suggestions:
SQL> with test (dstart, dend) as
2 (select '01/2019', '05/2019' from dual)
3 select to_char(add_months(to_date(dstart, 'mm/yyyy'), level - 1), 'mm/yyyy') result
4 from test
5 connect by
6 level <= months_between(to_date(dend, 'mm/yyyy'), to_date(dstart, 'mm/yyyy')) + 1;
RESULT
-------
01/2019
02/2019
03/2019
04/2019
05/2019
SQL>

Here is a PL/SQL procedure and some test code.
DECLARE
PROCEDURE p_list_months(start_date IN DATE, end_date IN DATE) AS
v_count INTEGER := 0;
BEGIN
v_count := MONTHS_BETWEEN(end_date, start_date);
FOR i in 0..v_count LOOP
dbms_output.put_line(to_char(add_months(start_date, i),'mm/yyyy'));
END LOOP;
END;
BEGIN
p_list_months(SYSDATE, ADD_MONTHS(SYSDATE, 5));
END;
And a SQL query:
-- start_date 10/2019 end_date 10/2020
SELECT to_char(add_months(TO_DATE('10/2019','mm/yyyy'), LEVEL-1), 'mm/yyyy') dat
FROM dual
CONNECT BY LEVEL <= (MONTHS_BETWEEN( TO_DATE('10/2020','mm/yyyy'),
TO_DATE('10/2019','mm/yyyy'))) + 1;

Related

Printing a pattern of alphanumeric numbers in Oracle sql

How can I create a temp table in a sql select statement which will hold list of alphanumeric numbers.
Select yearlyQuarters from temp;
2023Q1
2022Q4
2022Q3
2022Q2
2022Q1
2021Q4
2021Q3
2021Q2
2021Q1
......
I tried creating the temp data as
WITH t(n) AS (
SELECT 1900 from dual
UNION ALL
SELECT n+1 from t WHERE n < 3000)
SELECT * FROM t;
I am not sure how can I add the quarter details to the numbers.
You can cross-join the result with the quarters. For example:
WITH t(n) AS (
SELECT 2000 from dual
UNION ALL
SELECT n+1 from t WHERE n < 2003)
SELECT t.n || 'Q' || y.q
FROM t
cross join (
select 1 as q from dual
union all select 2 from dual
union all select 3 from dual
union all select 4 from dual
) y
Result:
T.N||'Q'||Y.Q
-------------
2000Q1
2000Q2
2000Q3
2000Q4
2001Q1
2001Q2
2001Q3
2001Q4
2002Q1
2002Q2
2002Q3
2002Q4
2003Q1
2003Q2
2003Q3
2003Q4
See example at db<>fiddle.
Start with the date 1900-01-01 and repeatedly add 3 months to get the next quarter and then use TO_CHAR to format it:
SELECT TO_CHAR(
ADD_MONTHS(DATE '1900-01-01', 3 * LEVEL - 3),
'YYYY"Q"Q'
) AS yearly_quarters
FROM DUAL
CONNECT BY ADD_MONTHS(DATE '1900-01-01', 3 * LEVEL - 3) <= SYSDATE;
fiddle

Oracle SQL: Compare Date Ranges and get the dates of the same day

I have different events:
If more than 2 of them take place in one day, I would like to know which ones and how many that are.
How do I build the SQL command?
Expected output:
More than 2 events on the same day.
Try this:
WITH calendar
AS ( SELECT TRUNC( SYSDATE - LEVEL ) AS cal_day
FROM DUAL
CONNECT BY LEVEL < 30)
SELECT calendar.cal_day,
COUNT( e.event_id ) AS number_of_events,
LISTAGG( e.event_id, ', ' ) WITHIN GROUP (ORDER BY e.date_from)
AS events
FROM calendar, event e
WHERE calendar.cal_day BETWEEN e.date_from AND e.date_until
GROUP BY calendar.cal_day
HAVING COUNT( e.event_id ) > 1;
You can always change number in CONNECT BY LEVEL < :n or materialize calendar as a table.
sample input:
create table ns_123(a int,b date,c date);
insert into ns_123 values(179,'27-sep-2018','27-sep-2018');
insert into ns_123 values(181,'26-sep-2018','28-sep-2018');
insert into ns_123 values(180,'27-sep-2018','27-sep-2018');
select * from ns_123;
select distinct n1.a from ns_123 n1,ns_123 n2 where (n1.b-n2.b)>=(n1.c-n2.c);
sample output:
179
180
181

select records with a 6 month interval

I want to select with an oracle sql statement the records with a 6 month time interval.
Example
01/06/2011 AMOUNT
01/12/2011 AMOUNT
01/06/2012 AMOUNT
01/12/2012 AMOUNT
And so on
How can I do this with oracle sql?
select ADD_MONTHS(trunc(sysdate), (rownum - 1) * 6) some_date
from dual
connect by level <= 5;
SOME_DATE
-----------
18.04.2014
18.10.2014
18.04.2015
18.10.2015
18.04.2016
WITH got_r_num AS
(
SELECT t.* -- OR WHATEVER YOU WANT
, DENSE_RANK () OVER ( PARTITION BY TRUNC (created_date, 'MONTH')
ORDER BY TRUNC (created_date) -- DESC
) AS r_num
FROM test_table
WHERE MOD ( MONTHS_BETWEEN ( TRUNC (SYSDATE)
, TRUNC (created_date)
)
, 6
) = 0
)
SELECT * -- or list all columns except r_num
FROM got_r_num
WHERE r_num = 1
;
Have a look here please .
If you need to sum of all records of 6 months in the AMOUNT field:
You can subquery with sum function and query with CONNECT BY LEVEL
SELECT x AS l_date,
(
SELECT sum(your_data)
FROM your_table
WHERE table_date >= x
AND table_date < add_months(x,6)
)AMOUNT
FROM(
SELECT add_months(to_date('01/06/2011','dd/mm/yyyy'),(LEVEL-1)*6) x
FROM dual
CONNECT BY LEVEL <= 4
);

Counting business days between two dates for each row in a table

I'm trying to implement this askTom solution to count the business days between two date fields in a table.
select count(*)
from ( select rownum rnum
from all_objects
where rownum <= to_date('&1') - to_date('&2')+1 )
where to_char( to_date('&2')+rnum-1, 'DY' ) not in ( 'SAT', 'SUN' )
I don't know how I can pass values to toms code or how to do a work around so that each time the select executes with a different set of dates and that way obtaining a similar output :
rowid | bussiness_days
I guess this could be implemented with a PL/SQL block but I'd prefer to keep it down to a query if possible. Could it be possible to pass values using &1 parameters from a select above toms one?
This is not like the original askTom, but if you're using 11gR2 you can use a Recursive CTE:
with rcte(a,b, i, is_wd) as (
select from_dt , to_dt , id, case when (to_char(from_dt, 'DY') in ('SAT','SUN')) then 0 else 1 end from t
union all
select decode(a, null, a,a+1), b, i, case when (to_char(a, 'DY') in ('SAT','SUN')) then 0 else 1 end
from rcte
where a+1 <= b
)
select i id, sum(is_wd)
from rcte
group by i
where t is a table containing "from_dates" and "to_dates"
Here is a sqlfiddle demo
Try this one:
SELECT COUNT(*)
FROM ( SELECT ROWNUM rnum
FROM all_objects
WHERE ROWNUM <= TO_DATE('2014-02-07','yyyy-mm-dd') - TO_DATE('2014-02-01','yyyy-mm-dd')+1 )
WHERE TO_CHAR( TO_DATE('2014-02-01','yyyy-mm-dd')+rnum-1, 'DY' ) NOT IN ( 'SAT', 'SUN' )
However, it has at least two issues:
It presumes the session NLS_DATE_LANGUAGE is set to English
The query gives wrong result if number of rows in ALL_OBJECTS is smaller than the amount of days between your ranges.
The version is less error-prone and faster:
WITH t AS
(SELECT TO_DATE('2014-02-01','yyyy-mm-dd')+LEVEL-1 the_date
FROM dual
CONNECT BY TO_DATE('2014-02-01','yyyy-mm-dd')+LEVEL-1 <= TO_DATE('2014-02-07','yyyy-mm-dd'))
SELECT COUNT(*)
FROM t
WHERE TO_CHAR(the_date, 'DY', 'NLS_DATE_LANGUAGE = AMERICAN') NOT IN ( 'SAT', 'SUN' )
This is another situation where a calendar table comes in handy. A calendar table has a row for every date.
If Saturday and Sunday are not business days, it's quite likely that holidays are not either. A field in the calendar table to indicate holidays makes it a lot easier to exclude them from your business days calculation.

How to repeat select query when date within range?

I have a table with a column "Date". The date will be displayed in a calendar in a cyclic form. For example the records date will be shown in the calendar in a certain day each week till a specific date (let's say TerminationDate). To summarize in my table I have the Date and the TerminationDate columns like this:
Table:
Title | Date | TerminationDate
------------------------------
t1 | d1 | td1
and I want to achieve something like this:
From query:
Title | Date | TerminationDate
------------------------------
t1 | d1+7 | td1
t1 | d1+14| td1
t1 | d1+21| td1
.................... till Date < TerminationDate
Does anyone have any idea how to achieve this in Oracle?
This should do the trick
select distinct title, date + ( level * 7 ), termination_date
from table
connect by date + ( level * 7 ) < termination_date
EDIT:
Forget about above query, since the rows must be connected only with itself there has to be
connect_by prior title = title
but that means a loop must be created. Unfortunately Oracle connect by clause throws an error if there is a loop whatsoever. Even if you use
date + ( level * 7 ) < termination_date
Oracle still stops execution immediately where it detects a loop at runtime. Using nocycle returns the result, but that returns only the first record which is date + 7
ANSWER:
So i had to approach to the problem in a different way
select t.*, date + (r * 7) as the_date
from table t,
(select rownum as r
from dual
connect by level < (select max(weeks) --max date interval as weeks to be used to repeat each row as much, if you know the max weeks difference you can use the constant number instead of this sub-query
from (select ceil((termination_date - date) / 7) as weeks
from table ))
)
where r < ceil((termination_date - date) / 7)
Let me know is there is any conufsion or performance problem
I have not tested the query ,but it should work like as you need
SELECT t1, d1 + (7 * LEVEL), termination_date
FROM tab
WHERE d1 + (7 * LEVEL) < termination_date
CONNECT BY LEVEL <= CEIL( (termination_date - d1) / 7);
EDIT
SELECT DISTINCT t1,dt,termination_date
FROM(
SELECT t1, d1 + (7 * LEVEL) dt, termination_date
FROM tab
WHERE d1 + (7 * LEVEL) < termination_date
CONNECT BY LEVEL <= CEIL( (termination_date - d1) / 7)
);
Here's one more way to do it
SELECT
*,
date + ( ROWNUM * 7 ) as modified_date
FROM (
SELECT
title,
date,
termination_date
FROM
table
) WHERE date + ( ROWNUM * 7 ) < termination_date