可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
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.