【问题标题】:How to combine/merge data from two tables having date range into single timeline query如何将具有日期范围的两个表中的数据组合/合并到单个时间线查询中
【发布时间】:2015-06-15 14:19:30
【问题描述】:

XXA - 包含基于日期范围的项目价格的表格。

XXB - 表格有基于日期范围的项目的新价格。

现在我希望 XXA 表根据 XXB 表中的新价格进行更新,并考虑日期范围。

如果新的XXB 表中缺少某些日期,那么对于这些​​日期,旧数据应保留在XXA 中。

例如:

表格XXA内容:

 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

表格XXB内容:

 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

现在基于XXB 数据,XXA 表应如下更改,其中XXA 表中的新价格会针对可用日期范围进行更新,对于其他日期,XXA 中的旧价格将被考虑。

 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

注意:我想,只有当我们将输出带入单个 sql 查询时,我们才能使用它来相应地更新表 XXA。当我尝试采用个别场景时,数据更改时结果会出错。有人可以帮我在单个查询中带来以上输出吗?

以下是我在场景方面尝试过的代码:

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;

【问题讨论】:

  • 其他为空时的异常;这是一种不好的做法,因为它隐藏了所有错误。更改空;提高;
  • 关于异常 - 当其他人提出时,没有必要做异常;万一出现异常,如果没有任何动作,就直接删除即可。
  • @sat33man:为什么从 2015 年 1 月 27 日到 2015 年 2 月 3 日期间的结果值为 344?此期间在两个表中都有定义。为什么你是从 XXB 拿的,而不是从 XXA 拿的?
  • @Rusty 因为 xxb 表有新价格,所以我需要考虑日期范围,用 xxb 表中的价格值更新 xxa 表。

标签: sql oracle


【解决方案1】:

这是一种方法,虽然它需要创建一个全局临时表,因为我不知道如何将行合并回 xxa(临时脑放屁,或者真的不可行;我不确定!开放的想法 *{:-) )。无论如何,除了作为下面插入/删除语句的包装器之外,不需要 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 表的列xxa的。获得这些信息后,我会使用 tabibitosan 方法将结果重新分组到范围中。


好的,这是一种使用合并的方法,但这确实意味着 xxa 表需要有一个主键(或标识每个项目的行的其他列)。注:如果 xxa 表中的行数多于组合 xxa/xxb 结果的行数,则“额外”行将在 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;

【讨论】:

  • 非常感谢。这行得通,但我忘了提到 xxa 数据不应作为其种子被删除。删除数据不会在审计报告中显示历史记录,因此我只能在该表中更新/插入。让我尝试使用这个..在临时表中填充数据后使用它将尝试实现它。如果可能的话,您也可以尝试不删除。不管怎样,你很棒。谢谢。
  • 如果 xxb 表中的一行与 xxa 表中的两行或多行覆盖相同的范围怎么办?那么,您肯定需要减少 xxa 中的行数吗?考虑一下,您可能可以将 xxa 表的连接添加到基于 rownum 的外部 SQL stmt 中,然后使用它来确定在合并中加入哪个 start_date。虽然它会让它变得更慢。我明天去看看。
  • 是的,你是对的。就像 xxa 有 5-Sep-15 ,20-Sep-15 和 2-Sep-15,3-Sep-15 。 XXB 有 1-Sep-15,22-Sep-15。现在我们在 xxa 中有 1 行不应该被删除。如果查询返回类似 1-Sep-15 ,4-Sep-15||5-Sep-15,20-Sep-15||21-Sep-15,22-Sep-15 的输出,那么它很好。除了这种情况,其他人都可以调整而不删除。
  • 我编辑了您的代码并添加到上述问题中。我刚刚在 XXA,XXB 中包含了状态列。基于此,同时对分隔日期进行分组。但是仍然如何确定哪一行用于更新 XXA 以及哪一行直接插入到 XXA 中?对此有任何想法吗?请帮忙..
  • 在状态栏中添加实际上并没有做任何有用的事情,但是!但是,我已经更新了我的答案以包含一种合并结果的方法。不过,它确实假设 xxa 有一个主键。如果您的表没有,那么您将不得不添加一个。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-12-07
  • 2014-05-30
  • 2017-10-17
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多