2012-10-19 13 views
8

Mam dwie tabele historii, które śledzą zmiany w wartościach bazy danych, używając identyfikatora wersji do śledzenia poszczególnych zmian. na przykładScalanie dwóch tabel śledzenia wersji podczas wypełniania wartości

Tabela 1:

rev | A | B 
================= 
1 | 100 | 'A' 
4 | 150 | 'A' 
7 | 100 | 'Z' 

Tabela 2:

rev | C | D 
================== 
1 | 200 | True 
5 | 0 | True 
8 | 0 | False 

Celem będzie połączenie tych dwóch tabel w:

rev | A | B | C | D 
=============================== 
1 | 100 | 'A' | 200 | True 
4 | 150 | 'A' | 200 | True 
5 | 150 | 'A' | 0 | True 
7 | 100 | 'Z' | 0 | True 
8 | 100 | 'Z' | 0 | False 

Pogląd, że dla danego rewizję, wziąłbym wartości odpowiadające tej rewizji lub najwyższą rewizję mniejszą od niej.

Kwerenda SQL, która przychodzi do głowy, byłoby coś podobnego do przekroczenia łącząca dwie tabele z więzów rev1 < rev2, a następnie wybierając wiersze za pomocą podzapytania gdzie rev1 = max (rev1) dla każdego podanego Rev2 ; zjednoczenie tego zapytania z jego odpowiednikiem wymieniającym i rev1 ; i wreszcie odfiltrowanie duplikatów z miejsca, gdzie rev1 = rev2.

Pytania są:

  • Czy jest nazwa dla tego typu przyłączyć?
  • Czy istnieje idiom do wykonywania tego typu sprzężenia w SQL, czy byłoby lepiej zrobić to programowo (co z pewnością byłoby znacznie prostsze i być może bardziej wydajne)?
+0

Co RDBMS? Niektóre mają wsparcie dla tego typu operacji, więc (szczególnie jeśli przestrzeń danych jest duża) może to być bardziej wydajne w bazie danych. –

+0

Więc nie chcesz zapytania, ale tylko odpowiedzi na te dwa pytania? –

+0

Baza danych to PostgreSQL, choć pod względem technicznym praca ma być niezależna od DB (realistycznie to się nie stanie). I tak, po prostu interesują mnie odpowiedzi na pytania, chyba że jest znacznie prostsze pytanie, które przeoczyłem. –

Odpowiedz

2

SQL Fiddle

select 
    coalesce(t1.rev, t2.rev) rev, 
    coalesce(a, lag(a, 1) over(order by coalesce(t2.rev, t1.rev))) a, 
    coalesce(b, lag(b, 1) over(order by coalesce(t2.rev, t1.rev))) b, 
    coalesce(c, lag(c, 1) over(order by coalesce(t1.rev, t2.rev))) c, 
    coalesce(d, lag(d, 1) over(order by coalesce(t1.rev, t2.rev))) d 
from 
    t1 
    full join 
    t2 on t1.rev = t2.rev 
order by rev 
1

Można to osiągnąć przez sub odpytuje

SELECT ISNULL(Table1.rev,Table2.rev) AS rev 
,ISNULL(A,(SELECT TOP 1 A FROM Table1 AS T1 WHERE ISNULL(Table1.rev,Table2.rev) > T1.rev AND A IS NOT NULL ORDER BY rev DESC)) AS A 
,ISNULL(B,(SELECT TOP 1 B FROM Table1 AS T1 WHERE ISNULL(Table1.rev,Table2.rev) > T1.rev AND B IS NOT NULL ORDER BY rev DESC)) AS B 
,ISNULL(C,(SELECT TOP 1 C FROM Table2 AS T2 WHERE ISNULL(Table1.rev,Table2.rev) > T2.rev AND C IS NOT NULL ORDER BY rev DESC)) AS C 
,ISNULL(D,(SELECT TOP 1 D FROM Table2 AS T2 WHERE ISNULL(Table1.rev,Table2.rev) > T2.rev AND D IS NOT NULL ORDER BY rev DESC)) AS D 
FROM Table1 
FULL OUTER JOIN Table2 
ON Table1.rev = Table2.rev 
+0

'isnull' nie jest prawidłową składnią postgresql –

0

Nie ma specyficzny typ dołączyć do obsługi tego rodzaju zapytania. Musisz to zrobić jako złożone zapytanie lub programowo. Poniżej znajduje się przykład kodu PL/PGSQL dla tego problemu, korzystając z przykładowych danych.

CREATE OR REPLACE FUNCTION getRev(OUT rev INT, OUT A INT, OUT B CHAR, OUT C INT, OUT D BOOL) RETURNS SETOF record STABLE AS 
$BODY$ 
DECLARE 
    c1 SCROLL CURSOR FOR SELECT * FROM Table1 ORDER BY rev; 
    c2 SCROLL CURSOR FOR SELECT * FROM Table2 ORDER BY rev; 
    r1 Table1%ROWTYPE; 
    r1c Table1%ROWTYPE; 
    r2 Table2%ROWTYPE; 
    r2c Table2%ROWTYPE; 
BEGIN 
    OPEN c1; 
    OPEN c2; 
    FETCH c1 INTO r1; 
    FETCH c2 INTO r2; 
    r1c := r1; 
    r2c := r2; 
    WHILE r1 IS NOT NULL AND r2 IS NOT NULL 
    LOOP 
    CASE 
    WHEN r1.rev = r2.rev THEN 
     rev := r1.rev; 
     A := r1.a; 
     B := r1.b; 
     C := r2.c; 
     D := r2.d; 
     FETCH c1 INTO r1c; 
     FETCH c2 INTO r2c; 
     CASE 
     WHEN r1c.rev = r2c.rev THEN 
     r1 := r1c; 
     r2 := r2c; 
     WHEN r1c.rev < r2c.rev THEN 
      r1 := r1c; 
     FETCH PRIOR FROM c2 INTO r2c; 
    ELSE 
      r2 := r2c; 
     FETCH PRIOR FROM c1 INTO r1c; 
     END CASE; 
    WHEN r1.rev < r2.rev THEN 
     WHILE r1c IS NOT NULL AND r1c.rev < r2.rev LOOP 
     r1 := r1c; 
     FETCH c1 INTO r1c; 
     END LOOP; 
     rev := r2.rev; 
     A := r1.a; 
     B := r1.b; 
     C := r2.c; 
     D := r2.d; 
     r1 := r1c; 
    ELSE 
     WHILE r2c IS NOT NULL AND r2c.rev < r1.rev LOOP 
     r2 := r2c; 
     FETCH c2 INTO r2c; 
     END LOOP; 
     rev := r1.rev; 
     A := r1.a; 
     B := r1.b; 
     C := r2.c; 
     D := r2.d; 
     r2 := r2c; 
    END CASE; 
    RETURN NEXT; 
    END LOOP; 
    CLOSE c1; 
    CLOSE c2; 
    RETURN; 
END 
$BODY$ 
LANGUAGE 'plpgsql'; 

Powinno działać w O (długość (Tabela1) + długość (Tabela2)).

Zwróć uwagę na trudną część w "CASE WHEN r1.rev = r2.rev": musimy wybrać, w której tabeli kontynuujemy skanowanie do następnej iteracji. Prawidłowy jest ten o najmniejszej wartości rev po kursorze, aby przejść przez wszystkie numery obrotów dostępne w obu tabelach. Z pewnością można uzyskać lepszą wydajność poprzez kodowanie go w C lub C++.