Kapittel 2 · 2B · Lærebok 2.6 + 4.1

Relasjonsalgebra

Operatorer som tar relasjoner inn og gir relasjoner ut. Det formelle språket optimalisatoren bruker når SQL skal kjøres.

01 · Hvorfor algebra

SQL er overflaten — algebraen er fundamentet

Når du skriver en SQL-spørring, parser DBMS-en den til et relasjons­algebra-tre. Optimalisatoren manipulerer dette treet: bytter rekkefølge på operatorer, presser σ ned mot tabellene, velger join-algoritmer. Skal du forstå hvorfor en spørring er treg eller rask, må du forstå algebraen.

Algebraen har fire egenskaper som gjør den nyttig:

  • Lukket — input og output er begge relasjoner. Operasjoner kan komponeres som funksjoner: πnamedept='Physics'(instructor)).
  • Funksjonell — ingen sideeffekter. Samme input gir samme output.
  • Algebraiske loverσp(R ⋈ S) = σp(R) ⋈ S hvis p bare nevner attributter i R. Slike lover er det optimalisatoren utnytter.
  • Komplett nok — alle SQL-spørringer (utenom aggregat og null-håndtering) kan oversettes til en relasjons­algebra-uttrykk.
Eksempel-database (samme som læreboka)

Vi bruker tabellene instructor, department, course, teaches gjennom hele siden. Skjema vises i sidebar­diagrammet på 2A · Fremmednøkler.

02 · Operator-oversikt

Seks fundamentale, tre avledede

De seks fundamentale operasjonene utgjør et fullt språk. De resterende kan defineres ved hjelp av disse — men brukes så ofte at de regnes som førsteklasses.

Fundamentale

σ
Select
Filtrer rader som oppfyller predikat p.
π
Project
Velg ut kolonner. Duplikater fjernes.
×
Kartesisk
Alle par av tupler — bygger bredere rader.
Union
Tupler i R eller S (kompatibelt skjema).
Differanse
Tupler i R som ikke er i S.
ρ
Rename
Gi relasjon eller attributter nye navn.

Avledede (utledes fra de fundamentale)

Join
σ-filter + ×. Theta, equi, natural.
Intersect
Tupler som finnes i begge. R ∩ S = R − (R − S).
÷
Division
«For alle …»-spørringer.

Hver operator har et input-skjema, et output-skjema og en presis semantikk. Vi går gjennom dem en for en med samme lille datasett, og avslutter med en interaktiv «bygg en spørring»-runner.

03 · σ Select

Filtrer rader: σp(R)

Notasjon: σp(R) — beholder de tuplene t ∈ R der predikatet p(t) er TRUE.

Predikat kan bygges av sammenligninger (=, ≠, <, ≤, >, ≥), konnektiver (∧, ∨, ¬), og kan referere til to attributter (σdept_name = building(department)) — ikke bare attributt mot konstant.

Output-skjema: uendret — Select fjerner aldri kolonner.

Eksempel — instruktører i Physics

Spørring: σdept_name = "Physics"(instructor)

instructor (input)
IDnamedept_namesalary
10101SrinivasanComp. Sci.65 000
22222EinsteinPhysics95 000
33456GoldPhysics87 000
76766CrickBiology72 000
83821BrandtComp. Sci.92 000
resultat
IDnamedept_namesalary
22222EinsteinPhysics95 000
33456GoldPhysics87 000

Sammensatt predikat: σdept_name = "Physics" ∧ salary > 90000(instructor) gir bare Einstein.

SQL-mapping

σp(R) blir SELECT * FROM R WHERE p.

Skriv σ-uttrykket · Lett
Skriv et relasjons­algebra-uttrykk som finner alle instruktører som tjener minst 80 000 OG ikke jobber i Comp. Sci.
σsalary ≥ 80000 ∧ dept_name ≠ "Comp. Sci."(instructor). Resultatet på datasettet over: bare Einstein og Gold.
04 · π Project

Velg ut kolonner: πL(R)

Notasjon: πA1, A2, …(R) — beholder bare de listede attributtene.

Output-skjema: bare attributtene i L.

