How to combine/merge data from two tables having date range into single timeline query

时间:2015-06-15 14:19:31

标签: sql oracle

XXA - table which contains price for items based on date range.

XXB - table has new price for items based on date range.

Now I want XXA table to be updated based on new prices from XXB table considering date range.

If some dates are missing in new XXB table then for those dates old data should be maintained in XXA.

For example:

Table XXA contents:

 ITEM | PRICE | START_DATE | END_DATE
---------------------------------------
 plug |  12   | 10-Jan-15  | 15-Jan-15
 plug |   1   | 20-Jan-15  | 25-Jan-15
 plug |   3   | 30-Jan-15  |  4-Feb-15
 plug |   4   |  5-Feb-15  | 10-Feb-15
 plug |  43   | 20-Feb-15  | 25-Feb-15

Table XXB contents:

 ITEM | PRICE | START_DATE | END_DATE
---------------------------------------
 plug |  345  |  1-Jan-15  | 10-Jan-15
 plug |  133  | 12-Jan-15  | 20-Jan-15
 plug |  344  | 27-Jan-15  |  3-Feb-15
 plug |  455  |  7-Feb-15  | 10-Feb-15
 plug |  431  | 17-Feb-15  | 23-Feb-15

Now based on XXB data, XXA table should be changed like below, where the new price from XXA table is updated for available date ranges, for other dates old price in XXA is considered.

 ITEM | PRICE | START_DATE | END_DATE
---------------------------------------
 plug |  345  |  1-Jan-15  | 10-Jan-15
 plug |   12  | 11-Jan-15  | 11-Jan-15
 plug |  133  | 12-Jan-15  | 20-Jan-15
 plug |    1  | 21-Jan-15  | 25-Jan-15
 plug |  344  | 27-Jan-15  |  3-Feb-15
 plug |    3  |  4-Feb-15  |  4-Feb-15
 plug |    4  |  5-Feb-15  |  6-Feb-15
 plug |  455  |  7-Feb-15  | 10-Feb-15
 plug |  431  | 17-Feb-15  | 23-Feb-15
 plug |   43  | 24-Feb-15  | 25-Feb-15

NOTE: I suppose, Only if we bring the output in single sql query we can use it to update the table XXA accordingly. When i tried by taking individual scenario wise result goes wrong when data changes. Can somebody help me bring above output in single query.

Below is the code which i tried scenario wise:

with date_range as (select least(xa.min_start_date, xb.min_start_date) range_start,
                           greatest(xa.max_end_date, xb.max_end_date) range_end
                    from   (select min(start_date) min_start_date,
                                   max(end_date) max_end_date
                            from   xxa) xa
                           cross join
                           (select min(start_date) min_start_date,
                                   max(end_date) max_end_date
                            from   xxb) xb), 
          dates as (select range_start + level -1 dt
                    from   date_range
                    connect by range_start + level -1 <= range_end),
      pivot_xxa as (select xxa.item,
                           xxa.price,
                           dts.dt,
                           'Exist' status
                    from   xxa
                           inner join dates dts on (dts.dt between xxa.start_date and xxa.end_date)),
      pivot_xxb as (select xxb.item,
                           xxb.price,
                           dts.dt,
                           'New' status
                    from   xxb
                           inner join dates dts on (dts.dt between xxb.start_date and xxb.end_date)),
            res as (select coalesce(pxb.item, pxa.item) item,
                           coalesce(pxb.price, pxa.price) price,
                           coalesce(pxa.dt, pxb.dt) dt,
                           row_number() over (partition by coalesce(pxb.item, pxa.item) order by coalesce(pxa.dt, pxb.dt))
                             - row_number() over (partition by coalesce(pxb.item, pxa.item), coalesce(pxb.price, pxa.price),pxa.status,pxb.status order by coalesce(pxa.dt, pxb.dt)) grp
                    from   pivot_xxa pxa
                           full outer join pivot_xxb pxb on (pxa.item = pxb.item
                                                             and pxa.dt = pxb.dt))

select item,
       price,
       min(dt) start_date,
       max(dt) end_date
from   res
group  by item,
          price,
          grp;

1 个答案:

答案 0 :(得分:1)

