Posted By: Jovo () on 'CZdatabases'
Title:     Jovo 2
Date:      Thu Oct  5 19:40:25 2000

Ahoj.

  Do tohoto problemu jsem se fakt zazral ... no spis jsem se v nem zamotal :-)
A nez jsem se vymotal, tak jsem pekne bloudil. Diky tomu text nakynul na 
velikost kolem 10kB. Kdo nechce cist, at mackne 'q'.

PS: Slibuju, ze takove traktaty uz ode mne neuvidite ;-]
                                                                     Jovo.
------------------------------------------------------------------------------

Z Jovova zapisniku - 2:"Jak lze take pouzit UNION" 
==================================================


  Kazdy programator obcas po obtezovani uzivatelem dospeje do stadia, kdy je 
treba data v nejake forme dostat ven. Jelikoz u nas UNLOADy nezabraly, 
programujeme "vystupni zpravy", cesky "sestavy", anglicky "report"y :-)
  Ve vetsine sestav je neco jako master-detail. Treba tohle mame v DB ... :

TABLE misto                             TABLE lidi

id        mesto         ulice           RC          Bydli Jmeno
======================================  ===================================
(serial) (char20)      (char30)         (char11)    (int) (char30)       
--------------------------------------  -----------------------------------
1         J.HRADEC      Tyrsova 626/II  741213,1234 2     SuE
2         LIBEREC       Veveri 2018     691234,1576 4     Vodnik     
3         PRAHA         Konipaskova 15  156611,4896       Bezdomovec
4         ZA TOPOLEM    Podle skal 1    168312,1236 1     Jovo
                                        496213,8965       Druhej Bezdomovec
                                        156793,1265 2     RoboShim           

... chceme udelat vystupni zpravu, kde bude neco jako

Misto1                          <-Blok mest (prvni iterace = master)
  vsichni, kdo bydli v miste 1    <-Blok lidi (druha iterace = detail)
Misto2
  vsichni, kdo bydli v miste 2
.................

Pokud opomenu naprosto brutalni, ale zato velmi ucinnou metodu, ve ktere se 
vyrobi jeden SELECT id,mesto,ulice,RC,Bydli,Jmeno FROM misto, lidi ... 
(pak se v prvni iteraci vybiraji jen id,mesto,ulice a v druhem zbytek), 
pak se vetsinou delaji dva SELECTy, pro kazdou iteraci jeden. 
  V nasem pripade tedy neco jako 

  1. iterace :
        SELECT  id,mesto,ulice
          FROM  misto, lidi
          WHERE misto.id = lidi.Bydli   --tato podminka zaruci, ze se vyberou
                                        --jen ty mista, kde nekdo bydli 
                                        --treba PRAHA se nevypise
  2. iterace
        SELECT  Jmeno
          FROM  lidi
          WHERE lidi.Bydli = <id z prvni iterace>

Tohle nam hodi:
---------------
Misto : 1 J.HRADEC      Tyrsova 626/II
                Jovo
Misto : 3 BRNO                  Veveri 12
                SuE
                RoboShim
Misto : 4 ZA TOPOLEM    Podle skal 1
                Vodnik

  Tak, a ted k cemu jsem se chtel dostat :-)  Ukol zni : Vypsat i lidi, 
kteri nikde nebydli. Jak na to, kdyz Bezdomovci nemaji bydliste a neni jak
tedy svazat druhou iteraci s prvni ? Reseni je nekolik:

1) Vyrobime jeste jednu iteraci, tu dame za tu prvni a druhou iteraci a 
   definujeme ji jako 
        SELECT Jmeno  FROM lidi WHERE lidi.Bydli IS NULL   
   To nekdy jde, nekdy ne a nekdy je to jen moc pracne.

2) Vyrobime si docasnou (TEMPORARY) tabulku, do ni nahrneme jak mista, kde 
   nekdo bydli, tak i radek navic, ktery bude pro Bezdomovce.
   To jde, ale je to spousta prace navic a jeste se musi opravovat SELECTY
   obou iteraci. 

3) Pouzijeme UNION :
  1. iterace :
        SELECT  id,mesto,ulice
          FROM  misto, lidi
          WHERE misto.id = lidi.Bydli
        UNION
        SELECT DISTINCT -1,"nema misto","nema ulici" 
          FROM misto
  2. iterace
        SELECT  Jmeno
          FROM  lidi
          WHERE isnull(lidi.Bydli,-1) = <id z prvni iterace>


  - pozn.: funkce isnull(par1,par2) dela toto :
                - kdyz je par1 NULL, pak vrati par2
                - jinak vrati par1
        - takze u Bezdomovcu vyhodi, ze maji Bydli nastavenou na -1


  