Duplikater: fjernes — fordi resultatet er en mengde. Dette er en av de viktigste kildene til ulikhet mellom relasjons­algebra og SQL (bag-semantikk).

Eksempel — bare ID, navn og lønn

Spørring: πID, name, salary(instructor)

π fjerner dept_name-kolonnen
IDnamedept_namesalary
10101SrinivasanComp. Sci.65 000
22222EinsteinPhysics95 000
33456GoldPhysics87 000
Visuelt fjerner π en kolonne. I praksis lages det en ny relasjon med færre attributter.

Når dukker duplikater opp?

Tabellen instructor har flere rader i samme avdeling. πdept_name(instructor) gir én rad per avdeling — ikke én per instruktør:

dept_name
Comp. Sci.
Physics
Biology
5 input-rader → 3 output-rader. Duplikater forsvinner.
SQL-mapping

πL(R) tilsvarer SELECT DISTINCT L FROM R. Uten DISTINCT beholder SQL duplikater — det er bag-semantikken.

Refleksjon · Middels
πdept_name(instructor) har 3 rader, men SQL-en SELECT dept_name FROM instructor kan returnere 5. Hvorfor?
Ren relasjons­algebra opererer på mengder — duplikater fjernes implisitt. SQL bruker multimengder (bags) — duplikater bevares med mindre du ber eksplisitt om DISTINCT. Dette er ytelsesvalg: dedupe koster en sortering eller hash, og er ikke alltid det brukeren ville hatt.
05 · × Kartesisk produkt

Sett alt mot alt: R × S

Notasjon: R × S — alle kombinasjoner av tupler. Hvis |R| = m og |S| = n, har resultatet m · n tupler.

Output-skjema: sammenstilling av begge skjemaene. Hvis et attributtnavn finnes i begge, prefikses det med relasjonsnavn (instructor.ID, teaches.ID).

Mini-eksempel

La A = {1, 2} og B = {x, y} (én attributt hver). Da er:

A × B
A.valB.val
1x
1y
2x
2y

Kartesisk produkt alene gir nesten aldri det du vil — det blir veldig stort, og kobler ting som ikke har med hverandre å gjøre. Men kombinert med σ får vi join:

σinstructor.ID = teaches.ID(instructor × teaches)
   ≡   instructor ⋈instructor.ID = teaches.ID teaches

Algebraen utleder altså join fra to mer fundamentale operasjoner.

06 · ⋈ Join

Sett tabeller sammen langs en betingelse

Det finnes flere join-varianter. De skiller seg bare i hvilket predikat som brukes — alle bygger ovenpå × og σ.

Theta-join ⋈θ Equijoin Natural join ⋈ Outer joins ⟕ ⟖ ⟗

Theta-join

R ⋈θ S = σθ(R × S). θ er et vilkårlig predikat — kan inneholde <, >, ulikhet, sammensatte uttrykk.

Equijoin

Theta-join der predikatet kun bruker likhet: R ⋈R.A = S.B S. Den vanligste varianten.

Natural join

Notasjon: R ⋈ S (uten subskript). Match på alle attributter med samme navn, og slå sammen til én kolonne i resultatet.

For instructor(ID, name, dept_name, salary) og department(dept_name, building, budget) er det bare dept_name som er felles. Da:

instructor ⋈ department = σinstructor.dept_name = department.dept_name(instructor × department) — men med én dept_name-kolonne i resultatet, ikke to.

instructor ⋈ department
IDnamedept_namesalarybuildingbudget
10101SrinivasanComp. Sci.65 000Taylor100 000
22222EinsteinPhysics95 000Watson70 000
33456GoldPhysics87 000Watson70 000
76766CrickBiology72 000Watson90 000
Pass på når skjemaet endres

Natural join er sårbar for skjemaendringer: legges et nytt likt-navngitt attributt til (f.eks. created_at i begge tabellene), endres semantikken stille. I produksjonskode er JOIN ON (eksplisitt) ofte tryggere enn NATURAL JOIN.

Outer joins — ikke kast bort «taperne»

I en vanlig (inner) join faller tupler uten match bort. Outer join bevarer dem ved å fylle inn NULL for de manglende attributtene.

  • Left outer ⟕ — bevarer alle rader i venstre tabell.
  • Right outer ⟖ — bevarer alle rader i høyre tabell.
  • Full outer ⟗ — bevarer alle på begge sider.

