1. SQL spørringer mot flere tabeller Avdeling for informatikk og e-læring, Høgskolen i Sør-Trøndelag SQL spørringer mot flere tabeller Tore Mallaug 29.9.2008 Lærestoffet er utviklet for faget Databaser Resymé: Denne andre leksjonen om SQL omhandler spørringer som henter data fra flere tabeller. Det vises noen eksempler på spørringer som utfører forening (eng. join) av to eller flere tabeller. Innhold 1.1. KOMMENTARER TIL LÆREBOKA... 1 1.2. EKSEMPEL: TIMEAVTALER... 2 1.3. FORENING AV TO TABELLER... 3 1.4. FORENING AV TRE TABELLER... 4 1.5. FORENING AV ALLE TABELLENE... 5 1.6. KOMBINERE JOIN MED GROUP BY... 6 1.1. Kommentarer til læreboka Denne andre leksjonen om SQL omhandler flertabellspørringer (forening) f.o.m. kap. 5.1 t.o.m. kap. 5.3 (gjelder 2.utgave av læreboka, i 1.utgave står dette til slutt i kap. 4). Det er først og fremst likhetsforening (equi-join) som er den absolutt essensielle her. Dette må du kunne på eksamen! Kap. 5.1.2 beskriver to måter du kan skrive likhetsforening på i SQL. Enten den moderne måten med INNER JOIN, eller gammelmåten hvor du skriver likhetsuttrykket for foreningen som en del av WHERE-uttrykket (se eksempel på s. 158). Begge måtene godtas, så velg den måten du liker best. Uansett, husk at hvis du skal hente ut data fra to eller flere tabeller (kan være flere enn tre tabeller), så må du ALLTID forene tabellene. Hvis ikke får du det kartesiske produkt, og resultatet blir bare rot. Mangel på forening er en av de vanligste feilene på eksamen og også en av de feilene det er lettest å trekke for (en av de andre er feil / mangelfull bruk av primær- og fremmednøkler). Så lær deg dette. På den andre siden bør du ikke forene mer enn nødvendig. Ved store databaser / tabeller er forening en relativt tidkrevende prosess for databasesystemet som i verste fall kan gå utover responstiden. Så det hjelper ikke med helgardering på eksamen (selv om det ofte er bedre enn ingen forening!) foren kun de tabellene du henter data fra. I praksis vil en forene mellom nøkler i to tabeller, typisk mellom primærnøkkelen (evt. deler av en sammensatt nøkkel) i den ene tabellen med en fremmednøkkel i den andre. Evt. mellom to primærnøkler hvis to tabeller har samme nøkkel. Det gir som regel det mest fornuftige resultatet. Dette er poengtert i læreboka. Imidlertid er det ingenting i veien for å forene mellom andre attributter også, gitt at disse har samme datatype / domene. Men så lenge vi har
SQL spørring mot flere tabeller side 2 av 6 primær- og fremmednøkler, vil foreningen på nøklene lime tabellene sammen, og vi kan plukke ut de tuplene vi vil ha i WHERE-utrykket. Kap. 5.1.3 nevner et særtilfelle hvor vi forener internt i samme tabell. Dette er nyttig hvis en ønsker å sammenligne tupler i en tabell opp mot hverandre. Kap. 5.1.4 RIGHT JOIN, LEFT JOIN og FULL JOIN er mindre vanlig enn INNER JOIN, men brukes i enkelte tilfeller men lær deg INNER JOIN eller gammel måten først! Kap. 5.1.6 viser et eksempel på EXISTS, mens kap.5.3 viser bruk av UNION. Les igjennom disse eksemplene. Eksamensoppgaver hvor EXISTS kan brukes har forekommet, mens UNION er sjelden i praksis. Så til slutt noen ekstra eksempler i timeavtaledatabasen. SQL-koden fungerer både i MS Access og MySQL, men i MySQL er datoformatet annerledes. 1.2. Eksempel: timeavtaler Tabellene fra forrige leksjon er som følger: TIMEBESTILLING (ansattnr*, pasientnr*, dag*, tid) LEGE (ansattnr, legenavn) PASIENT (pasientnr, fornavn, etternavn, gateadresse, postnr, poststed, telefon) LEGE_ROM (ansattnr*, dag, rom*) ROM_BEHANDLING (rom, behandling) Understrekete attributter er primærnøkkel. Attributter merket * er fremmednøkler.
SQL spørring mot flere tabeller side 3 av 6 1.3. Forening av to tabeller Oppgave: Skriv ut navnet til de pasientene som har timebestilling den 12.09.03 (Merk deg: i MySQL blir datoformatet her '2003-09-12'). Svar Query 2_1: FROM TIMEBESTILLING, PASIENT WHERE TIMEBESTILLING.pasientnr = PASIENT.pasientnr AND dag = '12.09.03'; eller: FROM TIMEBESTILLING INNER JOIN PASIENT ON TIMEBESTILLING.pasientnr = PASIENT.pasientnr WHERE dag='12.09.03'; Hva skjer i denne spørringa? Attributtene (fornavn, etternavn) finnes i tabellen PASIENT, mens hvem som har bestilt time den 12.09.03 finnes i TIMEBESTILLING. Siden pasientnr finnes i begge tabellene (pasientnr er primærnøkkel i PASIENT og en fremmednøkkel i TIMEBESTILLING) kan vi joine på pasientnr. SELECT-linja er egentlig en projeksjon hvor vi henter ut (fornavn, etternavn) fra resultatet fra likhetsforeningen. Merk deg bruken av alias (står også nevnt i læreboka). Hvis vi i de to tabellene har attributt som heter det samme, i dette tilfelle pasientnr, må vi oppgi fra hvilke tabell vi ønsker å hente pasientnr fra ved et alias foran attributtnavnet, her TIMEBESTILLING.pasientnr og PASIENT.pasientnr. Hvis et attributtnavn finnes bare en plass i de joinede tabellene, som dag og fornavn, etternavn her, trenger vi ikke å bruke alias på dem. Hvis databasesystemet ikke forstår hvilke attributt du mener, kommer det enten en feilmelding eller en dialogboks som spør etter verdien. Dette betyr enten at du har glemt aliaset eller at du har skrevet attributtnavnet feil (typisk hvis dialogboksen kommer opp). Det er mulig å sette et kortere alias enn tabellnavnet slik: FROM TIMEBESTILLING T, PASIENT P WHERE T.pasientnr=P.pasientnr AND dag='12.09.03';
SQL spørring mot flere tabeller side 4 av 6 1.4. Forening av tre tabeller Oppgave: Skriv ut navnene på pasientene med timebestilling på rom b01 den 23.09.03. Svar Query 2_2: FROM TIMEBESTILLING, PASIENT, LEGE_ROM WHERE TIMEBESTILLING.pasientnr=PASIENT.pasientnr AND TIMEBESTILLING.ansattnr=LEGE_ROM.ansattnr AND TIMEBESTILLING.dag=LEGE_ROM.dag AND LEGE_ROM.dag='23.09.03' AND rom='b01'; eller: FROM (TIMEBESTILLING INNER JOIN PASIENT ON TIMEBESTILLING.pasientnr = PASIENT.pasientnr) INNER JOIN LEGE_ROM ON (TIMEBESTILLING.dag = LEGE_ROM.dag) AND (TIMEBESTILLING.ansattnr = LEGE_ROM.ansattnr) WHERE LEGE_ROM.dag='23.09.03' AND rom='b01'; Resultat: fornavn etternavn Gunnar Gran Hva skjer i denne spørringa? Her må vi joine både TIMEBESTILLING, PASIENT og LEGE_ROM. Hvorfor? TIMEBESTILLING er fortsatt grunntabellen for å finne ut hvem som har bestillinger den 23.09.03. Siden vi trenger (fornavn, etternavn) i svaret må PASIENT joines inn. Og siden vi trenger rom i WHERE-uttrykket trenger vi også å joine inn LEGE_ROM. For å finne ut hvor mange tabeller du tenger i en slik oppgave må du altså finne ut hvilke attributter du trenger i resultatet og i WHERE-uttrykket. Legg merke til at vi her bruker alias foran dag også i WHERE-uttrykket siden dag finnes både i TIMEBESTILLING og i LEGE_ROM (har ingen praktisk betydning hvilke dag vi bruker her, men Access gir feilmelding om vi ikke har med et alias). Legg også merke til hvordan sammensatte nøkler joines (du må joine hvert enkelt attributt i nøkkelen).
SQL spørring mot flere tabeller side 5 av 6 1.5. Forening av alle tabellene Oppgave: Skriv ut en (unormaliserte) resultattabell med attributtene fra alle tabellene i databasen. Poenget med denne oppgaven er å vise at alle tabellene lar seg forene til en stor. Dette er mulig hvis det finnes fremmednøkler i databasen slik at alt lar seg forene. (databasen er for øvrig normalisert hvis ingen attributter forekommer mer enn en gang i databasen med unntak av fremmednøkler (de medfører dessverre litt dobbeltlagring av nøkkelattributter)). Svar Query 2_3: SELECT T.ansattnr, L.legenavn, T.pasientnr, P.fornavn, P.etternavn, P.gateadresse, P.postnr, P.poststed, P.telefon, T.dag, T.tid, LR.rom, RB.behandling FROM TIMEBESTILLING T, PASIENT P, LEGE_ROM LR, LEGE L, ROM_BEHANDLING RB WHERE T.pasientnr=P.pasientnr AND T.ansattnr=LR.ansattnr AND T.dag=LR.dag AND T.ansattnr=L.ansattnr AND LR.rom = RB.rom ORDER BY T.dag; Resultat (har delt opp tabellen i to deler her grunnet plassmangel på siden): ansattnr legenavn pasientnr fornavn etternavn gateadresse postnr poststed telefon L102 Eva Moe P106 Gunnar Gran 7340 MELHUS 72727272 L100 Ole Aas P105 Per Olsen Bergbakken 7000 TRONDHEIM L100 Ole Aas P100 Gro Hansen Bakkegata 10 7021 TRONDHEIM 99999999 L110 Lise Berg P108 Hege Hansen Bakkegata 10 7021 TRONDHEIM 47404040 L102 Eva Moe P106 Gunnar Gran 7340 MELHUS 72727272 dag tid rom behandling 12.09.03 10:00 k11 konsultasjon 12.09.03 12:00 k10 konsultasjon 12.09.03 10:00 k10 konsultasjon 13.09.03 10:00 k12 konsultasjon 23.09.03 09:00 b01 kirurgisk
SQL spørring mot flere tabeller side 6 av 6 Merk deg at T.ansattnr er med i to joining her, både med L.ansattnr og LR.ansattnr. Dette er altså fullt mulig. Tar ikke med INNER JOIN-versjonen her (blir omfattende i dette eksemplet). 1.6. Kombinere join med GROUP BY Oppgave: Skriv ut (fornavn, etternavn) på de pasientene som har minst 2 bestillinger hos lege Eva Moe. Svar Query 2_4: FROM TIMEBESTILLING T, PASIENT P, LEGE L WHERE T.pasientnr=P.pasientnr AND T.ansattnr=L.ansattnr AND legenavn = 'Eva Moe' GROUP BY T.pasientnr, fornavn, etternavn HAVING COUNT(*)>1; Svar: fornavn etternavn Gunnar Gran Har med pasientnr i GROUP BY her for å skille evt. pasienter med samme (fornavn, etternavn).