2013-06-16 16 views
6

Ssałem w tym. Chciałbym móc to zrobić w czystym sql, ale w tym momencie zrobi to każde rozwiązanie.wybierz rekordy do porównania zakresów

Mam tabele ta i tb, zawierające listy zdarzeń, które wystąpiły w przybliżeniu w tym samym czasie. Celem jest znalezienie "osieroconych" rekordów z ta na tb. Np .:

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

Ale powiedzmy, że muszę użyć progu + -5 sekund. Tak, zapytanie znaleźć będzie wyglądać następująco:

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 

(skrzypce: http://sqlfiddle.com/#!4/b58f7c/5)

Zasady są:

  • Zdarzenia są mapowane 1 do 1
  • Najbliższym wydarzeniem na tb dla danego w ta zostanie uznane za poprawne odwzorowanie.

Powiedział, że uzyskany kwerenda powinien powrócić coś jak

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

Choć przykładowe zapytanie Wrzuciłem tu pokazuje dokładnie problem mam. Kiedy czas się nakłada, trudno jest systematycznie wybrać właściwy wiersz.

dense_rank() wydaje się być odpowiedzią, aby wybrać odpowiednie wiersze, ale co do partycjonowania/Sortowanie będzie umieścić je w porządku?

Warto wspomnieć, że robię to na Oracle 11gR2.

+3

Brzmi to bardzo trudne, i myślę, że istnieją pewne wymagania, które muszą być wyjaśnione. Na przykład, dlaczego dopasowałeś '1' do' a', kiedy '3' i' a' pasują bliżej? (Czy chcesz spożywać zapisy w kolejności ta.dt?) Co się dzieje, jeśli są więzy? Na przykład, co jeśli były dwa wiersze "b"? Czy jeden rząd odpowiada 2, a drugi 3, czy obaj pasują do 2? –

+1

zgodnie z twoją definicją wygląda na to, że sierota powinna być 3 – haki

+0

@Jonearles masz rację, może to zająć trochę wyjaśnienia. Główną zasadą jest tutaj * zdarzenia są odwzorowywane od 1 do 1 *.Oznaczałoby to, że "konsumowane są wydarzenia" po dopasowaniu - nie wspomniałem o tym, ponieważ wydaje się to implikować proces iteracyjny, który może być zbyt skomplikowany. W przypadku więzi, zrobi to dowolny rekord. Najlepiej byłoby przyjąć kolejność chronologiczną, ale tak naprawdę nie ma znaczenia, o ile przestrzegane jest mapowanie od 1 do 1. Czy odpowiedziałem na twoje pytanie? – filippo

Odpowiedz

2

Wygląda na to, że powinno to być możliwe za pomocą pojedynczej instrukcji SQL wykorzystującej funkcje analityczne Oracle, być może z pewną kombinacją wartości row_number(), lag() i max(). Ale po prostu nie mogłem owinąć się wokół niego głową. Nadal chciałem wbudować jedną funkcję analityczną w drugą i nie sądzę, żebyś mógł to zrobić. Możesz wykonywać kroki, korzystając ze wspólnych wyrażeń tabelarycznych, ale nie mogłem wymyślić, jak to zrobić.

Ale rozwiązanie proceduralne jest dość prosto do przodu za pomocą SQL * PL wraz z dodatkowym stołem aby zapisać wynik. Używam row_number(), aby przypisać stopę chronologiczną do każdego wiersza w każdej z tabel źródłowych. Potrzebujesz określonego wyniku, więc ważne jest, aby mieć wyłącznik remisu na wypadek, gdybyś miał zduplikowane daty i godziny, stąd moje zamówienie według dt, id. Oto SQL-Fiddle demo.

lub spojrzeć na poniższy kod:

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

Hej, dziękuję za odpowiedź. Patrząc na to wydaje się, że złożoność jest dość wysoka. Wypróbowałem to z kilkoma milionami płyt i poszedłem powoli. Próbuję zindeksować zapytanie, aby uzyskać większą wydajność, ale nadal ... – filippo

Powiązane problemy