Spajanje podataka po zajednickom atributu

Tuesday, 16.10.2007 – Srdjan

Miša, jedan moj kolega s posla, je juče naišao na problem prilikom pravljenja nekog izveštaja. Za potrebe tog izveštaja mora da prikupi podatke iz četiri različita izvora, a onda da ih objedini u jedinstven izveštaj.

Miša je napravio strukturu od četiri tabele, i u svaku od tabela je importovao podatke iz po jednog izvora. Svi prikupljeni podaci se odnose na odnose sa poslovnim partnerima (bilo da se radi o kupcima ili dobavljačima). Ono što je zajedničko svim podacima je da svi izvori koriste zajednički način ‘šifriranja’ partnera.

Pošto nemaju svi izvori odnose sa svim partnerima, ni jedan izvor nema kompletnu listu partnera. Problem je nastao kako se od četiri nepotpune liste partnera može napraviti jedna jedinstvena lista. Po dobijanju jedinstvene liste partnera ostaje rutinski posao spajanja tabela.

Miša mi se obratio za pomoć baš za kreiranje ove jedinstvene liste partnera, jer je problem dodatno komplikovala činjenica da su različiti izvori davali različita imena partnerima.

Da bih ilustrovao problem, daću sledeću strukturu tabela:

CREATE TABLE podaci_prodaje ( 
  sifra_partnera INTEGER NOT NULL PRIMARY KEY, 
  ime_partnera VARCHAR(50) NOT NULL, 
  broj_faktura INTEGER NOT NULL, 
  prodajna_vrednost NUMERIC(12, 2) NOT NULL);         

CREATE TABLE podaci_nabavke ( 
  sifra_partnera INTEGER NOT NULL PRIMARY KEY, 
  ime_partnera VARCHAR(50) NOT NULL, 
  datum_poslednje_nabavke DATE NOT NULL, 
  nabavna_vrednost NUMERIC(12, 2) NOT NULL);              

CREATE TABLE podaci_marketinga ( 
  sifra_partnera INTEGER NOT NULL PRIMARY KEY, 
  ime_partnera VARCHAR(50) NOT NULL, 
  broj_promocija INTEGER NOT NULL);

Ovu strukturu ću popuniti test podacima:

INSERT INTO podaci_prodaje (sifra_partnera, ime_partnera, broj_faktura, prodajna_vrednost) 
  VALUES (1, 'Mika str', 2, 15000.00); 
INSERT INTO podaci_prodaje (sifra_partnera, ime_partnera, broj_faktura, prodajna_vrednost) 
  VALUES (2, 'Pera doo', 5, 46500.00); 
INSERT INTO podaci_prodaje (sifra_partnera, ime_partnera, broj_faktura, prodajna_vrednost) 
  VALUES (4, 'Joca doo', 4, 75000.00);         

INSERT INTO podaci_nabavke (sifra_partnera, ime_partnera, datum_poslednje_nabavke, nabavna_vrednost) 
  VALUES (4, 'D.O.O. JOCA', '2007-03-01', 22000.00); 
INSERT INTO podaci_nabavke (sifra_partnera, ime_partnera, datum_poslednje_nabavke, nabavna_vrednost) 
  VALUES (5, 'Doo ZIKA i SONS', '2007-05-15', 15000.00);          

INSERT INTO podaci_marketinga (sifra_partnera, ime_partnera, broj_promocija) 
  VALUES (1, 'MIKA', 1); 
INSERT INTO podaci_marketinga (sifra_partnera, ime_partnera, broj_promocija) 
  VALUES (3, 'MELANIJA', 2); 
INSERT INTO podaci_marketinga (sifra_partnera, ime_partnera, broj_promocija) 
  VALUES (4, 'JOCA', 5);

Napomena: Spomenuo sam da je Miša napravio četiri tabele, a ja ovde radim sa tri tabele jer to ne utiče na princip rešavanja problema.

Zahtev je da se od datih podataka napravi izveštaj sa sledećim kolonama: ime partnera, broj faktura, prodajna vrednost, datum poslednje nabavke, nabavna vrednost i kolona sa brojem promocija. Takođe, svi podaci o jednom partneru se moraju pojaviti u jednom redu.

U testnim podacima treba uočiti da ni jedna od službi (prodaje, nabavke, marketinga) nema u svojim podacima sve partnere. Druga stvar koju treba uočiti je neznatna razlika u imenovanju partnera u pojedinim službama. Tako služba prodaje partnera sa šifrom 1 imenuje ‘Mika str’, dok tog istog partnera marketing imenuje ‘MIKA’.

Ova činjenica o imenovanju partnera komplikuje stvari jer se objedinjena lista partnera nemože dobiti prostim spajanjem (UNION) podataka raznih službi.

SELECT sifra_partnera, ime_partnera FROM podaci_prodaje 
 UNION ALL 
SELECT sifra_partnera, ime_partnera FROM podaci_nabavke 
 UNION ALL 
SELECT sifra_partnera, ime_partnera FROM podaci_marketinga;

Prikazan upit ne spaja podatke o partnerima na ispravan način jer duplira partnere koji su različito imenovani od strane različitih službi. Ovde nebi pomogla ni upotreba čistog UNION umesto UNION ALL.