这是一种方法,虽然它需要创建一个全局临时表,因为我无法解决如何将行合并回xxa(临时脑屁,或者真的不可行;我&#39;我不确定!开放给想法* {:-))。无论如何,除了作为下面插入/删除语句的包装器之外,不需要PL / SQL。

create table xxa (item varchar2(5),
                  price number,
                  start_date date,
                  end_date date);

create table xxb (item varchar2(5),
                  price number,
                  start_date date,
                  end_date date);

create global temporary table xxa_xxb_tmp_res (item varchar2(5),
                                               price number,
                                               start_date date,
                                               end_date date);

insert into xxa
select 'plug', 12, to_date('10/01/2015', 'dd/mm/yyyy'), to_date('15/01/2015', 'dd/mm/yyyy') from dual union all
select 'plug', 1, to_date('20/01/2015', 'dd/mm/yyyy'), to_date('25/01/2015', 'dd/mm/yyyy') from dual union all
select 'plug', 3, to_date('30/01/2015', 'dd/mm/yyyy'), to_date('04/02/2015', 'dd/mm/yyyy') from dual union all
select 'plug', 4, to_date('05/02/2015', 'dd/mm/yyyy'), to_date('10/02/2015', 'dd/mm/yyyy') from dual union all
select 'plug', 43, to_date('20/02/2015', 'dd/mm/yyyy'), to_date('25/02/2015', 'dd/mm/yyyy') from dual;

insert into xxb
select 'plug', 345, to_date('01/01/2015', 'dd/mm/yyyy'), to_date('10/01/2015', 'dd/mm/yyyy') from dual union all
select 'plug', 133, to_date('12/01/2015', 'dd/mm/yyyy'), to_date('20/01/2015', 'dd/mm/yyyy') from dual union all
select 'plug', 344, to_date('27/01/2015', 'dd/mm/yyyy'), to_date('03/02/2015', 'dd/mm/yyyy') from dual union all
select 'plug', 455, to_date('07/02/2015', 'dd/mm/yyyy'), to_date('10/02/2015', 'dd/mm/yyyy') from dual union all
select 'plug', 431, to_date('17/02/2015', 'dd/mm/yyyy'), to_date('23/02/2015', 'dd/mm/yyyy') from dual;

commit;

insert into xxa_xxb_tmp_res (item, price, start_date, end_date)
with date_range as (select least(xa.min_start_date, xb.min_start_date) range_start,
                           greatest(xa.max_end_date, xb.max_end_date) range_end
                    from   (select min(start_date) min_start_date,
                                   max(end_date) max_end_date
                            from   xxa) xa
                           cross join
                           (select min(start_date) min_start_date,
                                   max(end_date) max_end_date
                            from   xxb) xb), 
          dates as (select range_start + level -1 dt
                    from   date_range
                    connect by range_start + level -1 <= range_end),
      pivot_xxa as (select xxa.item,
                           xxa.price,
                           dts.dt
                    from   xxa
                           inner join dates dts on (dts.dt between xxa.start_date and xxa.end_date)),
      pivot_xxb as (select xxb.item,
                           xxb.price,
                           dts.dt
                    from   xxb
                           inner join dates dts on (dts.dt between xxb.start_date and xxb.end_date)),
            res as (select coalesce(pxb.item, pxa.item) item,
                           coalesce(pxb.price, pxa.price) price,
                           coalesce(pxa.dt, pxb.dt) dt,
                           row_number() over (partition by coalesce(pxb.item, pxa.item) order by coalesce(pxa.dt, pxb.dt))
                             - row_number() over (partition by coalesce(pxb.item, pxa.item), coalesce(pxb.price, pxa.price) order by coalesce(pxa.dt, pxb.dt)) grp
                    from   pivot_xxa pxa
                           full outer join pivot_xxb pxb on (pxa.item = pxb.item
                                                             and pxa.dt = pxb.dt))
select item,
       price,
       min(dt) start_date,
       max(dt) end_date
from   res
group  by item,
          price,
          grp;


delete from xxa where item in (select item from xxa_xxb_tmp_res);

insert into xxa (item, price, start_date, end_date)
select item, price, start_date, end_date
from xxa_xxb_tmp_res;

commit;

select * from xxa
order by item, start_date;

ITEM       PRICE START_DATE END_DATE  
----- ---------- ---------- ----------
plug         345 01/01/2015 10/01/2015
plug          12 11/01/2015 11/01/2015
plug         133 12/01/2015 20/01/2015
plug           1 21/01/2015 25/01/2015
plug         344 27/01/2015 03/02/2015
plug           3 04/02/2015 04/02/2015
plug           4 05/02/2015 06/02/2015
plug         455 07/02/2015 10/02/2015
plug         431 17/02/2015 23/02/2015
plug          43 24/02/2015 25/02/2015

drop table xxa;
drop table xxb;
drop table xxa_xxb_tmp_res;

大部分工作都是在第一个插入语句中完成的 - 基本上,我在做的是将每个表中的范围扩展为单独的行,然后将全部外部连接起来,然后再使用xxb表&#39 ; s列优先于xxa&#39; s。获得该信息后,我使用tabibitosan方法将结果重新分组回范围。

好的,这是使用合并的方法,但它确实意味着xxa表需要有一个主键(或一些其他列来标识每个项目的行)。注:如果xxa表中的行数多于组合的xxa / xxb结果,则&#34; extra&#34;行将在price,start_date和end_date列中具有空值。 (我仍然认为应删除这些行!):

create table xxa (pk_col number,
                  item varchar2(5),
                  price number,
                  start_date date,
                  end_date date,
                  constraint xxa_pk primary key (pk_col));

create table xxb (item varchar2(5),
                  price number,
                  start_date date,
                  end_date date);

create sequence xxa_seq
  start with 1
  maxvalue 999999999999999999999999999
  minvalue 1
  nocycle
  cache 20
  noorder;

insert into xxa
select xxa_seq.nextval,
       item,
       price,
       start_date,
       end_date
from   (select 'plug' item, 12 price, to_date('10/01/2015', 'dd/mm/yyyy') start_date, to_date('15/01/2015', 'dd/mm/yyyy') end_date from dual union all
        select 'plug', 1, to_date('20/01/2015', 'dd/mm/yyyy'), to_date('25/01/2015', 'dd/mm/yyyy') from dual union all
        select 'plug', 3, to_date('30/01/2015', 'dd/mm/yyyy'), to_date('04/02/2015', 'dd/mm/yyyy') from dual union all
        select 'plug', 4, to_date('05/02/2015', 'dd/mm/yyyy'), to_date('10/02/2015', 'dd/mm/yyyy') from dual union all
        select 'fork', 10, to_date('02/01/2015', 'dd/mm/yyyy'), to_date('20/01/2015', 'dd/mm/yyyy') from dual union all
        select 'fork', 20, to_date('22/01/2015', 'dd/mm/yyyy'), to_date('28/01/2015', 'dd/mm/yyyy') from dual union all
        select 'fork', 30, to_date('01/02/2015', 'dd/mm/yyyy'), to_date('10/02/2015', 'dd/mm/yyyy') from dual union all
        select 'club', 10, to_date('02/01/2015', 'dd/mm/yyyy'), to_date('20/01/2015', 'dd/mm/yyyy') from dual union all
        select 'club', 20, to_date('22/01/2015', 'dd/mm/yyyy'), to_date('28/01/2015', 'dd/mm/yyyy') from dual union all
        select 'club', 30, to_date('01/02/2015', 'dd/mm/yyyy'), to_date('10/02/2015', 'dd/mm/yyyy') from dual);

insert into xxb
select 'plug', 345, to_date('01/01/2015', 'dd/mm/yyyy'), to_date('10/01/2015', 'dd/mm/yyyy') from dual union all
select 'plug', 133, to_date('12/01/2015', 'dd/mm/yyyy'), to_date('20/01/2015', 'dd/mm/yyyy') from dual union all
select 'plug', 344, to_date('27/01/2015', 'dd/mm/yyyy'), to_date('03/02/2015', 'dd/mm/yyyy') from dual union all
select 'plug', 455, to_date('07/02/2015', 'dd/mm/yyyy'), to_date('10/02/2015', 'dd/mm/yyyy') from dual union all
select 'plug', 431, to_date('17/02/2015', 'dd/mm/yyyy'), to_date('23/02/2015', 'dd/mm/yyyy') from dual union all
select 'fork', 15, to_date('15/01/2015', 'dd/mm/yyyy'), to_date('18/01/2015', 'dd/mm/yyyy') from dual union all
select 'fork', 25, to_date('24/01/2015', 'dd/mm/yyyy'), to_date('08/02/2015', 'dd/mm/yyyy') from dual union all
select 'club', 100, to_date('03/01/2015', 'dd/mm/yyyy'), to_date('20/02/2015', 'dd/mm/yyyy') from dual;

commit;

merge into xxa tgt
using (with date_range as (select least(xa.min_start_date, xb.min_start_date) range_start,
                                  greatest(xa.max_end_date, xb.max_end_date) range_end
                           from   (select min(start_date) min_start_date,
                                          max(end_date) max_end_date
                                   from   xxa) xa
                                  cross join
                                  (select min(start_date) min_start_date,
                                          max(end_date) max_end_date
                                   from   xxb) xb), 
                 dates as (select range_start + level -1 dt
                           from   date_range
                           connect by range_start + level -1 <= range_end),
             pivot_xxa as (select xxa.item,
                                  xxa.price,
                                  dts.dt
                           from   xxa
                                  inner join dates dts on (dts.dt between xxa.start_date and xxa.end_date)),
             pivot_xxb as (select xxb.item,
                                  xxb.price,
                                  dts.dt
                           from   xxb
                                  inner join dates dts on (dts.dt between xxb.start_date and xxb.end_date)),
                   res as (select coalesce(pxb.item, pxa.item) item,
                                  coalesce(pxb.price, pxa.price) price,
                                  coalesce(pxa.dt, pxb.dt) dt,
                                  row_number() over (partition by coalesce(pxb.item, pxa.item) order by coalesce(pxa.dt, pxb.dt))
                                    - row_number() over (partition by coalesce(pxb.item, pxa.item), coalesce(pxb.price, pxa.price) order by coalesce(pxa.dt, pxb.dt)) grp
                           from   pivot_xxa pxa
                                  full outer join pivot_xxb pxb on (pxa.item = pxb.item
                                                                    and pxa.dt = pxb.dt)),
             final_res as (select item,
                                  price,
                                  min(dt) start_date,
                                  max(dt) end_date,
                                  row_number() over (partition by item order by min(dt)) rn
                           from   res
                           group  by item,
                                     price,
                                     grp),
                xxa_rn as (select pk_col,
                                  item,
                                  price,
                                  start_date,
                                  end_date,
                                  row_number() over (partition by item order by start_date) rn
                           from   xxa)
       select coalesce(fr.item, xa.item) item,
              fr.price,
              fr.start_date,
              fr.end_date,
              xa.pk_col
       from   final_res fr
              full outer join xxa_rn xa on (fr.item = xa.item and fr.rn = xa.rn)) src
  on (tgt.item = src.item and tgt.pk_col = src.pk_col)
when matched then
update set tgt.price = src.price,
           tgt.start_date = src.start_date,
           tgt.end_date = src.end_date
when not matched then
insert (pk_col, tgt.item, tgt.price, tgt.start_date, tgt.end_date)
values (xxa_seq.nextval, src.item, src.price, src.start_date, src.end_date);

commit;

select * from xxa
order by item, start_date;

    PK_COL ITEM       PRICE START_DATE END_DATE  
---------- ----- ---------- ---------- ----------
         8 club          10 02/01/2015 02/01/2015
         9 club         100 03/01/2015 20/02/2015
        10 club                                  
         5 fork          10 02/01/2015 14/01/2015
         6 fork          15 15/01/2015 18/01/2015
         7 fork          10 19/01/2015 20/01/2015
        27 fork          20 22/01/2015 23/01/2015
        26 fork          25 24/01/2015 08/02/2015
        22 fork          30 09/02/2015 10/02/2015
         1 plug         345 01/01/2015 10/01/2015
         2 plug          12 11/01/2015 11/01/2015
         3 plug         133 12/01/2015 20/01/2015
         4 plug           1 21/01/2015 25/01/2015
        25 plug         344 27/01/2015 03/02/2015
        23 plug           3 04/02/2015 04/02/2015
        28 plug           4 05/02/2015 06/02/2015
        21 plug         455 07/02/2015 10/02/2015
        24 plug         431 17/02/2015 23/02/2015

drop table xxa;
drop table xxb;
drop sequence xxa_seq;