27.07.2014 Views

Enemmän irti sql-tietokannoista - MikroPC

Enemmän irti sql-tietokannoista - MikroPC

Enemmän irti sql-tietokannoista - MikroPC

SHOW MORE
SHOW LESS

Create successful ePaper yourself

Turn your PDF publications into a flip-book with our unique Google optimized e-Paper software.

HYVÄTNEUVOT<br />

HYVÄT NEUVOT: SQL-TIETOKANNAT<br />

TEKSTI: JONI MOILANEN<br />

Enemmän <strong>irti</strong><br />

<strong>sql</strong>-<strong>tietokannoista</strong><br />

SQL-TIETOKANTAKIELESTÄ KERTOVAN KAKSIOSAINEN SARJAN AVAUSOSA<br />

KESKITTYY HARVOIN KÄYTETTYIHIN, MUTTA TUIKI HYÖDYLLISIIN<br />

SQL:N PIIRTEISIIN. SEURAAVA OSA KESKITTYY KANNAN OPTIMOINTIVINKKEIHIN.<br />

Sql (Structured Query Language) on monipuolinen tiedon<br />

määrittelyyn, muokkaukseen ja hakemiseen kehitetty tietokantakieli.<br />

Sql:n yleisyys mahdollistaa <strong>sql</strong>-taitojen hyödyntämisen<br />

eri valmistajien tietokantapalvelimien kanssa, vaikkakin<br />

pieniä eroja toteutuksissa löytyy yhä.<br />

Tähän kaksiosaiseen sarjaan on koottu käyttökelpoisia vinkkejä <strong>sql</strong>-kannan<br />

tehokkaampaan hyödyntämiseen. Saadakseen sarjasta täyden hyödyn,<br />

on lukijalla oltava entuudestaan perustiedot relaatio<strong>tietokannoista</strong> ja<br />

<strong>sql</strong>:stä.<br />

SARAKKEIDEN YHDISTÄMINEN<br />

Sql:llä on helppo yhdistää sarakkeita suoraan SELECT-osiossa. Tämä on<br />

usein myös tehokkaampaa kuin itse sovelluksessa tehtynä.<br />

Relaationaalista tietokantaa suunniteltaessa ensin normalisoidaan tiedot,<br />

eli jaotellaan tauluihin ja sarakkeisiin. Ensimmäisessä normalisointisäännössä<br />

edellytetään, että sarake saa koostua vain yhdestä tiedosta. Esimerkiksi<br />

ihmisten nimet olisi hyvä pilkkoa erillisiin Etunimi ja Sukunimi -sarakkeisiin,<br />

jotta molempiin tietoihin pääsee tarvittaessa erikseen käsiksi.<br />

Käytännössä nimitietoja tarvitaan kuitenkin usein yhdessä ja yhdistäminen<br />

käy helposti jo tietokannassa. Huomaa, että välilyönti on lisättävä itse:<br />

SELECT Etunimi || ' ' || Sukunimi As 'Nimi'<br />

FROM Henkilot<br />

Kaksi putkimerkkiä ( || )on ANSI-SQL:n määrittelemä menetelmä sarakkeiden<br />

yhdistämiseen, jota kaikki tietokannat eivät noudata. Oracle ja PostgreSQL<br />

tukevat, mutta esimerkiksi Microsoftin Accessissa merkki on & ja<br />

SQL Serverissä yhdistämiseen on käytettävä plussaa (+) ja MySQL:ssä<br />

Concat() -funktiota.<br />

Samoin sarakkeita yhdistäessä olisi nimettävä tämä uusi, muodostettu<br />

sarake jollakin sarakesynonyymillä (esimerkin As 'Nimi'), jotta siihen olisi<br />

mahdollista viitata ohjelmallisesti ja jotta tulosjoukko olisi selkeämpi lukea.<br />

SQL JA LASKUTOIMITUKSET<br />

Myös laskutoimitukset antavat paljon mahdollisuuksia. Kaikki peruslaskutoimitukset<br />

onnistuvat suoraan sarakkeiden ja vakioiden välillä. Tietokannasta<br />

riippuen voi löytyä myös paljon hyödyllisiä funktioita, joita voi hyödyntää.<br />

Ajatellaan, että meillä on Tuotteet taulu, jossa on sarake Hinta, joka sisältää<br />

tuotteen hinnan euroissa ja haluamme tulosjoukossa myös sarakkeen,<br />

josta näkee hinnan markoissa. SQL:llä tämä onnistuu helposti:<br />

SELECT Hinta As 'Hinta (eur)', Hinta * 5.94573 As 'Hinta (mk)'<br />

FROM Tuotteet<br />

Desimaalierotin on aina piste. Tulosjoukkoon tulee tällä kyselyllä pitkiä<br />

