【问题标题】:select records for range comparison选择记录进行范围比较
【发布时间】:2013-06-12 08:08:06
【问题描述】:

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

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

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 上执行此操作。

【问题讨论】:

  • 这听起来很困难,我认为有一些要求需要澄清。例如,为什么1 匹配到a,而3a 匹配更接近? (你想按 ta.dt 的顺序消费记录吗?)另外,如果有平局怎么办?例如,如果有两个“b”行怎么办?一行匹配 2,另一行匹配 3,还是都匹配 2?
  • 根据您的定义,孤儿应该是 3
  • @jonearles 你是对的,这可能需要一些澄清。不过这里的主要规则是事件被映射为 1 到 1。这实际上意味着“事件被消耗”一旦匹配——我不想不提这一点,因为它似乎暗示了一个可能变得过于复杂的迭代过程。在平局的情况下,任何一个记录都可以。理想情况下会按时间顺序排列,但只要尊重 1 对 1 映射,这并不重要。我回答你的问题了吗?
  • @haki 这就是我想要展示的。 ta.ia = 3tb上没有通讯记录。

标签: sql oracle sorting range dense-rank


【解决方案1】:

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

但是使用 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)
;

【讨论】:

  • 您好,谢谢您的回答。看起来它的复杂性似乎相当高。我已经用几百万条记录尝试过它并且速度很慢。我正在尝试索引查询以获得更多性能,但仍然...
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-05-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多