Det här är ett avsnitt i en webbkurs om databaser som finns fritt tillgänglig på adressen http://www.databasteknik.se/webbkursen/. Senaste ändring: 18 juli 2005.

Av Thomas Padron-McCarthy. Copyright, alla rättigheter reserverade, osv. Skicka gärna kommentarer till webbkursen@databasteknik.se.

Introduktion till frågespråket SQL

Den här handledningen beskriver hur man använder frågespråket SQL. Ett frågespråk är ett språk som man använder för att ställa frågor till en databashanterare, dvs göra sökningar i en databas. Exemplen följer den vanliga SQL-standarden SQL-92, även kallad SQL2.

Det kan vara bra att läsa avsnittet om relationsmodellen först.

Varför ska jag lära mig det här?

SQL är det helt dominerande frågespråket idag. Om du någon gång ska arbeta med en databashanterare på ett mer avancerat sätt än att bara fylla i formulär, är chansen därför stor att att du kommer att använda SQL.

Förberedelser

Vi antar att du nu sitter framför en dator, och kan skriva SQL-kommandon. Hur man gör för att åstadkomma denna situation, och hur man gör för att skriva in och köra SQL-kommandona, varierar beror på vilken dator och vilken databashanterare det handlar om. Därför tar vi inte upp det här.

Exempeldatabas: Idrottsklubben

Vi antar också att du arbetar med en databas som beskriver en liten idrottsklubb. Databasen består av tre tabeller, som kommer att beskrivas nedan.

De första stapplande stegen

Ge följande kommando till databashanteraren:
select * from Medlem;
Kommandot, eller SQL-frågan, "select * from Medlem" betyder ungefär "välj ut alla kolumnerna ur tabellen Medlem". Semikolonet på slutet ingår egentligen inte i SQL-frågan, men behövs ibland för att markera slutet på frågan, eller för att skilja olika SQL-frågor åt.

När du kör denna SQL-fråga kommer du som svar att få en tabell som liknar den här:

Medlemsnummer Namn Telefon
2 Stina 282677
3 Saddam 260088
4 Lotta 174590
1 Olle 260088

Exakt hur du gör för att "köra frågan" beror på vilken databashanterare du använder. På vissa system kanske du har ett särskilt fönster där du kan skriva in och redigera frågan, varefter du kör den genom att klicka med musen på en knapp som det står Kör på. I andra system skriver du in frågan, avslutar den med ett semikolon (;), och sen när du trycker på returtangenten så körs frågan.

Det här är innehållet i tabellen Medlem i databasen. Den tabellen innehåller data om klubbens medlemmar. Varje rad beskriver en medlem. Som vi ser har tabellen tre kolumner, nämligen Medlemsnummer (medlemsnummer), Namn (medlemmens namn) och Telefon (medlemmens telefonnummer).

Faktaruta: Ibland kallar man tabellerna för relationer, raderna för tupler, och kolumnerna för attribut.

Varje rad (eller tupel) beskriver en sak av något slag, i det här fallet en medlem i idrottsklubben, och varje kolumn (eller attribut) beskriver en egenskap hos en sådan sak, exempelvis dess nummer eller namn.

Alla värden i samma kolumn (för samma attribut) har samma typ, och mängden av alla värden som kan finnas i kolumnen brukar vi kalla domän. Det motsvarar ungefär begreppet datatyp i programmeringsspråk.

Enkla frågor med SELECT

Med hjälp av SELECT kan vi mer detaljerat beskriva vad vi vill ha för svar. Om vi till exempel bara vill ha reda på namnen på klubbens medlemmar, alltså attributet (kolumnen) Namn i relationen (tabellen) Medlem, skriver vi:
select Namn from Medlem;

Namn
Stina
Saddam
Lotta
Olle
Med kommandot "select Namn from Medlem" menar vi helt enkelt "välj ut kolumnen Namn ur raderna i tabellen Medlem".

Vi kan välja ut mer än en kolumn om vi vill, till exempel namnet och telefonnumret (attributen Namn och Telefon):

select Namn, Telefon from Medlem;

