SQL - združevanje tabel 2. del

SQL - združevanje tabel 2. del

Avtor: Matija Lokar

SELECT in več tabel

  • "Prave" baze sestavlja več tabel
  • Pogosto želimo kot rezultat dobiti podatke, ki se "skrivajo" v več tabelah
(tabele.jpg)

Združene tabele

  • V kateri državi so bile leta 2004 OI?
  • Za vsako državo izpiši, kolikokrat je priredila OI
  • Da odgovorimo, potrebujemo podatke obeh tabel
(slika1.jpg)

Združene tabele

  • seveda moramo računati na nepopolne podatke
(slika2.jpg)

Združevanje tabel

  • Navedimo več tabel
  • SELECT * FROM zaposleni, naslovi
  • Dobimo kartezični produkt, torej n x m zapisov (če je n število zapisov v prvi, m pa v drugi tabeli)

    • V primeru zaposlenih 5 x 4 = 20 zapisov!
    • Kot bi vsak zaposleni stanoval na VSEH naslovih!
  • Za OI

    • 5 x 3 zapisov
  • Seveda pa je to včasih čisto smiselno

    • Seznam deklet
    • Seznam fantov
    • Izvedena tabela: možni pari
(slika3.jpg)

Združevanje tabel

  • Kako do določenih stolpcev?

    • SELECT ime FROM zaposleni, naslovi
    • Kam "spada" ime?
    • SELECT zaposleni.ime FROM zaposleni, naslovi
    • Torej ime_tabele.ime_stolpca
    • Če ni možnosti za zmedo (ime stolpca se pojavi le v eni od navedenih tabel), lahko ime tabele spustimo.
  • SELECT kraj FROM OI, Mesta
  • SELECT * FROM OI, Mesta WHERE kraj = 'Atene'
  • Na kateri stolpec kraj mislimo?
  • SELECT OI.kraj FROM OI, Mesta
  • SELECT OI.kraj FROM OI, Mesta WHERE Mesta.kraj = 'Atene' AND drzava = 'Kitajska'

Združevanje na osnovi ključev

  • Primarni ključ, tuji (zunanji) ključ, ...
  • SELECT * FROM zaposleni, naslovi WHERE naslovi.IDZaposlenega = zaposleni.IDZaposlenega
  • Zapisi, kjer se primarni ključ tabele zaposleni ujema z zunanjim ključem tabele naslovi
(slika4.jpg)
  • Pozor! Združena tabela ni taka kot smo želeli (P18) prej!
  • "Zgubili" smo zapis za Matejo Likar, za katero žal ne poznamo naslova!

Združevanje

  • Združiti v celoto vrstice prve tabele, ki se ujemajo z vrsticami druge tabele.
  • Relacije:

    • 1:1 ... Vrstica prve tabele se ujema z eno (ali nobeno) vrstico druge tabele
    • 1:n ... Vrstica prve tabele se ujema z več vrsticami druge tabele
  • Ujemanje (relacije) izvedemo preko ključev

    • Primarni ključ

      • Stolpec ali kombinacija stolpcev, ki eneolično določa vrstico
    • Tuji (zunanji) ključ

      • Primarni ključ druge tabele

Ključi

(slika5.jpg)

Tuj (zunanji) ključ:

  • verjetno: IDOddelka

Primarni ključ:

  • IDZaposlenega (pogosto namensko polje!)
  • ImePriimek (če ni dveh "JanezNovak")

Ključi

(slika6.jpg)

Izbor le določenih vrednosti

  • Denimo, da nas zanimajo podatki o zaposlenem s šifro MK1
  • SELECT * FROM zaposleni, naslovi
         WHERE IDZaposlenega = "MK1
    "

    • Napaka! IDZaposlenega je v obeh tabelah!
  • SELECT * FROM zaposleni, naslovi
         WHERE zaposleni.IDZaposlenega = "MK1
    "
  • Koliko zapisov dobimo?

    • 4 (za vsako vrstico iz naslovov)
  • SELECT * FROM zaposleni, naslovi
         WHERE naslovi.IDZaposlenega = "MK1
    "
  • Koliko zapisov dobimo?

    • 5 (za vsako vrstico iz tabele zaposlenih)
  • SELECT * FROM zaposleni, naslovi
         WHERE naslovi.IDZaposlenega = zaposleni.IDZaposlenega = "MK1

    • Seveda narobe!
  • SELECT * FROM zaposleni, naslovi
         WHERE naslovi.IDZaposlenega = zaposleni.IDZaposlenega AND naslovi.IDZaposlenega = "MK1
    "
  • Ali ne gre "lepše"?

