选择记录进行范围比较

时间:2013-06-16 01:34:41

标签: sql oracle sorting range dense-rank

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

我有tatb个表,其中包含同时发生大约的事件列表。目标是在ta上找到tb的“孤儿”记录。 E.g:

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
  • tbta上给定的最近事件将被视为正确的映射。

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

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

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

dense_rank()似乎是选择正确行的答案,但是哪些分区/排序会将它们放在正确的位置?

值得一提的是,我在Oracle 11gR2上这样做。

1 个答案:

答案 0 :(得分:2)

使用Oracle的分析函数,使用row_number(),lag()和max()的某种组合可能会出现这种情况。但我根本无法绕过它。我一直希望将一个分析函数嵌入到另一个函数中,我认为你不能这样做。您可以使用Common Table Expressions进行步骤,但我无法弄清楚如何使其工作。

但是程序解决方案使用PL * SQL以及一个额外的表来存储结果是相当直接的。我使用row_number()为每个源表中的每一行分配一个按时间顺序排列。你想要一个确定的结果,所以如果你有重复的日期时间,有一个平局断路器是很重要的,因此我的订单是dt,id。这是SQL-Fiddle demo

或者查看下面的代码:

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)
;