to nam vyhodi:
Misto : 1 J.HRADEC      Tyrsova 626/II
                Jovo
Misto : 3 BRNO          Veveri 12
                SuE
                RoboShim
Misto : 4 ZA TOPOLEM    Podle skal 1
                Vodnik
Misto:  -1 nema misto   nema ulici
                Bezdomovec
                Druhej Bezdomovec

  Elegantni a jednoduche. A ted prichazim k tomu, o co jsme si nabili drzku -
"vono to vobcas nefunguje, cece" :-) Uskali je nekolik :

1) nikdo nam nezaruci, ze id v tabulce misto bude vzdy ruzne od -1. Nejaky
  proces muze byt naprogramovan tak, aby do serial nacpal svoje vlastni 
  cislo. Kdyz chceme pouzit cislo, muzeme se trefit do neceho, co tam uz je.
   Tomu se da zabranit, ze si zjistime nejake cislo idcka, ktere tam neni
  nez zacneme delat iterace. Da se pouzit treba 
  SELECT MAX(id) INTO CISLO FROM misto
  CISLO = CISLO + 1
  1. iterace :
        SELECT  id,mesto,ulice
          FROM  misto, lidi
          WHERE misto.id = lidi.Bydli
        UNION
        SELECT DISTINCT CISLO,"nema misto","nema ulici" 
          FROM misto
  2. iterace
        SELECT  Jmeno
          FROM  lidi
          WHERE isnull(lidi.Bydli,CISLO) = <id z prvni iterace>
  - v tomto pripade bude CISLO rovno 5
  - pokud delate v cistem SQL, ve vyrazu 
                SELECT DISTINCT CISLO,"nema misto","nema ulici" 
        nahradte "CISLO" vyrazem
                (SELECT (MAX(id)+1) FROM misto)

  To ma zase tu vadu, ze pokud nad DB pracuje vic uzivatelu, muze se stat, ze
do tabulky misto nekdo mezitim vlozi dalsi misto (c.5) a lidi, kteri budou
bydlet zrovna "na petce", sestava zaradi mezi Bezdomovce. Co s tim ?
Navic ten UNION muze selhat, viz [2].

 a - vykaslat se na to s tim, ze to nastane jednou za milion let. Pokud muzu 
   rict, jednou za milion let znamena obvykle zitra :-)
 b - CISLO = CISLO + 1500000 To take neni moc systemove, ze, ale lepri nez [a]
 c - zamknout tabulky (LOCK TABLE) v dobe vytvareni sestavy pro zapis. Ok, ale
   proces nesmi spadnout (aby nezustaly viset zamky) a bezet hodne rychle, at
   se ostatni uzivatele cekanim nezblazni. Nepouzitelne pro viceuzivatelske
   systemy s rychlou obmenou dat. Navick kdyz mate tabulku distribuovanou
   do dalsich patnacti mist ...
 d - vyhrnout si do TEMPORARY tabulek data a pak v klidu z nich udelat sestavu
   (CREATE TEMP TABLE misto_tmp (...jako misto...); 
        INSERT INTO SELECT * FROM misto;
    CREATE TEMP TABLE lidi_tmp (...jako lidi...); 
        INSERT INTO SELECT * FROM lidi 
                     WHERE lidi.Bydli=misto_tmp; 
    vlozeni radku pro bezdomovce;).
   Prace navic, je to o DOST pomalejsi, zere to cas procesoru a hlavne 
   potrebuje misto na disku.


2) Kviz. 
 a - Co vyhodi  SELECT -1,"nema misto","nema ulici" FROM misto (Bez DISTINCT) ?
     - odpoved: ctyrikrat to same, v nasem pripade da :
                -1,"nema misto","nema ulici"
                -1,"nema misto","nema ulici"
                -1,"nema misto","nema ulici"
                -1,"nema misto","nema ulici"
       proto tam musi byt DISTINCT

 b - A ted to, co nas stalo pul hodiny ladeni :-)  Co vyhodi
     SELECT DISTINCT -1,"nema misto","nema ulici" pokud bude tabulka 
     misto prazdna ?
     - odpoved: VUBEC NIC !  :-) Pricina je, ze SELECT prochazi tabulku radek 
       po radku a kdyz podminka sedi, tak vypise to, co je za klauzuli SELECT.
    Proto se musi kod opravit
       SELECT MAX(id) INTO CISLO FROM misto -> 
                SELECT isnull(MAX(id),1) INTO CISLO FROM misto