Notranji stik

  • Dejansko gre le za "lepšo" obliko
  • SELECT * FROM zaposleni
         INNER JOIN  naslovi ON (naslovi.IDZaposlenega = zaposleni.IDZaposlenega)
         WHERE  naslovi.IDZaposlenega = "MK1
    "
  • Torej – tabela, iz katere pobiramo podatke je tabela, ki nastane, če združimo vrstice tabele zaposleni s tistimi vrsticami tabele naslovi, kjer se vsebini polj IDZaposlenega ujemata
  • Iz tako dobljene tabele pa upoštevamo le vrstice, kjer velja naslovi.IDZaposlenega = "MK1"
  • Samo združevanje ustreznih vrstic (brez "filtriranja")

    • SELECT * FROM zaposleni
            INNER JOIN  naslovi ON (naslovi.IDZaposlenega = zaposleni.IDZaposlenega)
  • Stike običajno izvajamo preko ključev (primarni s tujim)

    • Ni nujno, da se ključa v tabelah imenujeta enako!

Notranji stik

  • Če obstajajo zapisi v tabeli zaposlenih, ki nimajo pripadajočega zapisa v tabeli naslovov, jih v tabeli ni.

    • V tabeli zaposlenih je nekdo z IDZaposlenega ML1. Če v tabeli naslovon ni vrstice, kjer bi bil stolpec IDZaposlenega enak ML1, potem vrstice s "pridruženimi" podatki (praznimi) ni!
  • Opombe

    • INNER lahko spustimo (včasih moramo - RDBMS uporablja drugačen "standard")
    • Včasih je možna le oblika, kot je na prejšnji prosojnici (Oracle 8i in starejši, 9i vpelje to (standardno) obliko)
    • ...

OI

  • SELECT * FROM oi INNER JOIN kraji ON (oi.kraj = kraji.kraj)
(slika7.jpg)

OI

  • SELECT * FROM kraji INNER JOIN oi ON (oi.kraj = kraji.kraj)
(slika8.jpg)

Vrstni red pomemben za vrstni red "zadetkov"

  • SELECT * FROM oi INNER JOIN kraji ON (oi.kraj = kraji.kraj)
  • SELECT * FROM kraji INNER JOIN oi ON (oi.kraj = kraji.kraj)
(slika9.jpg)

Združevanje - zgled

(slika10.jpg) (slika11.jpg)

Združevanje - zgled

(slika12.jpg)

Zgleda

  • Kateri glasbenik je posnel pesem 'Exodus'?

    • Naslov pesmi je v tabeli Track, ime izvajalca pa v tabeli Album.
    • SELECT artist  FROM album
            JOIN track ON (album.asin = track.album)
                WHERE song = 'Exodus'
(slika13.jpg)

Zgleda

  • Za vsak album skupine 'Beatles' izpiši njegov naslov in število skladb, ki so na njem

    • SELECT title AS Naslov, COUNT(*) AS 'Št. skladb' FROM album JOIN track ON (asin = album)
         WHERE artist LIKE '%Beatles%'
            GROUP BY title
(slika14.jpg)

Zunanji stik

  • Kaj, če bi želeli dobiti pare, kjer leva (ali desna) tabela nima para

    • V tabeli zaposlenih je nekdo z IDZaposlenega ML1 in v tabeli naslovov ni vrstice, kjer bi bil stolpec IDZaposlenega enak ML1.
    • Želimo dobiti združeno vrstico, kjer so podatki le o ML1 (pripadajočih podatkov o naslovu pa ni)!
(tabele1.jpg)

OI

(slide22.jpg)

Oracle

  • SELECT * FROM zaposleni, naslovi
         WHERE zaposleni.IDZaposlenega = naslovi.IDZaposlenega(+)
  • Če desno NI ustreznega ključa, vzamemo polja s samimi praznimi vrednostmi
  • SELECT * FROM zaposleni, naslovi
         WHERE zaposleni.IDZaposlenega(+) = naslovi.IDZaposlenega
  • Če levo NI ustreznega ključa, vzamemo polja s samimi praznimi vrednostmi
  • Do različice 8i
  • Različica 9i (trenutna je 11g) vpelje "standardno" sintakso

