Sql 以月份为单位的Oracle日期
我想得到两个日期之间的月份表,其中包含两个日期所涵盖的每个月的一小部分 例如,如果开始日期为2017年1月15日,结束日期为2017年3月1日,它将输出:Sql 以月份为单位的Oracle日期,sql,oracle,date-arithmetic,Sql,Oracle,Date Arithmetic,我想得到两个日期之间的月份表,其中包含两个日期所涵盖的每个月的一小部分 例如,如果开始日期为2017年1月15日,结束日期为2017年3月1日,它将输出: 01/2017 : 0.5483.. 02/2017 : 1 03/2017: 0.0322.. 其中,1月和3月的计算值分别为17/31和1/31。我目前有以下问题: WITH dates_between as (SELECT ADD_MONTHS(TRUNC(TO_DATE(:givenStartDate,'dd/mm/yyyy'),
01/2017 : 0.5483..
02/2017 : 1
03/2017: 0.0322..
其中,1月和3月的计算值分别为17/31和1/31。我目前有以下问题:
WITH dates_between as (SELECT ADD_MONTHS(TRUNC(TO_DATE(:givenStartDate,'dd/mm/yyyy'), 'MON'), ROWNUM - 1) date_out
FROM DUAL
CONNECT BY ADD_MONTHS(TRUNC(TO_DATE(:givenStartDate,'dd/mm/yyyy'), 'MON'), ROWNUM - 1)
<= TRUNC(TO_DATE(:givenEndDate,'dd/mm/yyyy'), 'MON')
)
select * from dates_between
这将在两个日期之间输出每个月,并将其格式设置为月初。我只需要另一个专栏给我开始和结束日期的分数。我不知道有什么方法可以做到这一点而不让它变得混乱。以下是我将如何做到这一点的注意事项。我已将您的日期扩展到多行,纯粹出于演示目的。如果只使用一组参数,则无需执行以下操作:
WITH params AS (SELECT 1 ID, '15/01/2017' givenstartdate, '01/03/2017' givenenddate FROM dual UNION ALL
SELECT 2 ID, '15/01/2017' givenstartdate, '23/01/2017' givenenddate FROM dual UNION ALL
SELECT 3 ID, '01/01/2017' givenstartdate, '07/04/2017' givenenddate FROM dual),
dates_between AS (SELECT ID,
to_date(givenstartdate, 'dd/mm/yyyy') givenstartdate,
to_date(givenenddate, 'dd/mm/yyyy') givenenddate,
add_months(trunc(to_date(givenstartdate, 'dd/mm/yyyy'), 'MON'), LEVEL - 1) start_of_month,
last_day(add_months(trunc(to_date(givenstartdate, 'dd/mm/yyyy'), 'MON'), LEVEL - 1)) end_of_month
FROM params
CONNECT BY add_months(trunc(to_date(givenstartdate, 'dd/mm/yyyy'), 'MON'), LEVEL - 1) <=
trunc(to_date(givenenddate, 'dd/mm/yyyy'), 'MON')
AND PRIOR ID = ID
AND PRIOR sys_guid() IS NOT NULL)
SELECT ID,
givenstartdate,
givenenddate,
start_of_month date_out,
end_of_month,
months_between(LEAST(givenenddate, end_of_month) + 1, GREATEST(start_of_month, givenstartdate))
FROM dates_between;
ID GIVENSTARTDATE GIVENENDDATE DATE_OUT END_OF_MONTH DIFF
注意:根据您的要求,您可能需要添加案例陈述,以决定是否要在差异计算中添加1。试试这个
对于第一个月,我计算了剩余天数/总天数,而对于上个月,我将其减去1,得到通过天数/总天数
根据每月31天计算结果的分数部分。这意味着,如果你的范围开始或结束于不到31天的一个月,那么你得到的分数可能与你预期的不一样:
select months_between(date '2017-04-02', date '2017-04-01') as calc from dual
CALC
----------
.0322580645
。。。是1/31,不是1/30。要获得0.0333。。。相反,您需要计算每个月的天数,至少是第一个月和最后一个月的天数。这使用了一个递归的CTE 11gR2+来获取月份,使用另一个CTE提供的两个日期范围作为演示来显示差异。当然,您也可以使用分层查询:
with ranges (id, start_date, end_date) as (
select 1, date '2017-01-15', date '2017-03-01' from dual
union all select 2, date '2017-01-31', date '2017-03-01' from dual
union all select 3, date '2017-02-28', date '2017-04-01' from dual
),
months (id, month_start, month_days, range_start, range_end) as (
select id,
trunc(start_date, 'MM'),
extract(day from last_day(start_date)),
start_date,
end_date
from ranges
union all
select id,
month_start + interval '1' month,
extract(day from last_day(month_start + interval '1' month)),
range_start,
range_end
from months
where month_start < range_end
)
select id,
to_char(month_start, 'YYYY-MM-DD') as month_start,
month_days,
case when month_start = trunc(range_start, 'MM')
then month_days - extract(day from range_start) + 1
when month_start = trunc(range_end, 'MM')
then extract(day from range_end)
else month_days end as range_days,
(case when month_start = trunc(range_start, 'MM')
then month_days - extract(day from range_start) + 1
when month_start = trunc(range_end, 'MM')
then extract(day from range_end)
else month_days end) / month_days as fraction
from months
order by id, month_start;
第一个CTE范围只是演示数据。第二个递归CTE月生成每个月的开始日期和天数,同时跟踪原始范围日期。最后一个查询只是根据范围内的月份天数与该月份的总天数计算分数
月份天数和范围天数仅显示在输出中,以便您可以查看计算依据,您可以从实际结果中忽略这些,并根据需要设置月份开始日期的格式
对于原始的一对绑定变量,等效变量为:
with months (month_start, month_days, range_start, range_end) as (
select trunc(to_date(:givenstartdate, 'DD/MM/YYYY'), 'MM'),
extract(day from last_day(to_date(:givenstartdate, 'DD/MM/YYYY'))),
to_date(:givenstartdate, 'DD/MM/YYYY'),
to_date(:givenenddate, 'DD/MM/YYYY')
from dual
union all
select month_start + interval '1' month,
extract(day from last_day(month_start + interval '1' month)),
range_start,
range_end
from months
where month_start < range_end
)
select to_char(month_start, 'MM/YYYY') as month,
(case when month_start = trunc(range_start, 'MM')
then month_days - extract(day from range_start) + 1
when month_start = trunc(range_end, 'MM')
then extract(day from range_end)
else month_days end) / month_days as fraction
from months
order by month_start;
MONTH FRACTION
------- --------
01/2017 0.5483
02/2017 1
03/2017 0.0322
有时候它真的很简单:你的逻辑中有一个问题。如果三月份的百分比是1/31,那么一月份的百分比不是应该是15/31而不是16/31吗?这是范围内的百分比,因为2017年1月15日是开始日期,一个月还剩下16天。2017年3月1日和3月3日是结束日期,因此3月只涵盖1天。所以开始日期不包括在范围内?如果包括在内,1月15日至31日为17天。如果范围从2017年1月31日开始,那么分数将显示0?此外,如果范围扩大到4月份会怎样;4月的某一天应该是1/30还是1/31,因为这两个月的间隔会给你带来什么?啊,是的,谢谢你指出这一点,我忽略了这一点——它是包容的。我会更新这个问题。因此,如果范围从31日开始,则为该月的1/31。理想情况下,这将是一个月中的几天,因此,正如您所指出的,四月份的天数将是1/30,这两个月之间的天数不能真正用于解决这一问题。真遗憾,再简单不过了。不过效果很好,干杯!
select months_between(date '2017-04-02', date '2017-04-01') as calc from dual
CALC
----------
.0322580645
with ranges (id, start_date, end_date) as (
select 1, date '2017-01-15', date '2017-03-01' from dual
union all select 2, date '2017-01-31', date '2017-03-01' from dual
union all select 3, date '2017-02-28', date '2017-04-01' from dual
),
months (id, month_start, month_days, range_start, range_end) as (
select id,
trunc(start_date, 'MM'),
extract(day from last_day(start_date)),
start_date,
end_date
from ranges
union all
select id,
month_start + interval '1' month,
extract(day from last_day(month_start + interval '1' month)),
range_start,
range_end
from months
where month_start < range_end
)
select id,
to_char(month_start, 'YYYY-MM-DD') as month_start,
month_days,
case when month_start = trunc(range_start, 'MM')
then month_days - extract(day from range_start) + 1
when month_start = trunc(range_end, 'MM')
then extract(day from range_end)
else month_days end as range_days,
(case when month_start = trunc(range_start, 'MM')
then month_days - extract(day from range_start) + 1
when month_start = trunc(range_end, 'MM')
then extract(day from range_end)
else month_days end) / month_days as fraction
from months
order by id, month_start;
ID MONTH_STAR MONTH_DAYS RANGE_DAYS FRACTION
------ ---------- ---------- ---------- --------
1 2017-01-01 31 17 0.5483
1 2017-02-01 28 28 1
1 2017-03-01 31 1 0.0322
2 2017-01-01 31 1 0.0322
2 2017-02-01 28 28 1
2 2017-03-01 31 1 0.0322
3 2017-02-01 28 1 0.0357
3 2017-03-01 31 31 1
3 2017-04-01 30 1 0.0333
with months (month_start, month_days, range_start, range_end) as (
select trunc(to_date(:givenstartdate, 'DD/MM/YYYY'), 'MM'),
extract(day from last_day(to_date(:givenstartdate, 'DD/MM/YYYY'))),
to_date(:givenstartdate, 'DD/MM/YYYY'),
to_date(:givenenddate, 'DD/MM/YYYY')
from dual
union all
select month_start + interval '1' month,
extract(day from last_day(month_start + interval '1' month)),
range_start,
range_end
from months
where month_start < range_end
)
select to_char(month_start, 'MM/YYYY') as month,
(case when month_start = trunc(range_start, 'MM')
then month_days - extract(day from range_start) + 1
when month_start = trunc(range_end, 'MM')
then extract(day from range_end)
else month_days end) / month_days as fraction
from months
order by month_start;
MONTH FRACTION
------- --------
01/2017 0.5483
02/2017 1
03/2017 0.0322