NULLS, LEFT JOIN, EXISTS i NOT IN

Friday, 04.07.2008 – Zidar

Kada zelimo da uporedimo dve tabele A i B, i da pokazemo koji su redovi prisutni u A, a nema ih u B (Accessov ‘unmatched query’), to generalno mozemo da uradimo na nekoliko nacina. Mozemo da upotrebimo LEFT JOIN, mozemo da upotrebimo NOT IN ili NOT EXISTS. Da li su svi nacini podjednako dobri i pouzdani? Podjednako dobri znaci podjednako brzi, a pouzdani znaci da li daju tacan rezultat. Zanimljivo pitanje, da li daju tacan rezultat.

Pogledajmo primer. Kreirajmo dve jednostavne tabele i napunimo ih test podacima.

CREATE TABLE A (Broj smallint, ime VARCHAR(5))
GO
INSERT INTO A (Broj, ime) VALUES (1,‘Aca’)
INSERT INTO A (Broj, ime) VALUES (2,‘Maca’)
INSERT INTO A (Broj, ime) VALUES (3,‘Caca’)
INSERT INTO A (Broj, ime) VALUES (5,‘Aca’)
INSERT INTO A (Broj, ime) VALUES (6,‘Aca’)
GO
CREATE TABLE B (Broj smallint, ime VARCHAR(5))
GO
INSERT INTO B (Broj, ime) VALUES (1,‘Aca’)
INSERT INTO B (Broj, ime) VALUES (NULL,‘Maca’)
INSERT INTO B (Broj, ime) VALUES (5,‘Caca’)
GO   

Pokusajmo da upotrebimo NOT IN da pokazemo, koji brojevi su u tabeli A, a nisu u tabeli B:

SELECT A.* 
FROM A
WHERE Broj NOT IN (SELECT Broj FROM B) 

Sledi rezultat:

  Broj  ime
-----  ------
(0 row(s) affected)

Ocigledno je da rezultat nije tacan. Pokusajmo sa NOT EXISTS:

SELECT A.* 
FROM A 
WHERE NOT EXISTS (SELECT Broj FROM B WHERE B.Broj = A.Broj)  

Rezultat je ovaj put tacan:

 Broj  ime
----- -----    
     2  Maca      
     3  Caca      
     6  Aca 

(3 row(s) affected)

Da probamo i LEFT JOIN:

SELECT A.* FROM A
LEFT JOIN B ON A.Broj = B.Broj
WHERE B.Broj IS NULL 

Dobijamo ponovo tacan rezultat:

 Broj  ime
----- -----
     2  Maca      
     3  Caca      
     6  Aca 

(3 row(s) affected)

Zasto je ovo ovako? Primetite da u tabeli B imamo na jednom mestu NULL. E, taj jedan NULL cini da ono sto imamo u IN (SELECT…) vraca NULL. Onda <nesto> IN NULL vraca NULL.

Sta bi bilo kad bi i prva tabela A imala neki NULL u koloni Broj?


UPDATE A SET Broj =  NULL WHERE ime = ‘Caca’
 

 

SELECT * FROM A


    Broj  ime
-------  ------
      1   Aca
      2   Maca
NULL  Caca
      5   Aca
      6   Aca

(5 row(s) affected)

‘ajmo sve ponovo:

SELECT A.* FROM A
WHERE Broj NOT IN (SELECT Broj FROM B)

  Broj ime
-----  -------
(0 row(s) affected)

SELECT A.* 
FROM A
WHERE NOT EXISTS (SELECT Broj FROM B WHERE B.Broj = A.Broj)

  Broj ime
—— —–
     2 Maca
  NULL Caca
     6 Aca
(3 row(s) affected)


SELECT A.*
FROM A
LEFT JOIN B ON A.Broj = B.Broj
WHERE B.Broj IS NULL

  Broj ime
—— —–
     2 Maca
  NULL Caca
     6 Aca