desimaaleja, joten käytännössä voisi olla järkevää pyöristää tulos esimerkiksi<br />

Cast()-funktion avulla. Cast() on tarkoitettu tietotyyppien muuntamiseen<br />

ja sitä tarvitaan erityisesti silloin, kun haluaa yhdistellä erityyppisiä numeroita<br />

ja merkkijonoja sisältäviä sarakkeita keskenään.<br />

Seuraava hieman muokattu kysely pyöristää luvut luettavampaan muotoon:<br />

SELECT Hinta As 'Hinta (eur)', Cast(Hinta * 5.94573 As Decimal(15,2))<br />

As 'Hinta (mk)'<br />

FROM Tuotteet<br />

Muunnettava arvo tulee sulkeisiin ensimmäisenä, jonka jälkeen tulee As<br />

ja kohdetyyppi, joka on tässä tapauksessa Decimal. Decimal-tyyppiin muuntaessa<br />

tarvitaan tieto tarkkuudesta ja desimaalipilkun jälkeisistä numeroista.<br />

Ensimmäinen luku (15) kertoo siis, kuinka paljon numeroita luvussa voi<br />

olla yhteensä pilkun molemmin puolin ja jälkimmäinen luku (2) kertoo,<br />

kuinka monta numeroa halutaan pilkun oikealle puolelle.<br />

ULKOLIITOKSET<br />

Ulkoliitokset ovat erittäin käteviä silloin harvoin, kun niitä tarvitaan. Jotta<br />

ulkoliitokset ymmärtäisi, on hyvä palauttaa ensin mieleen, miten tavalliset<br />

(sisä-)liitokset oikeastaan toimivat.<br />

58 <strong>MikroPC</strong> 12 / 2002 W W W . M I K R O P C . N E T


ASIAKKAAT<br />

AsiakasID Etunimi Sukunimi<br />

10 Anssi Kestotilaaja<br />

20 Mikko Peesee<br />

30 Matti Meikäläinen<br />

Sanotaan, että meillä on tyypillinen suhde Tilaukset ja Asiakkaat -taulujen<br />

välillä. Tilaukset -taulussa oleva viiteavain (AsiakasID) viittaa Asiakkaat -<br />

taulun perusavaimeen (AsiakasID).<br />

Standardin mukaisella liitoksella nämä taulut voidaan yhdistää seuraavanlaisella<br />

<strong>sql</strong>-kyselyllä:<br />

SELECT *<br />

FROM Tilaukset t JOIN Asiakkaat a<br />

ON t.AsiakasID = a.AsiakasID<br />

Mikäli tietokanta ei tue tätä ANSI SQL-92:n määrittelemää liitosta tai<br />

olet muuten mieltynyt perinteiseen liitokseen, niin sama saadaan aikaan<br />

myös sen avulla:<br />

SELECT *<br />

FROM Tilaukset t, Asiakkaat a<br />

WHERE t.AsiakasID = a.AsiakasID<br />

TILAUKSET<br />

TilausID AsiakasID Tuote<br />

1001 10 <strong>MikroPC</strong><br />

1002 10 Talouselämä<br />

1003 20 Tietoviikko<br />

1004 40 <strong>MikroPC</strong><br />

Tämä liitos vastaa siis kysymykseen: "Hae kaikki tilaukset ja niihin liittyvät<br />

asiakkaat".<br />

SISÄLIITOS<br />

TilausID AsiakasID Tuote AsiakasID Etunimi Sukunimi<br />

1001 10 <strong>MikroPC</strong> 10 Anssi Kestotilaaja<br />

1002 10 Talouselämä 10 Anssi Kestotilaaja<br />

1003 20 Tietoviikko 20 Mikko Peesee<br />

▲ Sisäliitoksen palauttama tulosjoukko. Pois jäävät AsiakasID 30 ja<br />

TilausID 1004.<br />

SELECT *<br />

FROM Tilaukset t RIGHT OUTER JOIN Asiakkaat a<br />

ON t.AsiakasID = a.AsiakasID<br />

Vastaavasti RIGHTin tilalle laitettaisiin LEFT, mikäli haluttaisiin korostaa<br />

Tilaukset-taulua. Lisäksi on vielä FULL-ulkoliitos, joka korostaa molempia<br />

puolia. Perinteisessä liitoksessa laitetaan yhtäsuuruusmerkin vasemmalle<br />

tai oikealle puolelle tähti (*) kuvaamaan ulkoliitosta.<br />

Tulosjoukossa on NULL kaikissa niissä Tilaukset -taulun riveissä, joille ei<br />

löydy vastinparia Asiakkaat -taulusta. Läheskään aina ulkoliitos ei välttämättä<br />