LEFT OUTER JOIN

  • SELECT * FROM zaposleni
         LEFT OUTER JOIN  naslovi ON (naslovi.IDZaposlenega = zaposleni.IDZaposlenega)
  • Leva tabela (zaposleni ) je glavna – vse vrstice iz nje zagotovo dobijo par! Če v desni (naslovi ) ni ustrezne vrstice, se vzame taka z ničelnimi vrednostmi.
  • Torej dobimo tabelo, ki ima vsaj toliko vrstic kot leva (glavna) tabela.
  • To smo mi želeli (glede na sliko)!
(tabele.jpg)

OI - LEFT OUTER JOIN

(slika15.jpg)

SELECT oi.leto, oi.kraj, kraji.drzava
FROM oi LEFT OUTER JOIN kraji ON
(oi.kraj = kraji.kraj)

SELECT oi.leto, oi.kraj, kraji.drzava
FROM kraji LEFT OUTER JOIN oi ON
(oi.kraj = kraji.kraj)

RIGHT OUTER JOIN

  • SELECT * FROM zaposleni
         RIGHT OUTER JOIN  naslovi
             ON (naslovi.IDZaposlenega = zaposleni.IDZaposlenega)
  • Desna tabela (naslovi ) je glavna – vse vrstice iz nje dobijo vsaj en par! Če v levi tabeli (zaposleni )ni ustrezne vrstice, se vzame taka z ničelnimi vrednostmi.

OI – RIGHT OUTER JOIN

(slide27.jpg)

SELECT oi.leto, oi.kraj, kraji.drzava
   FROM kraji RIGHT OUTER JOIN oi ON
   (oi.kraj = kraji.kraj)

SELECT oi.leto, oi.kraj, kraji.drzava
   FROM oi RIGHT OUTER JOIN kraji ON
   (oi.kraj = kraji.kraj)

OI – OUTER JOIN

(slide28.jpg)

SELECT oi.leto, oi.kraj, kraji.drzava
   FROM oi LEFT OUTER JOIN kraji ON
   (oi.kraj = kraji.kraj)