(3 row(s) affected) 

Kao sto se moglo ocekivati, NOT IN gresi, dok NOT EXISTS i LEFT JOIN rade.

Na nesrecu, NOT IN se mnogo lakse pise nego NOT EXISTS ili LEFT JOIN. Da stvar bude gora, u ranijim verzijama MS SQL, NOT EXISTS je radio znatno brze nego NOT IN.
E, MS SQL 2005 je ‘ispravio gresku’, te sada NOT IN radi isto tako brzo kao NOT EXISTS ili LEFT JOIN.
I to Microsoft oglasava kao ‘major improvement’, kao lakse se pise kod, zaboravite komplikovane sintakse itd.

Da se razumemo, ako nema NULL, onda sva tri nacina daju tacan rezultat. Znaci, dobar dizajn baze sa izbegavanjem NULL koiko god moze, pomaze da vas neki suptilni bagovi ne udare po glavi.

Ovaj “Write Post” editor me ubija. Dodaje razmake kad mu se prohte, prelama red gde mu padne napamet. Mislio sam da 30 godina od kako su kopjuteri izmisljeni mogu da naprave ljudski editor za web. Izgleda da nema nista od toga. Sve sto nije plain text mozes da zaboravis… Tuga jedna i zalost.

🙂

  1. 4 Responses to “NULLS, LEFT JOIN, EXISTS i NOT IN”

  2. Posto definitvno jako dobro objasnjavas, da li bi mogao da padne neki text o rekurzijama sa WITH u MS SQL 2005. Po cijeli dan se mucim sa njima. 🙁

    By Djordje on Jul 9, 2008

  3. WITH je veoma mocna naredba. Od kako sam naucio WITH, tesko da postoji situacija koju ne mogu da razresim, rekurzija ili ne. Medjutim, izgubio sam sposobnost da pisem kverije sa subkverijima iz prostog razloga sto se pomocu WITH sve to isto pise mnogo lakse i razumljivije. Zato bih vrlo rado vise pisao o WITH uopste. Sad kad znam da postoji interesovanje i da vise od tri coveka zaista citaju sta to mudro imamo da kazemo na ovom sajtu, pokusacu da uradim nesto na tu temu.

    Rekurzije su samo jedan oblik, prilicno nepovezan sa ostatkom WITH filozofije. Ja licno izbegavam rekurzije, rekurzivan nacin razmisljanja uopste. Stoga mi je tesko naci dobar primer gde bi se rekurzije mogle primeniti, a da ne zahteva neko drugo specijalisticko znanje izvan SQL-a. Gde vidim rekurzije jeste teorija grafova i hijerarhije, drvo odlucivanja i slicno. Problem je sto te oblasti same po sebi nisu bas jednostavne (barem meni) i kad spojite dve komplikovane stvari (WITH rekurzije i grafove) lako se moze covek izgubiti.

    Moze li da prvo damo pregled generalne upotrebe WITH, pa onda da se na kraju malo poigramo sa rekurzijama?

    By zidar on Aug 15, 2008

  4. Kad ostari covek on pocne da zaboravlja. Ako na ovom sajtu izaberes MS SQL videces da smo pisali o “Common Tabkle Expressions” – to je zvanicni naziv za WITH, i tu smo dali i nekoliko primera sa rekurzijom. Nema grafova i hijerarhija, ali ima racunanja vremena.

    Pogledaj kategoriju MS SQL:
    http://www.baze-podataka.net/category/ms-sql-server/ i videces da ima nesto o iterativnim ciklusima.

    By zidar on Aug 15, 2008

  5. Pocetnik sam u koristenju SQL servera 2008 , sa malo predznanja. Pri nestrucnom radu t,j, ucenju iskljucila sam servera i sad mi trazi login da bih se povezala s njim.( Pomozite mi oko toga) Hvala

    By SK on Feb 26, 2009

Post a Comment