Oracle SQL - get number of days between two dates for a specified month

匿名 (未验证) 提交于 2019-12-03 01:37:02

问题:

I want to get the number of days between two dates but only for a specified month.

Sample Data:

datefrom  dateto     28/1/2016 15/2/2016  10/2/2016 3/3/2016   5/2/2016  16/2/2016  20/1/2016 10/3/2016

Expected output for February:

datefrom  dateto    numofdays 28/1/2016 15/2/2016 15 10/2/2016 3/3/2016  19 5/2/2016  16/2/2016 11 20/1/2016 10/3/2016 29

Expected output for January with same dates:

datefrom  dateto    numofdays 28/1/2016 15/2/2016 4 10/2/2016 3/3/2016  0 5/2/2016  16/2/2016 0 20/1/2016 10/3/2016 11

Expected output for March with same dates:

datefrom  dateto    numofdays 28/1/2016 15/2/2016 0 10/2/2016 3/3/2016  3 5/2/2016  16/2/2016 0 20/1/2016 10/3/2016 10

The month will be selected by a parameter (when output into excel via ODBC) by a month number 1 to 12.

回答1:

You can expand each date range into its component days, either with a hierarchical query or a recursive CTE; and then count how many match the specified month:

with r (datefrom, dateto, dateday) as (   select datefrom, dateto, datefrom   from t   union all select datefrom, dateto, dateday + 1   from r   where dateday < dateto ) select datefrom, dateto,   to_char(to_date(:monthnum, 'MM'), 'MON', 'NLS_DATE_LANGUAGE=ENGLISH') as month,   count(case when to_char(dateday, 'MM') = :monthnum then dateday end) as numofdays from r group by datefrom, dateto order by datefrom, dateto;

Using 1, 2 and 3 in turn for the bind variable generates:

DATEFROM  DATETO    MONTH         NUMOFDAYS --------- --------- ------------ ---------- 20-JAN-16 10-MAR-16 JAN                  12 28-JAN-16 15-FEB-16 JAN                   4 05-FEB-16 16-FEB-16 JAN                   0 10-FEB-16 03-MAR-16 JAN                   0  DATEFROM  DATETO    MONTH         NUMOFDAYS --------- --------- ------------ ---------- 20-JAN-16 10-MAR-16 FEB                  29 28-JAN-16 15-FEB-16 FEB                  15 05-FEB-16 16-FEB-16 FEB                  12 10-FEB-16 03-MAR-16 FEB                  20  DATEFROM  DATETO    MONTH         NUMOFDAYS --------- --------- ------------ ---------- 20-JAN-16 10-MAR-16 MAR                  10 28-JAN-16 15-FEB-16 MAR                   0 05-FEB-16 16-FEB-16 MAR                   0 10-FEB-16 03-MAR-16 MAR                   3

Alternatively you can generate all the days for the specified month and get the intersect of both sets.

As you're only specifying the month, not the year, you're assuming a range will never span more than a year - or if it does, that you want to count days in the months that appear in both years.

It's also assuming the same date range doesn't appear more than once; if it does then the grouping and counting will be off. Presumably your real data has a key that you haven't shown, which could be included in the CTE and the group-by clause to avoid that.



回答2:

February is not a month, it is the generic name of a month in a year. A "month" in the proper sense is February 2016, or February 2017 etc. Based on your desired output, I assume you mean February 2016.

The problem is trivial. However you define the month, you can identify the first and the last day of the month. For example, if you input the month as a six-character string: input = '201602', then you can use something like

to_date(input, 'yyyymm')                as month_start,  last_day(to_date(input, 'yyyymm'))      as month_end

and then compute number of days like this:

Preparation (in SQLPlus):

SQL> variable input varchar2(30) SQL> exec :input := '201602';  PL/SQL procedure successfully completed.  SQL> alter session set nls_date_format = 'dd/mm/yyyy';

Query:

with      test_dates ( datefrom, dateto ) as (        select to_date('28/1/2016', 'dd/mm/yyyy'), to_date('15/2/2016', 'dd/mm/yyyy') from dual union all        select to_date('10/2/2016', 'dd/mm/yyyy'), to_date('3/3/2016' , 'dd/mm/yyyy') from dual union all        select to_date('5/2/2016' , 'dd/mm/yyyy'), to_date('16/2/2016', 'dd/mm/yyyy') from dual union all        select to_date('20/1/2016', 'dd/mm/yyyy'), to_date('10/3/2016', 'dd/mm/yyyy') from dual      ) --  end of test data; solution (SQL query) begins below this line select t.datefrom, t.dateto, to_char(to_date(:input, 'yyyymm'), 'MON yyyy') as month,        case when t.datefrom > m.month_end or t.dateto < m.month_start then 0             else least(t.dateto, m.month_end) - greatest(t.datefrom, m.month_start) + 1             end as number_of_days from   test_dates t cross join                    ( select to_date(:input, 'yyyymm') as month_start,                            last_day(to_date(:input, 'yyyymm')) as month_end                      from   dual) m ;

Output: (Note: the numbers in your "desired output" are incorrect)

DATEFROM   DATETO     MONTH    NUMBER_OF_DAYS ---------- ---------- -------- -------------- 28/01/2016 15/02/2016 FEB 2016             15 10/02/2016 03/03/2016 FEB 2016             20 05/02/2016 16/02/2016 FEB 2016             12 20/01/2016 10/03/2016 FEB 2016             29  4 rows selected.


标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!