3) Vzpomenete, co se pise v prirucce o typu serial : 
    Pokud do databaze vkladate radek stylem 
   INSERT INTO misto VALUES(0,mesto,ulice), pak se vam misto nuly do DB
   spocita idcko. To znamena, ze nula tam nikdy nebude. Kdybychom udelali v
   nasem pripade INSERT INTO misto VALUES(0,"PARDUBICE","Pernikova 11"), do 
   databaze se vlozi radek 
        5,PARDUBICE,Pernikova 11
   (pokud nebude DB stroj nakonfigurovany, aby k serialu pridaval neco jineho,
   nebo pokud treba stroj nespadne a pak zacne pridelovat od jineho cisla).

  Takze finalni reseni je:
    1. iterace :
        SELECT  id, mesto,ulice
          FROM  misto, lidi
          WHERE misto.id = lidi.Bydli
        UNION
        SELECT DISTINCT 0,"nema misto","nema ulici" 
          FROM lidi
    2. iterace
        SELECT  Jmeno
          FROM  lidi
          WHERE isnull(lidi.Bydli,0) = <id z prvni iterace>

  Vsimnete si, ze SELECT za UNION vybira (FROM) z tabulky lidi. Duvod : 
  viz bod [2b] - pokud bude tabulka mist prazdna, SELECT za UNIONem 
  nevybere nic. Jelikoz tiskneme lidi, tak nam pripad, ze se nevybere
  nic z lidi nevadi, protoze by to stejne zadne lidi nevytisklo.
  Konec postupu. Uffff :-)
 
Vas Jovo.
============================================================================
PS:
 -   Zkuste vymyslet reseni pro pripad, kdyz sloupec id nebude serial, 
     ale treba jen INT ... spravne, tady jde pouzit uz jen [1], popripade
     [2d] :-)

 -   Pozor! : UNION vyhazuje duplicity, takze pokud date
     SELECT DISTINCT 1 FROM lidi 
     UNION 
     SELECT DISTINCT 1 FROM lidi, pak to nevybere 
        1 
        1
    , ale pouze jednu jednicku !
        1
    V nasi modelove situaci vyhazovani duplicit to nevadi, ale jinak s 
    UNIONEM opatrne !

 - Pokud byste chteli optimalizovat vyraz az do mrte, pak si muzete
   vsimnout, ze tabulka lidi muze mit milion radku. Potom
     SELECT DISTINCT 0,"nema misto","nema ulici" FROM lidi  
   muze na spatne nakonfigurovanych strojich bezet hodne dlouho. Da se to 
   vyresit pres docasnou tabulku.
     CREATE TEMP TABLE dummy (num  INTEGER);
     INSERT INTO dummy VALUES(0);
   a pak jen 1. iteraci upravit na :
        SELECT  id,mesto,ulice
          FROM  misto, lidi
          WHERE misto.id = lidi.Bydli
        UNION
        SELECT num,"nema misto","nema ulici" 
          FROM dummy
 - Pro ty, kteri/ktere si priklady chteji overit : 
          create temp table misto   (
            id serial,
            mesto char(20),
            ulice char(30)     
          );
          create temp table lidi    (
            RC char(11),
            Bydli int,     
            Jmeno char(30)     
          );
          insert into misto values(1,"J.HRADEC","Tyrsova 626/II");
          insert into misto values(2,"LIBEREC","Veveri 2018");    
          insert into misto values(3,"PRAHA","Konipaskova 15");
          insert into misto values(4,"ZA TOPOLEM","Podle skal 1");
          insert into lidi values("741213,1234",2,"SuE");
          insert into lidi values("691234,1576", 4,     "Vodnik"); 
          insert into lidi values("156611,4896",NULL,   "Bezdomovec");
          insert into lidi values("168312,1236", 1,     "Jovo");
          insert into lidi values("496213,8965",NULL,   "Druhej Bezdomovec");
          insert into lidi values("156793,1265", 2,     "RoboShim");
          select * from lidi;
          select * from misto;
          drop table dummy;
          drop table misto;
          drop table lidi;

Search the boards