6

我很喜欢这个。希望我可以用纯 sql 来完成,但此时任何解决方案都可以。

我有ta和表格,其中包含大约tb同时发生的事件列表。目标是从on中查找“孤儿”记录。例如:tatb

create table ta ( dt date, id varchar(1));
insert into ta values( to_date('20130101 13:01:01', 'yyyymmdd hh24:mi:ss') , '1' );
insert into ta values( to_date('20130101 13:01:02', 'yyyymmdd hh24:mi:ss') , '2' );
insert into ta values( to_date('20130101 13:01:03', 'yyyymmdd hh24:mi:ss') , '3' );


create table tb ( dt date, id varchar(1));
insert into tb values( to_date('20130101 13:01:5', 'yyyymmdd hh24:mi:ss') , 'a' );
insert into tb values( to_date('20130101 13:01:6', 'yyyymmdd hh24:mi:ss') , 'b' );

但是假设我必须使用 +-5 秒的阈值。因此,要查找的查询类似于:

  select
    ta.id ida,
    tb.id idb
  from
    ta, tb
  where 
    tb.dt between (ta.dt - 5/86400) and (ta.dt + 5/86400)
  order by 1,2 

(小提琴:http ://sqlfiddle.com/#!4/b58f7c/5 )

规则是:

  • 事件映射为 1 到 1
  • tb给定事件中最接近的事件ta将被视为正确的映射。

也就是说,生成的查询应该返回类似

IDA | IDB
1   | a
2   | b
3   | null  <-- orphan event

尽管我放在这里的示例查询准确地显示了我遇到的问题。当时间重叠时,很难系统地选择正确的行。

dense_rank()似乎是选择正确行的答案,但是什么分区/排序将使它们正确?

值得一提的是,我在 Oracle 11gR2 上执行此操作。

4

1 回答 1

2

似乎这应该可以通过使用 Oracle 的分析函数的单个 SQL 语句来实现,也许是 row_number()、lag() 和 max() 的某种组合。但我根本无法绕过它。我一直想在另一个中嵌入一个分析函数,我认为你做不到。您可以使用公用表表达式逐步进行,但我不知道如何使其工作。

但是使用 PL*SQL 和一个额外的表来存储您的结果,程序解决方案是相当直接的。我使用 row_number() 为每个源表中的每一行分配时间顺序。你想要一个确定的结果,所以有一个决胜局很重要,以防你有重复的日期时间,因此我的顺序是 dt, id。这是一个SQL-Fiddle 演示

或者看看下面的代码:

create table result ( 
  dif number, 
  ida varchar(1),
  idb varchar(1),
  dta date,
  dtb date
);

declare
  prevA integer := 0;
  prevB integer := 0;
begin
  for rec in (
    with 
    ordered_ta as (
      select dt dta,
             id ida,
             row_number() over (order by dt, id) rowNumA
        from ta
    ),
    ordered_tb as (
      select dt dtb,
             id idb, 
             row_number() over (order by dt, id) rowNumB 
        from tb
    )
    select ta.*,
           tb.*,
           abs(dta - dtb) * 86400 dif
      from ordered_ta ta
      join ordered_tb tb
        on dtb between (dta - 5/86400) and (dta + 5/86400)
     order by rowNumA, rowNumB
  )
  loop
    if rec.rowNumA > prevA and rec.rowNumB > prevB then
      prevA := rec.rowNumA;
      prevB := rec.rowNumB;
      insert into result values (
        rec.dif,
        rec.ida,
        rec.idb,
        rec.dta,
        rec.dtb
      );
    end if;
  end loop;
end;
/

select * from result
union all
select null dif, id ida, null idb, dt dta, null dtb
  from ta
 where id not in (select ida from result)
union all
select null dif, null ida, id idb, null dta, dt dtb
  from tb
 where id not in (select idb from result)
;
于 2013-06-16T17:31:44.757 回答