Namn Telefon
Stina 282677
Saddam 260088
Lotta 174590
Olle 260088

Faktaruta: Om du kan någon relationsalgebra, så motsvarar det här operationen projektion ("project" på engelska) i relationsalgebran: en operation som väljer ut vissa av kolumnerna i en relation (dvs tabell).

Den första frågan vi ställde, select * from Medlem, skulle också kunna uttryckas så här:

select Medlemsnummer, Namn, Telefon from Medlem;
Vi har nu sett hur man kan använda select för att välja ut de av tabellens kolumner (relationens attribut) som man är intresserad av. Men vi kan också tala om vilka rader (tupler) vi vill ha med i svaret. Om vi vill ha information bara för den medlem som heter Lotta, skriver vi:
select * from Medlem
where Namn = 'Lotta';

Medlemsnummer Namn Telefon
4 Lotta 174590
Med ordet where kunde vi alltså ange ett "sökvillkor", som talade om vilka tupler vi var intresserade av.

Faktaruta: Om du kan någon relationsalgebra, så motsvarar det här operationen selektion ("select" på engelska) i relationsalgebran: en operation som väljer ut de rader i en relation (dvs tabell) som uppfyller ett visst villkor. Relationsalgebrans select har inget att göra med den select som vi har i SQL, som bara betyder ungefär att "här börjar en SQL-fråga".