SELECT oi.leto, oi.kraj, kraji.drzava
   FROM oi RIGHT OUTER JOIN kraji ON
   (oi.kraj = kraji.kraj

FULL OUTER JOIN

OI – FULL OUTER JOIN

(slika2.jpg)

SELECT oi.leto, oi.kraj, kraji.drzava
   FROM oi FULL OUTER JOIN kraji ON
   (oi.kraj = kraji.kraj)

OI – JOIN(i)

(slide31.jpg)

SELECT oi.leto, oi.kraj, kraji.drzava
FROM oi FULL OUTER JOIN kraji ON
(oi.kraj = kraji.kraj)

(slide31a.jpg)

SELECT oi.leto, oi.kraj, kraji.drzava
FROM oi LEFT OUTER JOIN kraji ON
(oi.kraj = kraji.kraj)

(slide31b.jpg)

SELECT oi.leto, oi.kraj, kraji.drzava
FROM oi RIGHT OUTER JOIN kraji ON
(oi.kraj = kraji.kraj)

(slide31c.jpg)

SELECT oi.leto, oi.kraj, kraji.drzava
FROM oi INNER JOIN kraji ON
(oi.kraj = kraji.kraj)

(slide31d.jpg)

Vsa združevanja

  • Kartezični produkt
  • Notranje
  • Zunanje

    • Levo
    • Desno
    • Polno

Celoten stavek SELECT

SELECT [STRAIGHT_JOIN] [SQL_SMALL_RESULT] [SQL_BIG_RESULT]
[SQL_BUFFER_RESULT][SQL_CACHE | SQL_NO_CACHE]
[SQL_CALC_FOUND_ROWS] [HIGH_PRIORITY] [DISTINCT | DISTINCTROW |
ALL] select_expression,... [INTO {OUTFILE | DUMPFILE}
'file_name' export_options] [FROM table_references [WHERE
where_definition] [GROUP BY {unsigned_integer | col_name |
formula} [ASC | DESC], ... [WITH ROLLUP]] [HAVING
where_definition] [ORDER BY {unsigned_integer | col_name |
formula} [ASC | DESC] ,...] [LIMIT [offset,] row_count |
row_count OFFSET offset] [PROCEDURE
procedure_name(argument_list)] [FOR UPDATE | LOCK IN SHARE
MODE]]

table_references: table_reference, table_references
table_reference [INNER | CROSS] JOIN table_reference
[join_condition] table_reference STRAIGHT_JOIN table_reference
table_reference LEFT [OUTER] JOIN table_reference
[join_condition] table_reference NATURAL [LEFT [OUTER]] JOIN
table_reference table_reference RIGHT [OUTER] JOIN
table_reference [join_condition] table_reference NATURAL [RIGHT [OUTER]]
JOIN table_reference

table_reference: table_name [[AS] alias] [[USE INDEX (key_list)]
| [IGNORE INDEX (key_list)] | [FORCE INDEX (key_list)]]

join_condition: ON conditional_expr | USING (column_list)

Primer

  • Dve tabeli Naročniki in NarocilaNarocnikov
(slide34a.jpg)


(slide34b.jpg)
  • Želimo izpisati skupno količino vseh naročnikov, ki se pišejo Kmetec in ki so naročali več kot enkrat
(slide34c.jpg)

Poizvedba

SELECT Nar.NID, Nar.NIme, Nar.NPriimek,
   COUNT(NNar.NarociloID) StNar,
   SUM(Nnar.Kolicina) Kol

   FROM Narocniki Nar LEFT OUTER JOIN
   NarocilaNarocnikov NNar ON
   Nar.NarocnikID = NNar.NarocnikID

   WHERE Nar.NarocnikPriimek = 'Kmetec'

   GROUP BY NarocnikID, NarocnikIme, NarocnikPriimek
   HAVING COUNT(NarociloID) > 1

   ORDER BY NarocnikID

Vrstni red izvajanja

(7)SELECT <seznam> (8) DISTINCT (10) TOP
(1) FROM <leva table>
(3) <tip združevanja> JOIN <desna tabela>
(2) ON <pogoji združevanja>
(4) WHERE <omejitveni pogoji>
(5) GROUP BY <razvrščanje v skupine>
(6) HAVING <omejitveni pogoji>
(9) ORDER BY <urejanje>

Prvi korak

  • FROM Narocniki Nar ... JOIN NarocilaNarocnikov NNar
(slide37.jpg)

Drugi korak

  • Filter ON (Nar.NarocnikID = NNar.NarocnikID)
  • Izvedemo primerjanja
(slide38.jpg)
  • obdržimo vrstice s TRUE

    (slide38a.jpg)

Zunanje združevanje

  • Dodamo "leve" oz "desne vrstice"
(slide39.jpg)

Where

  • WHERE Nar.NPriimek = 'Kmetec'
(slide40.jpg)

Group By

  • GROUP BY NarocnikID, NarocnikIme , NarocnikPriimek
(slide41.jpg)

S Having izločimo skupine

  • HAVING COUNT(NarociloID) > 1
(slide42.jpg)

In sedaj so na vrsti stolpci

  • SELECT Nar.NID, Nar.NIme, Nar.NPriimek, COUNT(NNar.NarociloID) StNar, SUM(Nnar.Kolicina) Kol
(slide43.jpg)

Celoten stavek SELECT

SELECT [STRAIGHT_JOIN] [SQL_SMALL_RESULT] [SQL_BIG_RESULT]
[SQL_BUFFER_RESULT][SQL_CACHE | SQL_NO_CACHE]
[SQL_CALC_FOUND_ROWS] [HIGH_PRIORITY] [DISTINCT | DISTINCTROW |
ALL] select_expression,... [INTO {OUTFILE | DUMPFILE}
'file_name' export_options] [FROM table_references [WHERE
where_definition] [GROUP BY {unsigned_integer | col_name |
formula} [ASC | DESC], ... [WITH ROLLUP]] [HAVING
where_definition] [ORDER BY {unsigned_integer | col_name |
formula} [ASC | DESC] ,...] [LIMIT [offset,] row_count |
row_count OFFSET offset] [PROCEDURE
procedure_name(argument_list)] [FOR UPDATE | LOCK IN SHARE
MODE]]

table_references: table_reference, table_references
table_reference [INNER | CROSS] JOIN table_reference
[join_condition] table_reference STRAIGHT_JOIN table_reference
table_reference LEFT [OUTER] JOIN table_reference
[join_condition] table_reference NATURAL [LEFT [OUTER]] JOIN
table_reference table_reference RIGHT [OUTER] JOIN
table_reference [join_condition] table_reference NATURAL [RIGHT [OUTER]]
JOIN table_reference

table_reference: table_name [[AS] alias] [[USE INDEX (key_list)]
| [IGNORE INDEX (key_list)] | [FORCE INDEX (key_list)]]

join_condition: ON conditional_expr | USING (column_list)
0%
0%