palauta mitään sisäliitoksesta poikkeavaa, vaan se edellyttää vastinparittomia<br />

rivejä.<br />

ASIAKKAITA KOROSTAVA ULKOLIITOS<br />

TilausID AsiakasID Tuote AsiakasID Etunimi Sukunimi<br />

1001 10 <strong>MikroPC</strong> 10 Anssi Kestotilaaja<br />

1002 10 Talouselämä 10 Anssi Kestotilaaja<br />

1003 20 Tietoviikko 20 Mikko Peesee<br />

NULL NULL NULL 30 Matti Meikäläinen<br />

▲ Asiakas-taulua korostava ulkoliitos palauttaa myös ne asiakkaat,<br />

jotka eivät ole tilanneet mitään.<br />

TILAUKSIA KOROSTAVA ULKOLIITOS<br />

TilausID AsiakasID Tuote AsiakasID Etunimi Sukunimi<br />

1001 10 <strong>MikroPC</strong> 10 Anssi Kestotilaaja<br />

1002 10 Talouselämä 10 Anssi Kestotilaaja<br />

1003 20 Tietoviikko 20 Mikko Peesee<br />

1004 40 <strong>MikroPC</strong> NULL NULL NULL<br />

MOLEMPIA PUOLIA KOROSTAVA ULKOLIITOSKYSELY<br />

TilausID AsiakasID Tuote AsiakasID Etunimi Sukunimi<br />

1001 10 <strong>MikroPC</strong> 10 Anssi Kestotilaaja<br />

1002 10 Talouselämä 10 Anssi Kestotilaaja<br />

1003 20 Tietoviikko 20 Mikko Peesee<br />

1004 40 <strong>MikroPC</strong> NULL NULL NULL<br />

NULL NULL NULL 30 Matti Meikäläinen<br />

Entä jos joukossa on asiakkaita, jotka eivät ole vielä tilanneet tuotteita<br />

tai tilaukset-taulussa on tilauksia, joihin liitetyt asiakkaat ovat syystä tai<br />

toisesta poistettu?<br />

Yllä oleva kysely jättää nämä rivit kokonaan palauttamatta. Jos vaikka<br />

halutaan tulosjoukkoon mukaan myös ne asiakkaat, joilla ei ole tilausta, selvitään<br />

helpoiten ulkoliitoksella, vaikka muitakin keinoja siihen toki on.<br />

Ulkoliitokset on helpoin tehdä siten, että aloittaa ensin tavallisella sisäliitoksella,<br />

jonka muuttaa sen jälkeen oikeaksi tai vasemmaksi ulkoliitokseksi.<br />

Saadaksesi tulosjoukkoon myös ne asiakkaat, jotka eivät ole tehneet<br />

lainkaan tilauksia, on lisättävä RIGHT OUTER (tai pelkkä RIGHT) JOIN-sanan<br />

eteen. RIGHT sen vuoksi, sillä korostettava Asiakkaat-taulu on kyselyssä<br />

JOIN-sanan oikealla puolella. Lopullinen kysely näyttää siis tältä:<br />

Ulkoliitokset ovat siis käteviä myös rikkinäisten linkkien löytämiseksi,<br />

mikäli puutteellinen liiketoimintalogiikka on päästänyt sellaisia tietokantaan.<br />

Kun suodattaa WHERE-ehdolla kaikki NULLeja sisältävät rivit (WHERE<br />

sarake IS NULL), jää ylimääräiset tietueet helposti tarkasteltavaksi.<br />

Huomaa, että esimerkkikyselyssä on käytetty SELECT *-muotoa, jota ei<br />

tulisi tuotantokoodissa koskaan käyttää, vaan sarakkeet tulisi aina luetella,<br />

vaikka haluaisi kaikki ja rivitkin tulee käytännössä aina rajata.<br />

HELPPOA TILASTOINTIA<br />

Tietokannat sisältävät paljon markkinnoinnissakin hyödyllistä tietoa, mikäli<br />

sitä osaa kaivaa esille.<br />

GROUP BY auttaa ryhmittelemään tiedot annettujen ryhmittelyehtojen<br />

W W W . M I K R O P C . N E T <strong>MikroPC</strong> 12 / 2002 59


mukaisesti. GROUP BY:n avulla on helppoa vastata kysymyksiin, kuten<br />

"Kuinka paljon asiakkaita on kaupungeittain ja mikä on heidän ostoksiensa<br />

keskiarvo?".<br />

Ennen kuin GROUP BY:tä lähtee käyttämään, on syytä ymmärtää, että<br />

se toimii aivan erilailla, kuin tyypilliset SELECT-lauseet. SELECT ilman<br />

GROUP BY:tä palauttaa rivejä taulusta tai tauluista sellaisenaan.<br />