Treba nam suptilniji način za spajanje podataka o partnerima i to se postiže pomoću sledećeg pogleda:

CREATE VIEW partneri (sifra_partnera, ime_partnera) 
AS 
SELECT s.sifra_partnera, 
       CASE WHEN p.ime_partnera IS NOT NULL THEN p.ime_partnera 
            WHEN n.ime_partnera IS NOT NULL THEN n.ime_partnera 
            WHEN m.ime_partnera IS NOT NULL THEN m.ime_partnera 
       END 
  FROM (SELECT sifra_partnera FROM podaci_prodaje 
         UNION 
        SELECT sifra_partnera FROM podaci_nabavke 
         UNION 
        SELECT sifra_partnera FROM podaci_marketinga 
       ) AS s 
       LEFT OUTER JOIN 
       podaci_prodaje AS p 
         ON s.sifra_partnera = p.sifra_partnera 
       LEFT OUTER JOIN 
       podaci_nabavke AS n 
         ON s.sifra_partnera = n.sifra_partnera 
       LEFT OUTER JOIN 
       podaci_marketinga AS m 
         ON s.sifra_partnera = m.sifra_partnera;

U gornjem pogledu je prvo upotrebljen čist UNION za dobijanje svih (unikatnih) šifara, a onda su te šifre spojene sa odgovarajućim imenima partnera. Prost SELECT nad ovim pogledom vraća kompletnu listu jedinstvenih partnera:

sifra_partnera  ime_partnera 
--------------  --------------- 
             1  Mika str 
             2  Pera doo 
             3  MELANIJA 
             4  Joca doo 
             5  Doo ZIKA i SONS

Sada kada imamo jedinstvenu listu partnera uopšte nije teško doći do sledećeg upita:

SELECT s.ime_partnera, 
       p.broj_faktura, p.prodajna_vrednost, 
       n.datum_poslednje_nabavke, n.nabavna_vrednost, 
       m.broj_promocija 
  FROM partneri AS s 
       LEFT OUTER JOIN 
       podaci_prodaje AS p 
         ON s.sifra_partnera = p.sifra_partnera 
       LEFT OUTER JOIN 
       podaci_nabavke AS n 
         ON s.sifra_partnera = n.sifra_partnera 
       LEFT OUTER JOIN 
       podaci_marketinga AS m 
         ON s.sifra_partnera = m.sifra_partnera;

Ovaj upit nam daje traženi rezultat:

ime_partnera     broj_faktura  prodajna_vrednost  datum_poslednje_nabavke  nabavna_vrednost  broj_promocija 
---------------  ------------  -----------------  -----------------------  ----------------  -------------- 
Mika str                    2           15000.00                                                          1 
Pera doo                    5           46500.00 
MELANIJA                                                                                                  2 
Joca doo                    4           75000.00               2007-03-01          22000.00               5 
Doo ZIKA i SONS                                                2007-05-15          15000.00
  1. 3 Responses to “Spajanje podataka po zajednickom atributu”

  2. Fino, fino, samo verujem da bi bilo korisno naglasiti da se suština postiže upotrebom „LEFT OUTER JOIN“ koji „spaja“ i kada je poređena kolona NULL.

    By Данило on Oct 17, 2007

  3. Predlozio bih sledece resenje:

    Select partneri.sifra_partnera, MAX(partneri.ime_partnera)
    from
    (SELECT podaci_prodaje.sifra_partnera, podaci_prodaje.ime_partnera FROM podaci_prodaje
    UNION
    SELECT podaci_nabavke.sifra_partnera, podaci_nabavke.ime_partnera FROM podaci_nabavke
    UNION
    SELECT podaci_marketinga.sifra_partnera, podaci_marketinga.ime_partnera FROM podaci_marketinga
    ) partneri
    GROUP BY partneri.sifra_partnera;

    Pretpostavimo da se u ove tri tabele nalaze desetine hiljada podataka, i to vrlo verovatno da nekoliko hiljada zapisa nema u jednoj a ima u drugoj tabeli itd. Prema prethodnom resenju usled LEFT OUTER JOIN-a spojilo bi se, bespotrebno, jako mnogo podataka, sa null-ovima, tj. bilo bi daleko vise zapisa, kroz koje bi upit prosao, kako bi CASE izdvojio koje “ime_partnera” da ispise, sto bi znacajno uticalo na performanse.
    U ovom predlogu resenja, funkcija MAX(partneri.ime_partnera) (ili min) uz pretpostavku da nije bitno iz koje ce se tabele uzimati “ime_partnera”, poredi samo ono sto vec imamo, znaci maksimalno po 3 zapisa.
    Mislim da ovo resenje ima prednost u pogledu brzine izvrsavanja, kada je u pitanju velika kolicina podataka (sto je najcesce slucaj)

    By Andrija on Nov 9, 2007

  4. Tako je, a može da se upotrebi i UNION ALL.

    Treba napomenuti da ova dva upita ipak nisu ekvivalentna u slučaju kad se u ulaznim podacima pojavljuju duplirani redovi.

    By Srdjan on Nov 9, 2007

Post a Comment