SELECT i detaljer samt opsamlingsfunktioner

Tags:    databaser
<< < 123 > >>
Skrevet af Bruger #4522 @ 06.02.2008

SELECT


Hvis du absolut skal rage til dig: Alt om den mægtige *
Som du sikkert ved kan du vælge alle kolonner fra en tabel ved at bruge stjernesymbolet (*), som på både engelsk og dansk hedder asterisk (fra det latinske astrum; ja, man lærer noget nyt hver dag). Grundlæggende er asterisken blot en genvej for alle kolonner. Med udgangspunkt i Builders-tabellen er følgende to SELECT udsagn altså ens:

Fold kodeboks ind/udKode 


I vores eksempel med Builders-tabellen har vi kun to kolonner, men det er klart at hvis man har rigtig mange kolonner i en tabel er asterisken en god genvej og kan spare én for en masse tegn.

Hvis FROM bisætningen benævner to eller flere tabeller, kan det være nødvendigt at kvalificere asterisken med et tabelnavn - alt afhængig af hvilken information du ønsker at se i resultatmængden.

Hvis du f.eks. ønsker at se alle kolonnerne fra Builders-tabellen for de værfter som har bygget skibene i Ships-tabellen skal du bruge følgende SELECT:

Fold kodeboks ind/udKode 


Her har vi puttet Builders foran asterisken for at sørge for at vi kun får kolonnerne fra Builders-tabellen (og vi bruger DISTINCT for at sikre at hver værft kun opstår én gang i resultatmængde. Eksitensen af DISTINCT i SQL viser at SQL her faktisk bryder med matematikens koncept om en mængde. I matematikken kan en mængde ikke indeholde samme objekt/genstand/whatever flere gange - undtaget er multimængder, men det er noget andet. Visse databasepurister, heriblandt guruen C. J. Date [hvis skriverrier varmt kan anbefales] nævner dette som et af eksemplerne på problemerne med de nuværende implementationer af den relationelle databasemodel. Dette blev en meget lang bisætning gemt væk i en parantes så lad os fortsætte med det planlagte program).

Resultatet af ovenstående SELECT er:

Fold kodeboks ind/udKode 


Hvis vi nu ikke have specificeret Builders.*, men i stedet blot havde nøjes med asterisken, så havde resultatet været:

Fold kodeboks ind/udKode 


Som det ses får vi her alle kolonnerne fra de to tabeller der er benævnt i sætningens JOIN del (og vi bruger JOIN fordi vi kun ønsker Builder information fra de værfter som har bygget de skibe vi har i vores base, hvorfor værftet med BuilderId = 10 ikker er med da de ikke har bygget nogle af vores skibe).

SELECT uden brug af FROM
Nogle databaseprodukter, herunder SQL Server, tillader brugen af SELECT uden nogen FROM bisætning. Du vil nok kun gøre brug af dette meget sjælgdent, men det skal dog nævnes.

Du kan f.eks. anvende SQL Server som en simpel regnemaskine. Hvis du f.eks. smider dette i munden på SQL Server: SELECT 2+4, bliver resultatet:

Fold kodeboks ind/udKode 


Hvis du skal udregne din årlige fede programmørhyre så drop lommeregneren og brug SQL Server: SELECT 12*70000 giver:

Fold kodeboks ind/udKode 


De fleste database administratorer vil nok blive ret så knotten hvis alle firmaets ansatte sad og belemrede hans elskede SQL Server med sådanne regnestykker, men det er muligt. MySQL understøtter også disse "features".

Hvis du er i tvivl om dags dato kan SQL Server også her hjælpe: SELECT GETDATE() giver:

Fold kodeboks ind/udKode 


Det er lidet imponerende, og nok ikke noget du vil bruge så tit, men nu kender du til det.

Du kan regne med SELECT: Hvordan du bruger udtryk i SELECT
Du har netop set hvordan du kan få SQL Server (og MySQL) til at udregne simple regnestykker for dig ved at bruge regneudtryk i en SELECT uden en FROM bisætning. Brugen af sådanne regneudtryk bliver dog først interesserant når de bruges på noget data fra en tabel.

Hvis f.eks. vi ønsker at se en liste over vores skibe samt deres hastighed i både knob og km/t, kan vi bruge et udtryk i SELECT sætningen der omregner hastigheden i knob, som står i tabellens Speed-kolonne, til hastighed i km/t. Fra afsnittet om Warships databasen skrev jeg at en knob er 1,852 km/t og det kan vi bruge som omregningsfaktor. Følgende SELECT producerer det ønskede resultat:

Fold kodeboks ind/udKode 


Bemærk at jeg bruger kolonne alias så jeg kan forsyne resultatmængdens kolonner med nogle meningsfulde overskrifter. Resultatet bliver:

Fold kodeboks ind/udKode 


(Interessant skibsfakta: HMS betyder His Majesty's Ship eller Her Majesty's Ship - eller Submarine - og buges på den engelske flådes skibe. For de australske skibes vedkommende bruges Her Majesty's Australian Ship.)

Hvis du absolut skal være unik: Brugen af DISTINCT
Som vi så under afsnittet om den kraftfulde asterisk (*), kan DISTINCT bruges til fjerne gentagne rækker fra resultatmængden. Lad os kigge lidt nærmere på dette nøgleord.

DISTINCT virker på hele rækken, og ikke blot på det udtryk som det står foran. Tage følgende forespørgsel: SELECT Speed, Draught FROM Ships:

Fold kodeboks ind/udKode 


Bemærk her at rækken (13, 7.16) optræder to gange. Bemærk også at værdien 18 optræder to gange i den første kolonne, men at de komplette rækker er forskellige (18, 8.2) og (18, 7.9). Som sagt tager DISTINCT hele rækken i betragning når den fjerner nogle rækker, så den ene af (13, 7.16) vil blive fjernet, mens at de to rækker med 18 i første kolonne vil optræde i resultatmængden da der er tale om to forskellige rækker. SELECT DISTINCT Speed, Draught FROM Ships giver:

Fold kodeboks ind/udKode 


Med andre ord: DISTINCT virker på mængden (Speed, Draught), dvs. hele rækken, og ikke blot på (Speed). DISTINCT kan derfor kun optræde én gang i en SELECT sætning hvorfor sætningen SELECT DISTINCT Speed, DISTINCT Draught FROM Ships er ulovlig.

Hvis din SELECT indeholder mindst én nøgle for hver tabel nævnt i FROM, er DISTINCT overflødig (men ikke ulovlig). Dette er logisk da inklusionen af en nøglekolonne netop sørger for at resultatet er distinkt, da en tabel med nøgler aldrig har gentagne rækker.

Derudover, hvis resultatet kun indeholder én (eller ingen) rækker har DISTINCT selvsagt ingen virkning.

ALL er unødvendigt
Vi har lige snakket om DISTINCT. DISTINCTs modsætning er ALL som kan optræde på samme måde i en SELECT som DISTINCT. ALL har klart nok den modsatte virkning til DISTINCT. ALL er med andre ord ikke nødvendig at angive eksplicit.

De to følgende SELECT sætninger er altså ens:

Fold kodeboks ind/udKode 


Alt er ikke som det synes: Hvornår to rækker er ens
Hvis du har fulgt med, ikke blot i denne artikel, men i hele din SQL læretid, så har du måske undret dig over noget i forbindelse med DISTINCT.

Dengang du lærte om WHERE bisætningen (enten fra en anden artikel, bog, forelæsning etc.), lærte du helt sikkert om NULL. NULL er ikke en værdi som ande kolonneværdier såsom 4, 'Peter Larsen', 0.05 osv. NULL er en angivelse af fraværet af data. NULL er ikke en værdi, og kan derfor ikke sammenlignes med andre værder. NULL kan heller ikke sammenlignes med andre NULLs.

Tag for eksempel følgnede: NULL = 5. Dette kunne have stået i en WHERE bisætning:

SELECT EnEllerAndenKolonne
FROM EnEllerAndenTabel
WHERE NULL = 5;


Udover at være fjollet, er denne SELECT udefineret da NULL = 5 er udefineret. Du kan heller ikke sammenligne NULL med en kolonne, hvorfor

SELECT EnEllerAndenKolonne
FROM EnEllerAndenTabel
WHERE NULL = EnEllerAndenKolonne;


også er udefineret.

NULL er sig selv, kun sig selv og ikke andet. NULL kan derfor ikke sammenlignes med en kolonne eller anden værdi.

Såfremt man ønsker at undersøge for eksistensen af NULL skal konstruktet IS NULL anvendes:

SELECT EnEllerAndenKolonne
FROM EnEllerAndenTabel
WHERE EnEllerAndenKolonne IS NULL;


Dette burde betyder et problem for DISTINCT. DISTINCT fjerne gentagne rækker og gør brug af sammenligninger i processen. Men hvad med rækker der indheolder NULLs?

Tak f.eks. følgende rækker.

('Peter', NULL, 45);
('Peter', NULL, 45);
('Petersen', NULL, 47);


Af disse tre rækker er de to øverste ens uanset forekomsten af NULL. Men DISTINCT sammenligner jo netop rækkerne for at fjerne gentagne rækker. Og som netop forklaret kan man ikke sammenligne NULLs (og hvis man prøver får man et udefineret resultat)!

Så hvordan passer alt dette sammen? Den nemmeste måde at anskue det på (ideen kommer oprindeligt fra Rick van der Lans, se Referencer nedenfor), er at vi kan foretage både horisontale og vertikale sammenligninger.

Når vi bruger betingelser, f.eks. i en WHERE bisætning, foretages en horisontal sammenliging. I en sådanne sammenligning er NULL ikke lige med andre NULLs, og en sammenligning giver et udefineret resultat.

Sammenliginngen af de to rækker:

('Peter', NULL) = ('Peter', NULL)

giver et udefineret resultat. Bemærk at jeg har skrevet rækkerne på samme række, og her er altså tale om en horisontal sammenligning. Som nævnt er NULL her ikke sammenlignelig med andre NULLs. Resultatet er ikke en gang falsk, men altså udefineret da en sammenligning ikke er lovlig.

Horisontale sammenligninger er det vi finder i WHERE bisætninger.

Når DISTINCT udfører sit magi, er der tale om vertikale sammenligninger. Så en sammenligning af følgende to rækker:

('Peter', NULL, 45);
('Peter', NULL, 45);


returnerer sand, selvom vi her har NULLs; og brugen af DISTINCT vil fjerne den ene af rækkerne. Med andre ord er NULL = NULL i en vertikal sammenligning! Læg mærker til at rækkerne her står neden under hindanden - dvs. i en vertikal opsætning.

Jeg indrømmer at dette med horisontale og vertikale sammenligninger er grænsende til det teoretiske, men det er en helt uformel betragning som kan hjælpe til at forstå hvordan en SQL maskine (eng.: SQL engine) virker.




<< < 123 > >>

Hvad synes du om denne artikel? Giv din mening til kende ved at stemme via pilene til venstre og/eller lægge en kommentar herunder.

Del også gerne artiklen med dine Facebook venner:  

Kommentarer (5)

User
Bruger #6559 @ 07.02.08 19:34
En meget god artikel. Jeg synes der er mange gode ting, som man måske ikke lige er faldet over i andre artikler.

User
Bruger #2730 @ 08.02.08 09:54
Du skriver at følgende er ulovlig: select name, sum(speed)
Både ja og nej, den kan ikke eksekvere, der mangler en grupperings funktion. Så for at den skal virke skal den hedde select name, "sum(speed) group by name". Samtidig synes jeg starten er lidt malplaceret, du vil skrive om aggregeringsfunktioner, men starter ud med at have 2 sider om alt andet end aggregeringsfunktioner. Du er hurtigt omkring joins, men gør det ikke færdigt...

Det du dækker omkring aggregeringsfunktioner er godt og velskrevet
User
Bruger #4522 @ 08.02.08 11:17
Hej Brian,

Ang. SELECT name, sum(speed) værende ulovlig så tager jeg selvsagt ikke GROUP BY i betragtning i artiklen overhovedet, og det er med vilje da det er et stort emne for sig selv. Jeg kunne måske havde nævnt det forbehold mere eksplicit.

Meningen var at artiklen udelukkende skulle omhandle SELECT og mængdefunktioner, hverken GROUP BY eller FROM (og altså ej heller JOINs); artiklen var ikke ment til en nybegynder hvorfor jeg antog det rimeligt at læseren havde noget SQL viden i forvejen, og derfor kunne betragte emnerne i isolation.

Jeg medgiver dog at jeg muligvis kunne have gjort disse forbehold mere eksplicit. Du tydeliggør i hvert fald at jeg har fejlet hvad det angår.

Tak for dine kommentarer ;)
User
Bruger #11375 @ 17.02.08 01:11
Hej Jacob,

en virkelig superfed artikel som der simpelthen har været en fornøjelse at læse.

SQL er virkelig et fedt sprog med ret så mange muligheder. :-)

User
Bruger #8782 @ 20.06.08 16:07
Meget nyttig artikel, men mest for de brugere som har en lille smule forstand på MySQL vil jeg påskønne. :D
- Men jeg synes stadig artiklen er til en 5'er!

Og som Martin Th. Sonne siger, det var virkelig en fornøjelse at læse artiklen!
Du skal være logget ind for at skrive en kommentar.
t