Sql 如何从日期计算费率

Sql 如何从日期计算费率,sql,oracle,plsql,Sql,Oracle,Plsql,我正在创建一个函数,它将根据费率和日期之间的天数计算金额 通过循环,我需要检查哪个速率适合v_日期 CREATE TABLE INTEREST_RATE_TAB ( ID VARCHAR2(4 BYTE) NOT NULL, NAME VARCHAR2(100 BYTE) NOT NULL, RATE NUMBER NOT NUL

我正在创建一个函数,它将根据费率和日期之间的天数计算金额

通过循环,我需要检查哪个速率适合v_日期

CREATE TABLE INTEREST_RATE_TAB
(

  ID          VARCHAR2(4 BYTE)               NOT NULL,
  NAME          VARCHAR2(100 BYTE)             NOT NULL,
  RATE          NUMBER                         NOT NULL,
  START_DATE  DATE                           NOT NULL,
  END_DATE     DATE
)


Insert into INTEREST_RATE_TAB
   (ID, NAME, RATE, START_DATE, END_DATE)
Values
   ('1', 'RATE ', 1.2, TO_DATE('01/01/1999 00:00:00', 'MM/DD/YYYY HH24:MI:SS'), TO_DATE('12/23/2016 00:00:00', 'MM/DD/YYYY HH24:MI:SS'));
Insert into INTEREST_RATE_TAB
   (ID, NAME, RATE, START_DATE, END_DATE)
Values
   ('2', 'RATE II', 0.2, TO_DATE('12/24/2016 00:00:00', 'MM/DD/YYYY HH24:MI:SS'), TO_DATE('11/21/2017 00:00:00', 'MM/DD/YYYY HH24:MI:SS');
Insert into INTEREST_RATE_TAB
   (ID, NAME, RATE, START_DATE, END_DATE )
Values
   ('3', 'RATE III', 1.2, TO_DATE('11/22/2017 00:00:00', 'MM/DD/YYYY HH24:MI:SS'));
例如:

如果仅涵盖一个期间,例如,如果v_日期为2000年1月1日,并且 付款日期为2000年1月3日。然后循环中的v_数量将为1.2* 60 03.01.2000-01.01.2000

但如果它包含两个或多个时段,例如v_date为 2016年1月12日,v_日期付款为2018年1月1日,则循环的v_金额为1,2*23 2016年12月23日-2016年1月12日,然后v_金额为0.2* 332 2017年11月21日-2016年12月24日,然后v_金额=1,2*39 2018年1月1日 -2017年11月22日


我想我知道你需要什么。不需要循环,因为一个SQL就可以解决这个问题。提示:停止思考程序步骤,即循环。相反,要考虑集体处理的数据集

第一件事是要意识到你有4个条件来产生全部或部分期望的结果。如下图所示:

LET 
  Rs ==> rate.start_date
  Re ==> rate.end_date
  Vd ==> v_date 
  Vp ==> v_date_payout 

Case 1:  Vd>=Rs & Vp<=Re              Days = Vp-Vd
     Rs--------------------------Re
         Vd---------------Vp

 Case 2:  Vd<=Rs & (Vp>=Rs & Vp<=Re)   Days = Vp-Rs
       Rs--------------------------Re
  Vd---------------Vp
  
Case 3:  Vd>=Rs # Vp>=Re             Days = Re-Vd
     Rs--------------------------Re
                      Vd---------------Vp

 Case 4: Vd<Rs & Vp>=Re              Days Re-Rs
       Rs--------------------------Re
 Vd-------------------------------------Vp            

Other Cases: Exhibits 2 cases where Vd---Vp lies totally outside Rs----Re. 
  These will generate NO amount and so not selected for calculation.
             Rs--------------Re
  Vd----Vp                        Vd----Vp
解决方案包括两个步骤:首先,确定范围Vd--Ve和范围Rs--Re之间的重叠。在此步骤中,还提取要应用该速率的速率和天数。第二,对于每个贡献范围,计算每个范围的部分贡献,可以从范围Vp--Ve的最终和总计中丢弃

with irt as
   -- resolve null in end_date default to sysdate
   (select id, name, rate,start_date Rs, nvl(end_date,trunc(sysdate)) Re from interest_rate_tab)  
   
   -- generate example v_date, v_date_payout sets
   , vdata as 
   ( select 'v1' vid,date '2000-01-01' Vd, date '2000-03-01' Vp from dual union all
     select 'v2',    date '2016-12-01',    date '2018-01-01' Vp from dual
   )
  
select  t.*
     , days*rate                               range_amount  --contribution of each range
     , sum(days*rate) over (partition by vid)  total_amount  -- for vid total  
  from ( --Determinr Range, extract rate and calculate days to apply rate
         -- Vid, rate, days all that's needed, others included for verification purposes, but can e eliminted from final
         select  vid,Vd,Vp,Rs,Re,Rate                
              ,  case when ( Vd >= Rs and Vp < Re )          then Vp-Vd
                      when ( Vd<=Rs and (Vp>=Rs and Vp<=Re)) then Vp-Rs
                      when ( Vd>=Rs And  Vp>=Re)             then Re-Vd
                      when ( Vd<Rs  and  Vp>=Re)             then Re-Rs
                      else 0
                 end days
          -- following case staement for display of how daays were derived, for testing, but can e eliminted from fina
             ,  case when ( Vd >= Rs and Vp < Re )
                     then  '( Vd >= Rs and Vp < Re ) ==>Vp-Vd'
                     when ( Vd<=Rs and (Vp>=Rs and Vp<=Re))
                     then '( Vd<=Rs and (Vp>=Rs and Vp<=Re)) ==>Vp-Rs'
                     when ( Vd>=Rs And  Vp>=Re) 
                     then  '( Vd>=Rs And  Vp>=Re) ==>Re-Vd'
                     when ( Vd<Rs  and  Vp>=Re) 
                     then '( Vd<Rs  and  Vp>=Re) ==>Re-Rs'
                     else 'Error'
                end dd
          from irt
          join vdata 
            on (   ( Vd >= Rs and Vp < Re )           
            or ( Vd<=Rs and (Vp>=Rs and Vp<=Re))                           
            or ( Vd>=Rs And  Vp>=Re)                                      
            or ( Vd<Rs  and  Vp>=Re) 
           )  
      ) t 
order by vid;
       -- resolve null in end_date default to sysdate
       (select id, name, rate,start_date Rs, nvl(end_date,trunc(sysdate)) Re from interest_rate_tab)  
       
       -- generate example v_date, v_date_payout sets
       , vdata as 
       ( select 'v1' vid,date '2000-01-01' Vd, date '2000-03-01' Vp from dual union all
         select 'v2',    date '2016-12-01',    date '2018-01-01' Vp from dual
       )   
    select  t.*
         , days*rate amount_part                   --individual section amount
         , sum(days*rate) over (partition by vid)  --total amount for each vid
      from (
            with irt as
       -- resolve null in end_date default to sysdate
       (select id, name, rate,start_date Rs, nvl(end_date,trunc(sysdate)) Re from interest_rate_tab)  
       
       -- generate example v_date, v_date_payout sets
       , vdata as 
       ( select 'v1' vid,date '2000-01-01' Vd, date '2000-03-01' Vp from dual union all
         select 'v2',    date '2016-12-01',    date '2018-01-01' Vp from dual
       )
             select  vid,Vd,Vp,Rs,Re,Rate                
             -- determine range overlap                 -- calculate days
                  ,  case when ( Vd >= Rs and Vp < Re )          then Vp-Vd
                          when ( Vd<=Rs and (Vp>=Rs and Vp<=Re)) then Vp-Rs
                          when ( Vd>=Rs and  Vp>=Re)             then Re-Vd
                          when ( Vd<Rs  and  Vp>=Re)             then Re-Rs
                          else 0
                     end days
              -- following case statement for display of how days were derived, for testing, when validated delete
                 ,  case when ( Vd >= Rs and Vp < Re )
                         then  '( Vd >= Rs and Vp < Re ) ==>Vp-Vd'
                         when ( Vd<=Rs and (Vp>=Rs and Vp<=Re))
                         then '( Vd<=Rs and (Vp>=Rs and Vp<=Re)) ==>Vp-Rs'
                         when ( Vd>=Rs And  Vp>=Re) 
                         then  '( Vd>=Rs And  Vp>=Re) ==>Re-Vd'
                         when ( Vd<Rs  and  Vp>=Re) 
                         then '( Vd<Rs  and  Vp>=Re) ==>Re-Rs'
                         else 'Error'
                    end dd
              from irt
              join vdata 
                on (   ( Vd >= Rs and Vp < Re )           
                or ( Vd<=Rs and (Vp>=Rs and Vp<=Re))                           
                or ( Vd>=Rs And  Vp>=Re)                                      
                or ( Vd<Rs  and  Vp>=Re) 
               )  
          ) t 
    order by vid;
测试完成后,终于有了一个清理过的版本

with irt as
   -- end_datesolve null in end_date default to sysdate
   (select id, name, rate,start_date, nvl(end_date,trunc(sysdate)) end_date from interest_rate_tab)  
   
   -- generate example v_date, v_date_payout sets
   , v_data as 
   ( select 'v1' vid,date '2000-01-01' v_date, date '2000-03-01' v_date_payment from dual union all
     select 'v2',    date '2016-12-01',    date '2018-01-01' v_date_payment from dual
   )
  
select distinct vid, v_date, v_date_payment
     , sum(days*rate) over (partition by vid)  total_amount  -- for vid total  
  from ( --Determine Range, extract rate, and calculate days to apply rate
         select  vid,v_date,v_date_payment,start_date,end_date,Rate                
              ,  case when ( v_date >= start_date and v_date_payment < end_date )  then v_date_payment-v_date
                      when (     v_date<=start_date 
                            and v_date_payment>=start_date 
                            and v_date_payment<=end_date
                           )                                                       then v_date_payment-start_date
                      when ( v_date>=start_date And  v_date_payment>=end_date)     then end_date-v_date
                      when ( v_date<start_date  and  v_date_payment>=end_date)     then end_date-start_date
                      else 0
                 end days
          from irt
          join v_data 
            on (   ( v_date >= start_date and v_date_payment < end_date )           
                or ( v_date<=start_date and (v_date_payment>=start_date and v_date_payment<=end_date))                           
                or ( v_date>=start_date And  v_date_payment>=end_date)                                      
                or ( v_date<start_date  and  v_date_payment>=end_date) 
               )  
      ) t 
order by vid;

欢迎来到SO。你需要把你尝试过的东西和你被困的地方贴出来。社区专家将帮助解决这个问题。