Notera att i SQL använder vi enkla citationstecken (') för att avgränsa strängar, inte dubbla ("). Det brukar funka med dubbla citationstecken också, men egentligen ska det vara enkla.

Vi skulle kunna omformulera frågan så här:

select * from Medlem
where Medlem.Namn = 'Lotta';
Svaret blir:
Medlemsnummer Namn Telefon
4 Lotta 174590
Medlem.Namn betyder här "kolumnen Namn i tabellen Medlem", men eftersom det än så länge inte kan uppstå några missförstånd, räcker det med att skriva Namn, utan någon tabellangivelse.

Vi kan också göra strängmatchning med jokertecken (eller wildcards som det heter på engelska):

select Namn, Telefon from Medlem
where Namn like 'S%';

Namn Telefon
Stina 282677
Saddam 260088
Om man använder ordet "like" i jämförelsen, så innebär det strängmatchning med jokertecken. Understreck (_) betyder ett tecken, vilket som helst, medan procenttecken (%) betyder vilka tecken som helst, "=" funkar som exakt match:
select Namn, Telefon from Medlem
where Namn = 'S%';

Namn Telefon
Man kan bygga upp mer komplicerade where-villkor med hjälp av AND och OR, på samma sätt som man brukar kunna göra i programmeringsspråk:
select * from Medlem
where Namn like 'S%' and Medlemsnummer <= 2
or Telefon = '174590';

Medlemsnummer Namn Telefon
2 Stina 282677
4 Lotta 174590
Uttrycken kan grupperas med parenteser:
select * from Medlem
where Namn like 'S%' and (Medlemsnummer <= 2
or Telefon = '174590');

Medlemsnummer Namn Telefon
2 Stina 282677

Om man skriver fel

Om man skriver fel i sina SQL-frågor, så får man oftast ett felmeddelande. Olika databashanterare har olika felmeddelanden, men de kan se ut ungefär så här:
select * from Medlem
where Förnamn = 'Lotta';
[interbase.interclient]
Dynamic SQL Error
SQL error code = -206
Column unknown
FÖRNAMN
select * from Medlemm
where Namn = 'Lotta';
[interbase.interclient]
Dynamic SQL Error
SQL error code = -204
Table unknown
MEDLEMM

Frågor med flera tabeller

Vi har en tabell till i databasen, nämligen tabellen Sektion som beskriver idrottsklubbens olika sektioner:
select * from Sektion;

Sektionskod Namn Ledare
A Bowling 4
C Simning 2
B Kickboxing 4
Varje rad i tabellen Sektion innehåller data om en av klubbens sektioner. Tabellen har tre kolumner, nämligen Sektionskod (sektionskod), Namn (sektionens namn) och Ledare (ledare).

Ledare anger medlemsnumret på den medlem som är ledare för sektionen. Vi ser till exempel att medlem nummer 4 leder bowlingsektionen, och vi kan sen gå till medlemstabellen för att ta reda på vem medlem nummer 4 är. Medlemstabellen såg ju ut så här:

Medlemsnummer Namn Telefon
2 Stina 282677
3 Saddam 260088
4 Lotta 174590
1 Olle 260088
Vi ser alltså att det är Lotta som leder bowlingsektionen.

Faktaruta: Ett attribut (dvs en kolumn) som på det viset refererar till en annan tabell kallas referensattribut. Den kan också kallas främmande nyckel (foreign key på engelska) eftersom den refererar till en nyckel i en annan tabell.

Tabellerna i exemplet är så små att vi direkt ser att ledaren för bowlingsektionen är Lotta. Men om det är stora tabeller vill man förstås använda SQL-frågor för att få fram den informationen. Vi ska nu försöka oss på att göra detta.

Vi börjar med det mest uppenbara (men inte särskilt bra) sättet.

Först tar vi fram numret på bowlingsektionens ledare:

select Ledare from Sektion
where Namn = 'Bowling';

Ledare
4
Och sen tittar vi helt enkelt efter i medlemstabellen vem nummer 4 är:
select Namn from Medlem
where Medlemsnummer = 4;

Namn
Lotta
Det är dock ganska onödigt att ställa två separata frågor. I andra fall kan det dessutom bli väldigt krångligt.

I den andra frågan, select Namn from Medlem where Medlemsnummer = 4, var ju 4:an resultatet av den första frågan, select Ledare from Sektion where Namn = 'Bowling'. Därför stoppar vi in denna första fråga i den andra frågan, i stället för 4:an!

select Namn from Medlem
where Medlemsnummer = (select Ledare from Sektion
where Namn = 'Bowling');

Namn
Lotta
Notera att vi behövde skriva parenteser runt den andra, inre frågan.

Ett annat sätt att kombinera tabeller

Nu ska vi göra samma sak utan någon sub-select i where-villkoret.

Prova först att skriva båda tabellerna Sektion och Medlem efter from i select-frågan:

select * from Sektion, Medlem;

Sektionskod Namn Ledare Medlemsnummer Namn Telefon
A Bowling 4 2 Stina 282677
C Simning 2 2 Stina 282677
B Kickboxing 4 2 Stina 282677
A Bowling 4 3 Saddam 260088
C Simning 2 3 Saddam 260088
B Kickboxing 4 3 Saddam 260088
A Bowling 4 4 Lotta 174590
C Simning 2 4 Lotta 174590
B Kickboxing 4 4 Lotta 174590
A Bowling 4 1 Olle 260088
C Simning 2 1 Olle 260088
B Kickboxing 4 1 Olle 260088
Resultatet blir den så kallade kartesiska produkten av de två tabellerna, dvs alla kombinationer av rader. Låt oss nu välja ut bara de rader där ledarnumret Ledare är samma som medlemsnumret Medlemsnummer:
select * from Sektion, Medlem
where Ledare = Medlemsnummer;

Sektionskod Namn Ledare Medlemsnummer Namn Telefon
C Simning 2 2 Stina 282677
A Bowling 4 4 Lotta 174590
B Kickboxing 4 4 Lotta 174590
Vi har plötsligt fått en fin liten tabell där varje rad innehåller information om en sektion och dess ledare.

Vi var ju egentligen bara intresserade av bowlingsektionens ledare, så vi lägger in det också i where-villkoret:

select * from Sektion, Medlem
where Ledare = Medlemsnummer
and Sektion.Namn = 'Bowling';

Sektionskod Namn Ledare Medlemsnummer Namn Telefon
A Bowling 4 4 Lotta 174590
Och så gör vi en sista ändring, för att få fram bara namnet på ledaren:
select Medlem.Namn from Sektion, Medlem
where Ledare = Medlemsnummer
and Sektion.Namn = 'Bowling';

Namn
Lotta

Vad sportar Olle?

Tabellen Deltar innehåller data om vilka medlemmar som deltar i de olika sektionernas verksamhet:
select * from Deltar;

Medlem Sektion
1 A
1 B
1 C
2 C
3 A
Av den första raden i tabellen ser vi att medlem nummer 1 deltar i sektionen med sektionskoden A. På nästa rad står det att (samma) medlem nummer 1 också deltar i sektionen med sektionskoden B. Och så vidare.

Nu vill vi använda alla tre tabellerna i databasen (Medlem, Sektion och Deltar) för att svara på frågan Vilka sporter ägnar sig Olle åt?

Först gör vi på det dumma sättet, och börjar med att ta reda på Olles medlemsnummer:

select Medlemsnummer
from Medlem
where Namn = 'Olle';

Medlemsnummer
1
Sen tittar vi i tabellen Deltar för att få reda på vilka av sektionerna som Olle deltar i.
select Sektion from Deltar
where Medlem = 1;

Sektion
A
B
C
Ok, så vad är 'A', 'B' och 'C' för nåt?
select Namn from Sektion
where Sektionskod = 'A'
or Sektionskod = 'B'
or Sektionskod = 'C';

Namn
Bowling
Simning
Kickboxing
Vi kan skriva det lite enklare med "in":
select Namn from Sektion
where Sektionskod in ('A', 'B', 'C');

Namn
Bowling
Simning
Kickboxing
Det var alltså det dumma sättet. Nu ska vi i stället skriva ihop de tre frågorna till en enda fråga.

Först stoppar vi in en sub-select för att slippa det hårdkodade ('A', 'B', 'C'):

select Namn from Sektion
where Sektionskod = (select Sektion from Deltar
where Medlem = 1);
[interbase.interclient]
multiple rows in singleton select
Oops! Vi måste använda "in" i stället för "=". Den vanliga lika-med-jämförelsen med "=" kan nämligen bara jämföra med ett värde, och här är det ju tre (eftersom Olle deltar i tre sektioner).
select Namn from Sektion
where Sektionskod in (select Sektion from Deltar
where Medlem = 1);

Namn
Bowling
Simning
Kickboxing
Och så stoppar vi in den första frågan, select Medlemsnummer from Medlem where Namn = 'Olle', i stället för 1:an.
select Namn from Sektion
where Sektionskod in (select Sektion from Deltar
where Medlem = (select Medlemsnummer from Medlem
where Namn = 'Olle'));

Namn
Bowling
Simning
Kickboxing
Gör samma fråga men utan nån jämrans sub-select:
select Namn from Sektion, Deltar, Medlem
where Sektionskod = Sektion
and Medlem = Medlemsnummer
and Namn = 'Olle';

Namn
Olle
Olle
Olle
Öh, vanurå? Jo, den dumma databashanterare vi använde i exemplet borde nog ha varnat oss för att det finns två kolumner som heter "Namn" i de tabeller som frågan handlar om, nämligen Namn i Sektion.Namn och Medlem.Namn. I stället valde den en av dem, och det råkade bli Medlem.Namn!

Vi skriver om frågan så det blir rätt:

select Sektion.Namn
from Sektion, Deltar, Medlem
where Sektionskod = Sektion
and Medlem = Medlemsnummer
and Medlem.Namn = 'Olle';

Namn
Bowling
Kickboxing
Simning

in och not in

Vilka deltar i någon (= minst en) sektion?
select * from Medlem
where Medlemsnummer in (select Medlem from Deltar);

Medlemsnummer Namn Telefon
2 Stina 282677
3 Saddam 260088
1 Olle 260088
Frågan kan också, som vi sett ovan, skrivas om utan någon select-sats i where-villkoret:
select distinct Medlem.Medlemsnummer, Medlem.Namn, Medlem.Telefon
from Medlem, Deltar
where Medlem.Medlemsnummer = Deltar.Medlem;
Distinct behövs eftersom medlemmar som deltar i flera sektioner annars kommer med flera gånger i svaret. I den teoretiska relationsmodellen finns inga dubletter, men SQL behåller dem för det mesta. Orsaken är dels att det går fortare att ta fram svaret om databashanteraren slipper sortera bort dubletter, men också att användaren ofta är intresserad av dubletterna också.

Om vi vill veta vilka som inte deltar i någon sektion, stoppar vi bara in ett not:

select * from Medlem
where Medlemsnummer not in (select Medlem from Deltar);

Medlemsnummer Namn Telefon
4 Lotta 174590
En fråga som innehåller "... in (select ..." i where-villkoret kunde ju skrivas om med en vanlig likhet. Men "... not in (select ..." kan inte lika enkelt skrivas om. Frågan ovan kan man alltså inte skriva om så här:
select distinct Medlem.Medlemsnummer, Medlem.Namn, Medlem.Telefon
from Medlem, Deltar
where Medlem.Medlemsnummer != Deltar.Medlem;
Den frågan tar inte fram vilka som inte sportar, utan den tar fram alla medlemmar som inte är helt ensamma om att sporta! (Tänk så här: Varje medlem som går att para ihop med en rad i deltar-tabellen som inte handlar om henne själv.)

Aggregatfunktioner

En aggregatfunktion arbetar på en hel kolumn, till exempel för att summera alla värdena, eller räkna ut medelvärdet.

Här kommer några enkla aggregatfunktioner. Vad är det högsta medlemsnumret?

select max(Medlemsnummer) from Medlem;

MAX
4
Och vem är det som har det högsta medlemsnumret?
select * from Medlem
where Medlemsnummer = max(Medlemsnummer);
[interbase.interclient]
Dynamic SQL Error
SQL error code = -104
Invalid aggregate reference
Nej, man kan inte ha aggregatfunktioner i where-villkoret. Gör en sub-fråga:
select * from Medlem
where Medlemsnummer = (select max(Medlemsnummer) from Medlem);

Medlemsnummer Namn Telefon
4 Lotta 174590
Man kan också kombinera aggregatfunktioner med ett where-villkor, vilket gör att aggregatfunkionen bara beräknas för de rader som uppfyller where-villkoret. För att få fram det högsta medlemsnumret för medlemmar med namn som börjar på S:
select max(Medlemsnummer) from Medlem
where Namn like 'S%';

MAX
3

Vyer

En vy i SQL är en tabell som inte lagras i databasen, utan vars innehåll räknas ut på nytt varje gång man tittar på den. (Det kan fungera annorlunda internt i databashanteraren, men det ser alltid ut som att vyns innehåll räknas ut på nytt.)

Nu ska vi testa vyer. Visa sektionsnamn och namnet på ledaren för den sektionen:

select Sektion.Namn, Medlem.Namn
from Sektion, Medlem
where Ledare = Medlemsnummer;

Namn Namn
Simning Stina
Bowling Lotta
Kickboxing Lotta
Vi gör en vy av det:
create view Ledarskap
as select Sektion.Namn, Medlem.Namn
from Sektion, Medlem
where Ledare = Medlemsnummer;
[interbase.interclient]
unsuccessful metadata update
STORE RDB$RELATION_FIELDS failed
attempt to store duplicate value (visible to active transactions)
in unique index "RDB$INDEX_15"
Öh, gick ju inte. I svaret på en fråga, som bara ska skrivas ut, kan man tillåta två kolumner med samma namn, men inte i en vy eller tabell. Det fungerar bättre om vi ger namn till kolumnerna i vyn, till exempel så här:
create view Ledarskap (Sektionsnamn, Medlemsnamn)
as select Sektion.Namn, Medlem.Namn
from Sektion, Medlem
where Ledare = Medlemsnummer;
Ok
Nu funkar vyn som vilken tabell som helst.
select * from Ledarskap;

Sektionsnamn Medlemsnamn
Simning Stina
Bowling Lotta
Kickboxing Lotta
select Medlemsnamn
from Ledarskap
where Sektionsnamn = 'Bowling';

Medlemsnamn
Lotta

Att ändra på databasens innehåll

Kan vi lägga in rader i databasen på nåt sätt?
select * from Medlem;

Medlemsnummer Namn Telefon
2 Stina 282677
3 Saddam 260088
4 Lotta 174590
1 Olle 260088
insert into Medlem
values (4, 'Isaac', 281000);
[interbase.interclient]
violation of PRIMARY or UNIQUE KEY constraint "INTEG_16" on table "Medlem"
Aha! Databasen kollar att vi inte har dubletter på primärnyckeln: Så här skrev vi när vi skapade tabellen:
create table Medlem
(Medlemsnummer integer not null,
Namn varchar(6),
Telefon varchar(10),
primary key (Medlemsnummer));
Gör rätt, dvs använd ett nummer som är unikt:
insert into Medlem
values (7, 'Isaac', 281000);
Ok (1 row affected)
select * from Medlem;

Medlemsnummer Namn Telefon
2 Stina 282677
3 Saddam 260088
4 Lotta 174590
1 Olle 260088
7 Isaac 281000
Om det fattas ett värde, till exempel om vi vill lägga in medlemmen Nelson som inte har någon telefon?
insert into Medlem values (8, 'Nelson');
[interbase.interclient]
Dynamic SQL Error
SQL error code = -804
Count of columns does not equal count of values
I det läget måste man lista namnen på kolumnerna!
insert into Medlem (Medlemsnummer, Namn)
values (8, 'Nelson');
Ok (1 row affected)
select * from Medlem;

Medlemsnummer Namn Telefon
2 Stina 282677
3 Saddam 260088
4 Lotta 174590
1 Olle 260088
7 Isaac 281000
8 Nelson  
Nelsons telefon fick värdet null. Nelson har alltså inte något telefonnummer lagrat i tabellen, utan rutan är tom. (Det kan betyda att Nelson inte har någon telefon, eller också att vi bara inte vet numret.)
select * from Medlem where Telefon is null;

Medlemsnummer Namn Telefon
8 Nelson  
Notera att man måste använda "is null". Null är inte samma sak som noll eller en tom sträng:
select * from Medlem where Telefon = '';

Medlemsnummer Namn Telefon
Nu känner vi plötsligt för att ge kommandot commit:
commit;
Transaction committed
I och med att vi gjort commit, är ändringarna "sparade" i databasen. De kommer inte att försvinna om datorn kraschar eller om strömmen går, och i ett fleranvändarsystem blir de nu synliga för andra användare.

Kan vi ta bort rader också? Testa att ta bort en medlem:

delete from Medlem where Namn = 'Isaac';
Ok (1 row affected)
select * from Medlem;

Medlemsnummer Namn Telefon
2 Stina 282677
3 Saddam 260088
4 Lotta 174590
1 Olle 260088
8 Nelson  
Nu testar vi att ge kommandot rollback. Vid en rollback försvinner alla ändringar sen senaste commit, eller sen sessionens början om vi aldrig "commitat". Och plötsligt finns Isaac där igen:
rollback;
Transaction aborted
select * from Medlem;

Medlemsnummer Namn Telefon
2 Stina 282677
3 Saddam 260088
4 Lotta 174590
1 Olle 260088
7 Isaac 281000
8 Nelson  
Du kan läsa mer om commit och rollback i avsnittet om transaktioner.

Testa nu att ta bort Lotta:

delete from Medlem where Namn = 'Lotta';
[interbase.interclient]
violation of FOREIGN KEY constraint "INTEG_36" on table "Sektion"
Gick inte. Lotta refereras ju till (som ledare) från Sektion-tabellen. Databashanteraren upprätthåller referensintegritet för kopplingar mellan tabellerna med referensattribut. Referensintegritet betyder att om det nu står på en rad i sektionstabellen att medlem nummer 4 leder sektionen, så ska det banne mig också finnas en medlem nummer 4 i medlemstabellen.

En gång har vi skrivit så här för att tala om för databashanteraren att Ledare i Sektion är ett referensattribut:

alter table Sektion add foreign key (Ledare) references Medlem (Medlemsnummer);

Du kan läsa mer om referensintegritet och andra liknande kontroller i avsnittet om integritetsvillkor.

Till sist ska vi också se att vi kan ändra rader med kommandot update:

update Sektion
set Namn = 'Skydiving'
where Namn = 'Bowling';
Ok (1 row affected)
select * from Sektion;

Sektionskod Namn Ledare
A Skydiving 4
C Simning 2
B Kickboxing 4

En sorts fotnot: Så här skapade vi vår klubb-databas

create table Medlem
(Medlemsnummer integer not null,
Namn varchar(6),
Telefon varchar(10),
primary key (Medlemsnummer));

create table Sektion
(Sektionskod character(1) not null,
Namn varchar(10),
Ledare integer,
primary key (Sektionskod));

create table Deltar
(Medlem integer not null,
Sektion character(1) not null,
primary key (Medlem, Sektion));

alter table Sektion
add foreign key (Ledare) references Medlem (Medlemsnummer);
alter table Deltar
add foreign key (Medlem) references Medlem (Medlemsnummer);
alter table Deltar
add foreign key (Sektion) references Sektion (Sektionskod);

Så här la vi in data i klubb-databasen

insert into Medlem (Medlemsnummer, Namn, Telefon)
values (2, 'Stina', '282677');
insert into Medlem (Medlemsnummer, Namn, Telefon)
values (3, 'Saddam', '260088');
insert into Medlem (Medlemsnummer, Namn, Telefon)
values (4, 'Lotta', '174590');
insert into Medlem (Medlemsnummer, Namn, Telefon)
values (1, 'Olle', '260088');

insert into Sektion (Sektionskod, Namn, Ledare)
values ('A', 'Bowling', 4);
insert into Sektion (Sektionskod, Namn, Ledare)
values ('C', 'Simning', 2);
insert into Sektion (Sektionskod, Namn, Ledare)
values ('B', 'Kickboxing', 4);

insert into Deltar (Medlem, Sektion)
values (1, 'A');
insert into Deltar (Medlem, Sektion)
values (1, 'B');
insert into Deltar (Medlem, Sektion)
values (1, 'C');
insert into Deltar (Medlem, Sektion)
values (2, 'C');
insert into Deltar (Medlem, Sektion)
values (3, 'A');

Exempel 2: Anställda och avdelningar

Först av allt: Super-enkla SQL-frågor för att titta på tabellerna:
select * from Anställd;

Nummer Namn Telefon Lön Chef Avdelning
2 Stina 2677 30000 0 H
3 Saddam 1088 22000 2 S
4 Lotta 4590 28000 2 H
1 Olle 2688 20000 3 S
8 Maria 2690 25000 4 C
9 Ulrik 2698 26000 8 C
10 Petter 2645 22000 8 C
select * from Avdelning;

Avdelningskod Namn Ansvarig
H Högkvarteret 2
S Säkerhet 3
C Data 8
En enkel fråga: Visa vem som är chef för varje avdelning:
select Avdelning.Namn, Anställd.Namn
from Avdelning, Anställd
where Avdelning.Ansvarig = Anställd.Nummer;

Namn Namn
Högkvarteret Stina
Säkerhet Saddam
Data Maria
Aggregatfunktioner: Genomsnittslönen för alla anställda
select avg(Lön)
from Anställd;

AVG
24714
Genomsnittslönen, uppdelat beroende på vem man har som chef (använd "group by"):
select Chef, avg(Lön)
from Anställd
group by Chef;

Chef AVG
2 25000
3 20000
4 25000
8 24000
0 30000
Genomsnittslönen, uppdelat på de olika avdelningarna
select Avdelning, avg(Lön)
from Anställd
group by Avdelning;

Avdelning AVG
C 24333
H 29000
S 21000
Använd "where" för att begränsa vilka rader i tabellen som tas med när aggregatfunktionen beräknas:
select Avdelning, avg(Lön)
from Anställd
where Lön > 20000
group by Avdelning;

Avdelning AVG
C 24333
H 29000
S 22000
Använd "having" för att begränsa vilka grupper (= rader i resultatet) som tas med:
select Avdelning, avg(Lön)
from Anställd
group by Avdelning
having avg(Lön) > 22000;

Avdelning AVG
C 24333
H 29000
Man kan förstås kombinera "where" och "having":
select Avdelning, avg(Lön)
from Anställd
where Lön > 20000
group by Avdelning
having avg(Lön) > 22000;

Avdelning AVG
C 24333
H 29000

Att koppla ihop en tabell med sig själv

Vem tjänar mer än sin chef? Uppenbarligen ska vi på något sätt koppla ihop rader i tabellen Anställd med andra rader i samma tabell. Första försöket:
select Namn, Lön, Namn, Lön
from Anställd, Anställd
where Chef = Nummer
and Lön > Lön;

Namn Lön Namn Lön
Helfiasko. Vi har inte talat om vad som är vad "i de två Anställd-tabellerna", och databashanteraren ger oss inte det svar vi hoppades på.

Om det hade varit två tabeller, en med de vanliga anställda (till exempel kallad proletär) och en med cheferna (till exempel kallad Chef), hade det varit enkelt att ställa frågan:

select proletär.Namn, proletär.Lön, Chef.Namn, Chef.Lön
from proletär, Chef
where proletär.Chef = Chef.Nummer
and proletär.Lön > Chef.Lön;
Genom att införa två "alias" för samma tabell blir det precis som att jobba med två tabeller:
select proletär.Namn, proletär.Lön, Chef.Namn, Chef.Lön
from Anställd as proletär, Anställd as Chef
where proletär.Chef = Chef.Nummer
and proletär.Lön > Chef.Lön;

Namn Lön Namn Lön
Ulrik 26000 Maria 25000

En sorts fotnot: Så här skapade vi vår Anställd-databas

create table Anställd
(Nummer integer not null,
Namn varchar(6),
Telefon varchar(10),
Lön integer,
Chef integer,
Avdelning character(1),
primary key (Nummer));

create table Avdelning
(Avdelningskod character(1) not null,
Namn varchar(12),
Ansvarig integer,
primary key (Avdelningskod));

alter table Anställd add constraint Anställd_till_Chef
foreign key (Chef) references Anställd (Nummer);
alter table Anställd add constraint Anställd_till_Avdelning
foreign key (Avdelning) references Avdelning (Avdelningskod);
alter table Avdelning add constraint Avdelning_till_Ansvarig
foreign key (Ansvarig) references Anställd (Nummer);

Så här la vi in data i Anställd-databasen

insert into Anställd (Nummer, Namn, Telefon, Lön, Avdelning)
values (2, 'Stina', '2677', 30000, 'H');
insert into Anställd (Nummer, Namn, Telefon, Lön, Chef, Avdelning)
values (3, 'Saddam', '1088', 22000, 2, 'S');
insert into Anställd (Nummer, Namn, Telefon, Lön, Chef, Avdelning)
values (4, 'Lotta', '4590', 28000, 2, 'H');
insert into Anställd (Nummer, Namn, Telefon, Lön, Chef, Avdelning)
values (1, 'Olle', '2688', 20000, 3, 'S');
insert into Anställd (Nummer, Namn, Telefon, Lön, Chef, Avdelning)
values (8, 'Maria', '2690', 25000, 4, 'C');
insert into Anställd (Nummer, Namn, Telefon, Lön, Chef, Avdelning)
values (9, 'Ulrik', '2698', 26000, 8, 'C');
insert into Anställd (Nummer, Namn, Telefon, Lön, Chef, Avdelning)
values (10, 'Petter', '2645', 22000, 8, 'C');

insert into Avdelning (Avdelningskod, Namn, Ansvarig)
values ('H', 'Högkvarteret', 2);
insert into Avdelning (Avdelningskod, Namn, Ansvarig)
values ('S', 'Säkerhet', 3);
insert into Avdelning (Avdelningskod, Namn, Ansvarig)
values ('C', 'Data', 8);

Litteratur

De flesta grundläggande databasböcker innehåller en genomgång av SQL. En mer avancerad bok:

Webblänkar


Webbkursen om databaser av Thomas Padron-McCarthy.