Eksempel: instructor ⟕ teaches beholder også instruktører som ikke underviser noen kurs:

instructor ⟕ teaches (LEFT OUTER) — utdrag
IDnamedept_namecourse_idsemester
10101SrinivasanComp. Sci.CS-101Fall 2017
22222EinsteinPhysicsPHY-101Fall 2017
33456GoldPhysicsNULLNULL
58583CalifieriHistoryNULLNULL
Gold og Califieri underviser ikke — i en inner join hadde de forsvunnet.
Forskjell · Middels
Hvilke instruktører forsvinner fra resultatet hvis du gjør instructor ⋈ teaches (inner) i stedet for instructor ⟕ teaches?
De som ikke har en match i teaches — altså instruktører som ikke underviser noen kurs (Gold, Califieri, Singh i lærebok-eksempelet). Inner join krever match på begge sider; left outer bevarer venstre uansett.
Lover · Vanskelig
Hvorfor er σp(R ⋈ S) ikke alltid lik p(R)) ⋈ S — selv når p bare nevner attributter i R?
For inner join gjelder lover-utvidelsen: optimalisatoren bruker akkurat denne identiteten («push selection past join») når den kan. For outer join bryter den ned — left outer kan introdusere NULL-rader fra venstre side selv etter filteret, og predikatet på p over en NULL-rad blir UNKNOWN, som filtreres bort. Lærdommen: algebraiske lover gjelder bare for spesifikke operatorer, og optimalisatoren må vite om forskjellene.
07 · ∪ ∩ −

Mengde­operatorer — krever skjema-kompatibilitet

For at R ∪ S, R ∩ S eller R − S skal gi mening, må R og S være kompatible:

  • Samme antall attributter (samme aritet).
  • Samme type på den i-te attributten i begge.

Det er posisjon som teller, ikke navn — derfor må man ofte projisere først for å justere skjemaene.

R S R ∪ S
Union: alle tupler i enten R eller S (eller begge).
R S R ∩ S
Intersect: tupler i begge.
R S R − S
Differanse: tupler i R som ikke er i S.

Eksempel — kurs gitt i begge semestre

πcourse_idsemester="Fall" ∧ year=2017(section))
  ∩
πcourse_idsemester="Spring" ∧ year=2018(section))

Resultatet er kurser som ble undervist i begge perioder.

Avledning

Intersect kan utledes fra differanse: R ∩ S = R − (R − S). Det er derfor ∩ ikke regnes som fundamental — men i SQL er den tilgjengelig direkte (INTERSECT).

Refleksjon · Lett
Kan instructor ∪ student gi mening?
Bare hvis skjemaene er kompatible. instructor(ID, name, dept_name, salary) og student(ID, name, dept_name, tot_cred) har samme aritet (4), men fjerde attributt har ulik type (salary vs. tot_cred). Du må projisere bort de inkompatible kolonnene først — f.eks. πID, name(instructor) ∪ πID, name(student).
08 · ρ Rename

Gi navn til mellomresultater

Notasjon:

  • ρx(E) — gir resultatet av uttrykk E navnet x.
  • ρx(A1, A2, …)(E) — i tillegg gis attributtene nye navn.

Hvorfor trenger man dette? Fordi resultatet av en algebra-operasjon ikke har noe navn, og noen spørringer trenger å bruke samme relasjon to ganger.

Eksempel — instruktører som tjener mer enn instruktør 12121

πi.ID, i.namei.salary > w.salary(
   ρi(instructor) × σw.ID = 12121w(instructor))
))

Vi navngir to «kopier» av instructor som i og w, slik at vi kan referere til i.salary og w.salary uten tvetydighet.

SQL-mapping

I SQL gjøres det samme med tabell-aliaser: FROM instructor i, instructor w. ρ er det algebraiske motstykket.

09 · ÷ Division

«Alle som …»-spørringer

Notasjon: R ÷ S — gitt at S's skjema er en delmengde av R's.