GROUP BY:tä hyödyntävä kysely puolestaan yhdistää useamman rivin<br />

annetulla, yhdistävällä tekijällä, joka voisi olla maa, kaupunki tai viiteavain.<br />

Ryhmittelyn jälkeen kysely kertoo esimerkiksi ryhmään kuuluvien rivien<br />

määrän tai summan.<br />

SELECTin jälkeen valitaan sarake, jonka mukaan tiedot halutaan ryhmitellä<br />

tarkastelua varten, jonka jälkeen koostefunktiolla kerrotaan, millä periaatteella<br />

sarakkeen tiedot niputetaan. Koostefunktiot on listattu oheisessa<br />

taulukossa. Osa koostefunktiosta hyväksyy myös DISTINCTin, jolloin huomioidaan<br />

vain erilaisten rivien arvot.<br />

Tulos olisi hieman selkeämpi, jos kategorioissa näkyisi numeroiden sijaan<br />

itse kategorioiden nimet, mutta se on ratkaistu yksinkertaisella liitoksella<br />

ja vaihtamalla ryhmittelysarakkeeksi CategoryName:<br />

SELECT CategoryName, Count(*) As 'Määrä', Sum(UnitPrice) As 'Summa',<br />

Avg(UnitPrice) As 'Keskiarvo'<br />

FROM Products p JOIN Categories c ON p.CategoryID = c.CategoryID<br />

GROUP BY CategoryName<br />

Lopputuloksena on hieman luettavampi tulosjoukko:<br />

RYHMITELTY TULOSJOUKKO<br />

SQL-FUNKTIOITA<br />

Koostefunktio/syntaksi<br />

Sum(ALL | DISTINCT sarake)<br />

AVG(ALL | DISTINCT sarake)<br />

COUNT(ALL | DISTINCT sarake)<br />

COUNT(*)<br />

Min (sarake)<br />

Max (sarake)<br />

Merkitys<br />

Arvojen summa.<br />

Keskiarvo.<br />

Arvojen määrä (NULLeja ei huomioida).<br />

Rivien määrä (laskee mukaan myös<br />

NULLit).<br />

Pienin arvo.<br />

Suurin arvo.<br />

Esimerkiksi seuraava kysely kertoo tuotteet-taulun (Products) tuotteiden<br />

määrän kategorioittain ja laskee niiden hintojen summan ja keskiarvon.<br />

▲ Liitokset ryhmittelyiden kanssa selkeyttävät kyselyä.<br />

--- Esimerkki SQL Serverin NorthWind-esimerkkitietokannalla:<br />

SELECT CategoryID, Count(*) As 'Määrä', Sum(UnitPrice) As 'Summa', Avg(UnitPrice) As 'Keskiarvo'<br />

FROM Products<br />

GROUP BY CategoryID<br />

Ryhmittelemme tuotteet kategorian perusteella ja kohdistamme yhdistettyihin<br />

riveihin erilaisia koostefunktioita. Tuloksena on tämän näköinen<br />

tulosjoukko:<br />

GROUP BY -TULOSJOUKKO<br />

Huomaa, ettei sarakkeita voi tavallisen<br />

SELECT-lauseen tavoin lisätä mielin määrin,<br />

ellei niitä käytä ryhmittelyyn. Esimerkiksi<br />

myynnit voi ryhmitellä ensin paikkakunnittain<br />

ja sitten tuotteittain.<br />

Ryhmittelyyn sopivia sarakkeita etsiessä<br />

kannattaa hakea sellaisia, joissa toistuu<br />

usein sama arvo, kuten kaupunki tai toiseen tauluun viittaava viiteavain, kuten<br />

esimerkin CategoryID.<br />

Mikäli GROUP BY:tä hyödyntävässä kyselyssä käyttää WHERE:ä, karsitaan<br />

suodatetut rivit ennen ryhmittelyä ja tulosjoukon näkymistä. Entä jos<br />

haluaa karsia vielä rivejä ryhmittelyn jälkeen? Se onnistuu HAVING-lauseella,<br />

jota käytetään, kuin WHEREä.<br />

Esimerkiksi edellisen kyselyn perään voisi lisätä:<br />

HAVING SUM(UnitPrice) > 400<br />

Koska vain Beverages-kategoria (juomat) täyttää tämän ehdon, on se ainoa<br />

rivi tulosjoukossa. ■<br />

▲ GROUP BY -esimerkin palauttama tulosjoukko kertoo tietoa<br />

tuotteiden määrästä kategorioittain ja hintojen summista ja<br />

keskiarvoista.<br />

60 <strong>MikroPC</strong> 12 / 2002 W W W . M I K R O P C . N E T

Hooray! Your file is uploaded and ready to be published.

Saved successfully!

Ooh no, something went wrong!