Semantikk: Resultatet er de t-verdiene fra R (over attributter ikke i S) der t kombinert med hver rad i S finnes i R.

Klassisk eksempel: «Finn alle studenter som har tatt alle CS-kurs.»

Konkret eksempel

La tatt(student, kurs) og påkrevd(kurs). Vi vil ha studenter som har tatt hvert kurs i påkrevd.

tatt
studentkurs
AnnaCS-101
AnnaCS-102
AnnaMA-100
BoCS-101
BoCS-102
CaraCS-101
påkrevd
kurs
CS-101
CS-102
tatt ÷ påkrevd
student
Anna
Bo

Cara har tatt CS-101 men ikke CS-102, så hun mangler en av de påkrevde — fjernes. Anna har tatt begge (pluss MA-100, som ikke disqualifiserer henne). Bo har tatt akkurat de to.

Avledning fra fundamentale operatorer

R ÷ S = πR−S(R) − πR−S((πR−S(R) × S) − R)

Tankegangen: «Alle kandidater» minus «de kandidatene som mangler en kombinasjon». Den siste står igjen som «alle som ikke mangler noen» = «alle som har alle».

I SQL — det dobbelte NOT EXISTS-trikset

SELECT DISTINCT t.student
FROM tatt t
WHERE NOT EXISTS (
  SELECT * FROM påkrevd p
  WHERE NOT EXISTS (
    SELECT * FROM tatt u
    WHERE u.student = t.student AND u.kurs = p.kurs
  )
);

«Det finnes ikke noe påkrevd kurs som ikke finnes blant studentens egne tatt-rader.» Skummelt å lese, men direkte oversettelse av division.

Forståelse · Vanskelig
Hvis påkrevd er tom, hva returnerer tatt ÷ påkrevd?
Alle distinkte studenter — fordi predikatet «for alle ingen kurs i påkrevd, …» er vakuumt sant. Dette er den klassiske «tomme universelle kvantor»-felle. Sjekk derfor alltid om dataen din kan ha tomme delmengder hvis du bygger «for alle …»-spørringer.
10 · Komposisjon og ekvivalens

Bygg uttrykk og skriv dem om

Siden hver operator gir en relasjon ut, kan de stables. «Finn navnene på alle Physics-instruktører som underviser CS-101» kan skrives som:

πname(
  σdept_name="Physics"(instructor)
   ⋈instructor.ID = teaches.ID
  σcourse_id="CS-101"(teaches)
)

Ekvivalente uttrykk

Det finnes flere måter å skrive samme spørring på. Optimalisatoren bruker ekvivalens­regler for å finne en billigere form. To eksempler:

σdept="Physics"(instructor ⋈ teaches)
   ≡   (σdept="Physics"(instructor)) ⋈ teaches
πA,B(R ⋈ S)
   ≡   (πA, joinattr(R)) ⋈ (πB, joinattr(S))

Den første («push selection past join») reduserer størrelsen før den dyre joinen. Den andre («push projection past join») kutter unødige kolonner tidlig. Begge gjør joinen billigere uten å endre resultatet.

Hvorfor du bør lære algebraen

Når du leser EXPLAIN-output i Postgres eller MySQL, ser du algebra-operatorer (Filter, HashAggregate, Hash Join). Forstår du algebraen, forstår du planen.

11 · Bygg en spørring

Interaktiv runner

Velg en operator-knapp under og se hvilke rader/kolonner som blir igjen. Bygges trinn for trinn på samme datasett. Reset nullstiller.

Aktiv relasjon

Algebra-uttrykk så langt

Trykk på trinnknappene over.

12 · Oppsummering

Du bør nå kunne …

  • … liste de seks fundamentale operatorene og forklare semantikken til hver.
  • … oversette en SQL-spørring uten aggregat til relasjons­algebra og motsatt.
  • … skille natural join, theta-join og outer joins, og kjenne SQL-motstykkene.
  • … skrive en division-spørring og vite hvorfor den er triks å skrive i SQL.
  • … argumentere for hvorfor algebraen er nyttig: optimalisatoren manipulerer den.
Sjekk forståelsen din

Gå tilbake til kapittel-oversikten og ta «Test deg selv»-quizen som dekker hele kapittel 2.