Yhteenvetofunktiot Näkymät Tietokannan ylläpito Tietokantatapahtumat... 88

Samankaltaiset tiedostot
Tietokantojen perusteet, syksy 1999 SQL- osa Harri Laine 1. SQL-yhteenvetofunktiot. SQL-yhteenvetofunktiot

Helsingin yliopisto, tktl DO Tietokantojen perusteet, kevät 2000 SQL- osa Harri Laine 1. SQL-yhteenvetofunktiot. SQL-yhteenvetofunktiot

Helsingin yliopisto, tktl DO Tietokantojen perusteet, kevät 2000 SQL- osa Harri Laine 1. SQL-yhteenvetofunktiot. SQL-yhteenvetofunktiot

Helsingin yliopisto, TKTL Tietokantojen perusteet, k 2000 Tietokantaohjelmointi Harri Laine 1. SQL:n käyttö ohjelmissa

Insert lauseella on kaksi muotoa: insert into taulu [(sarakenimet)] values (arvot)

SQL - Tietokannan ylläpito. SQL - Tietokannan ylläpito. SQL - Tietokannan ylläpito. SQL - Tietokannan ylläpito. SQL - Tietokannan ylläpito

Kyselyn yleisrakenne:

EXEC SQL BEGIN DECLARE SECTION

select tulostietomäärittely from taulukkeet [where valintaehdot] [group by ryhmitystekijät] [having ryhmärajoitteet] [order by järjestysperusta]

Harjoitustehtävä 1. Harjoitustehtävä 2. Harjoitustehtävä 2. Harjoitustehtävä 2. Harjoitustehtävä 2. SQL kysely

Johdanto Javaan ja tietokantojen käsittelyyn Java Database Connectivity (JDBC)

RDBMS - Yhteyskäytännöt

Tietokantojen perusteet, syksy 1999 SQL- osa Harri Laine 1. SQL-valintaehto. SQL-valintaehto. Opettajien nimet: Opiskelijoiden pääaineet

Helsingin yliopisto, TKTL Tietokantojen perusteet, k 2000 SQL- osa Harri Laine 1. SQL-valintaehto. SQL-valintaehto.

Java ja tietokannan käsittely (JDBC)

Tietokannat II -kurssin harjoitustyö

SELECT-lauseen perusmuoto

SQL-perusteet, SELECT-, INSERT-, CREATE-lauseet

SQL - STRUCTURED QUERY LANGUAGE

HSMT Tietokannoista. Ville Leppänen. HSMT, c Ville Leppänen, IT, Turun yliopisto, 2008 p.1/32

6 WWW-tietokantasovellukset Relaatiotietokannan suunnittelusta 134

TIEDONHALLINTA - SYKSY Luento 11. Hannu Markkanen /10/12 Helsinki Metropolia University of Applied Sciences

Ohjelmoinnin perusteet Y Python

Tietokantojen perusteet k2004helsingin yliopisto/tktl Tietokantojen perusteet, s 2005 SQL-perusteet. Harri Laine 1. SQL tietokantakieli

määritellä ja muokata tietokantaa ja sen käyttöoikeuksia virittää tietokannan talletusrakenteita hakea tietoa tietokannasta

määritellä ja muokata tietokantaa ja sen käyttöoikeuksia virittää tietokannan talletusrakenteita hakea tietoa tietokannasta

käännös käännösvaiheessa tarkasettaan linkitys

SQL-kielen perusteet. Tietokantojen perusteet

HAAGA-HELIA Heti-09 1 (12) ICT05 Tiedonhallinta ja Tietokannat O.Virkki Näkymät

Helsingin yliopisto/tktl Tietokantojen perusteet, s 2007 SQL:n perusteet. Harri Laine 1. SQL tietokantakieli. SQL tietokantakieli

Proseduurit, funktiot ja herättimet - esimerkkeinä Oracle, SQL Server, MySQL ja OCELOT. Jouni Huotari S2008

PROSEDUURIT, FUNKTIOT JA HERÄTTIMET - ESIMERKKEINÄ ORACLE, SQL SERVER, MYSQL JA OCELOT JOUNI HUOTARI K2009

4.3.4 SQL kyselyt... 45

TIEDONHALLINTA - SYKSY Luento 8. Saapumisryhmä: Pasi Ranne /9/13 Helsinki Metropolia University of Applied Sciences

TIEDONHALLINTA - SYKSY Luento 10. Hannu Markkanen /10/12 Helsinki Metropolia University of Applied Sciences

TIETOKANTOJEN PERUSTEET MARKKU SUNI

Tehtävä 1. Tietojen lisääminen, poistaminen, päivittäminen ja tulostaminen

Tietokanta (database)

HELIA 1 (14) Outi Virkki Tiedonhallinta

Kirjasto Relaatiotietokannat Kevät Auvinen Annemari Niemi Anu Passoja Jonna Pulli Jari Tersa Tiina

HELIA 1 (11) Outi Virkki Tiedonhallinta

HELIA 1 (15) Outi Virkki Tietokantasuunnittelu

Helsingin yliopisto Tietojenkäsittelytieteen laitos (H.Laine) Tietokantojen perusteet. Liitteenä: Tiivistelmä SQL-syntaksista

Ohjelmistojen mallintamisen ja tietokantojen perusteiden yhteys

Helsingin yliopisto/tktl DO Tietokantojen perusteet, s 2000 Johdanto & yleistä Harri Laine 1. Tietokanta. Tiedosto

Ohjelmoinnin perusteet, syksy 2006

HELIA TIKO-05 1 (17) ICT03D Tieto ja tiedon varastointi Räty, Virkki

IDL - proseduurit. ATK tähtitieteessä. IDL - proseduurit

HELIA 1 (14) Outi Virkki Tiedonhallinta

ATK tähtitieteessä. Osa 3 - IDL proseduurit ja rakenteet. 18. syyskuuta 2014

Java-kielen perusteet

jotakin käyttötarkoitusta varten laadittu kokoelma toisiinsa liittyviä säilytettäviä tietoja

Helsingin yliopisto, Tietojenkäsittelytieteen laitos Tietokantojen perusteet, , H.Laine

TIETOKANTOJEN PERUSTEET OSIO 14 MARKKU SUNI

5. HelloWorld-ohjelma 5.1

Ohjelmoinnin perusteet Y Python

Ohjelmoinnin perusteet Y Python

SQL. ! nykystandardi SQL3 eli SQL'99. ! CREATE TABLE, ALTER TABLE ja DROP TABLE. ! CREATE VIEW ja DROP VIEW. ! CREATE INDEX ja DROP INDEX

5 SQL TIETOKANTAKIELI...33

Relaation tyhjyyden testaaminen

11. Javan toistorakenteet 11.1

12. Javan toistorakenteet 12.1

Kirjoita jokaiseen erilliseen vastauspaperiin kurssin nimi, tenttipäivä, oma nimesi (selkeästi), opiskelijanumerosi ja nimikirjoituksesi

SQL:N PERUSTEET MARKKU SUNI

Relaatioalgebra. Kyselyt:

Tietokannat II -kurssin harjoitustyö

CSE-A1200 Tietokannat

Alkuarvot ja tyyppimuunnokset (1/5) Alkuarvot ja tyyppimuunnokset (2/5) Alkuarvot ja tyyppimuunnokset (3/5)

1. Omat operaatiot 1.1

Opettajana Mika Sorsa, HAMK:n ammatillisen opettajakoulutuksen opetusharjoittelija

Tutoriaaliläsnäoloista

12. Javan toistorakenteet 12.1

Ohjelmoinnin jatkokurssi, kurssikoe

Sisällys. 12. Näppäimistöltä lukeminen. Yleistä. Yleistä

Kyselyt: Lähtökohtana joukko lukuja Laskukaava kertoo miten luvuista lasketaan tulos soveltamalla laskentaoperaatioita

Sisällys. 1. Omat operaatiot. Yleistä operaatioista. Yleistä operaatioista

Relaatioalgebra. Relaatioalgebra. Relaatioalgebra. Relaatioalgebra - erotus (set difference) Kyselyt:

Tietokannat. CREATE TABLE table(col1,col2,... ); Luo uuden taulun. CREATE TABLE opiskelijat(opnumero,etunimi,sukunimi);

Ohjelmoinnin perusteet Y Python

Kirjoita kuhunkin erilliseen vastauspaperiin kurssin nimi, tentin päiväys, oma nimesi, syntymäaikasi ja nimikirjoituksesi.

Tällä viikolla. Kotitehtävien läpikäynti Aloitetaan Pelifirman tietovaraston suunnittelu Jatketaan SQL-harjoituksia

HELIA TIKO-05 1 (22) ICT03D Tieto ja tiedon varastointi E.Räty, O.Virkki

FROM-lausekkeessa voidaan määritellä useampi kuin yksi taulu, josta tietoja haetaan: Tuloksena on taululistassa lueteltujen taulujen rivien

Koostefunktiot. Viisi standardifunktiota: Esim. montako henkilöä on henkilo-taulussa:

Ohjelmoinnin perusteet Y Python

TIEDONHALLINNAN PERUSTEET - SYKSY 2013

Hakukyselyt: SELECT * FROM taulu WHERE sarake1 = Malli Nimi [WHERE sarake1 LIKE M% ] [WHERE BETWEEN ehto1 AND ehto2] [WHERE sarake1 IN/= (alikysely)]

Tiedonhallinnan perusteet. Viikko 1 Jukka Lähetkangas

815338A Ohjelmointikielten periaatteet Harjoitus 3 vastaukset

TIETOKANNAT: MYSQL & POSTGRESQL Seminaarityö

811120P Diskreetit rakenteet

8. Näppäimistöltä lukeminen 8.1

4.3.1 SQL tietokanta SQL:n kirjoitusasu SQL määrittelykielenä... 36

12. Näppäimistöltä lukeminen 12.1

1.3Lohkorakenne muodostetaan käyttämällä a) puolipistettä b) aaltosulkeita c) BEGIN ja END lausekkeita d) sisennystä

Pedacode Pikaopas. Tietokantaa hyödyntävän sovelluksen luominen

Java-kielen perusteet

HOJ Haja-aiheita. Ville Leppänen. HOJ, c Ville Leppänen, IT, Turun yliopisto, 2012 p.1/10

Ohjelmoinnin perusteet Y Python

15. Ohjelmoinnin tekniikkaa 15.1

Transkriptio:

Tietokantojen perusteet, DO NOT PRINT THIS DOCUMENT opetusmoniste osa 4 SQL:N PIIRTEITÄ JA TIETOKANTAOHJELMOINTI Harri Laine 4.3.4.10 Yhteenvetofunktiot... 68 4.3.4.11 Näkymät... 78 4.3.4.12 Tietokannan ylläpito... 82 4.3.4.13 Tietokantatapahtumat... 88 4.3.5 Tietokannan käyttö ohjelmasta... 91 4.3.5.1 Sulautettu SQL... 92 4.3.5.2 Tietokannan käyttö liittymäkirjaston avulla... 94 4.3.5.3 Tietokantaohjelmointikielet... 102

4.3.4.10 Yhteenvetofunktiot Tähän asti on käsitelty kyselyjä, joissa tuotetaan yksi tulosrivi jokaista valintaehdot täyttävää kohdetaulujen riviyhdistelmää kohti. Tällaiset kyselyt tuottavat tuloksenaan detaljitietoa. SQL:llä on mahdollista tuottaa myös yhteenvetotietoa (aggregate data). Yhteenvetotietojen muodostukseen ovat käytettävissä funktiot AVG keskiarvo COUNT lukumäärä MAX suurin arvo MIN pienin arvo ja SUM summa. Joissakin järjestelmissä on näiden lisäksi myös muita tilastollisia suureita, esimerkiksi keskihajonnan, laskevia funktioita. Yhteenvetotietoja haluttaessa tuloksen muodostamisperiaate muuttuu. Yhteenveto voidaan tuottaa joko koko vastausaineistosta tai ryhmiin jaotellusta vastausaineistosta. Vastausaineistolla tarkoitetaan tässä valintaehdot täyttäviä riviyhdistelmiä. Tuotettaessa yhteenveto koko vastausaineistosta saadaan kyselyn tulokseksi aina yksi rivi. Jos vastausaineisto on tyhjä tuottavat muut funktiot paitsi COUNT tuloksenaan tyhjäarvon (NULL). COUNT tuottaa tyhjästä vastausaineistosta arvon 0. Esimerkkejä: Ilmoittautumisten lukumäärä: select count(*) from ilmoittautuminen; Kurssien pienin ja suurin opintoviikkomäärä select min(opintoviikot) as pienin, max(opintoviikot) as suurin from kurssi; Helsinkiläisten opiskelijoiden keskimääräinen aloitusvuosi select avg(aloitusvuosi) from opiskelija where kaupunki= Helsinki ; 68

Funktioille annetaan argumentiksi lauseke, jonka perusteella funktion arvo lasketaan. Funktiolle COUNT voi antaa argumentiksi myös tähden (*) osoittamaan, että tulokseksi halutaan rivien lukumäärä. Argumenttiin voidaan liittää määre DISTINCT ilmaisemaan sitä, että funktion arvo tulisi laskea sarakkeen erilaisten arvojen perusteella. Funktion arvoa laskettaessa tyhjäarvot jätetään huomiotta eli ne eivät vaikuta tulokseen. Ainoa poikkeus tästä on COUNT(*), joka laskee mukaan kaikki rivit, vaikka jokin rivi sisältäisi pelkkiä tyhjäarvoja. Sensijaan sarakekohtaisesti COUNTfunktiokin ilmoittaa vain todellisten arvojen lukumäärä. Esimerkki: Taulu R(A,B,C) : A B C select count(*) from R = > 5 1 1 2 select count(a) from R => 5 1 2 2 select count(b) from R => 3 3 null 2 select count(distinct B) from R => 2 3 null 2 select count(*) from R where B is null => 2 5 2 2 select (distinct C) from R = 1 DISTINCT määreen käyttö toistuvien arvojen ottamiseksi vain kertaalleen mukaan laskentaan on yleensä järkevää vain COUNT-funktion yhteydessä. Keskiarvon tai summan laskeminen erillisistä arvoista tuottaa harvoin mitään hyödyllistä tulosta. Esimerkki: Monellako eri paikkakunnalla opiskelijat asuvat? select Opiskelijoiden asuinpaikkakuntia:, count(distinct kaupunki) from opiskelija; Yhteenvetofunktioita voi käyttää lausekkeissa ja niiden argumentteina voi käyttää lausekkeita. Toista yhteenvetofunktiota ei kuitenkaan voi käyttää toisen argumenttina eikä lausekkeisiin tai ylipäätään tulokseen saa mukaan samasta vastusaineistosta sekä rivikohtaista deltaljitietoa että yhteenvetotietoa. 69

Esimerkkejä: Suurimman ja pienimmän opintoviikkomäärän erotus select MAX(opintoviikot)-MIN(opintoviikot) erotus from kurssi; Kertokoon opiskelijanumeron parillisuus opiskelijan olevan naispuolinen. Naispuolisten opiskelijoiden osuus prosentteina saadaan tällöin selville kyselyllä: select 100*(count(onumero)-sum(mod(onumero,2)))/count(onumero) from opiskelija; Tässä mod(onumero,2) on miespuolisilla opiskelijoilla 1, joten sum(mod(onumero,2)) ilmoittaa miespuolisten opiskelijoiden lukumäärän. Erotuksella saadaan naispuolisten lukumäärä ja jakolaskulla heidän osuutensa. Kysely select nimi, max(opintoviikot) from kurssi; on syntaktisesti virheellinen. Siinä halutaan samasta aineistosta yhteenvetotietoa ja detaljitietoa. Miltä riviltä tuo detaljitieto otettaisiin? Jos kyselyssä haluttaisiin saada selville minkä kurssin opintoviikkomäärä on suurin se pitäisi esittää esimerkiksi seuraavasti select nimi, opintoviikot from kurssi where opintoviikot = (select max(opintoviikot) from kurssi); tai select nimi, maksi from kurssi K, (select max(opintoviikot) maksi from kurssi) M where K.opintoviikot=M.maksi; Kummassakin näistä vaihtoehdoista maksimi lasketaan ainakin loogisesti eri rivijoukosta kuin mistä detaljitieto nimi haetaan. 70

Ryhmiin jaottelu Ryhmiin jaottelu saadaan aikaan lisäämällä kyselyyn ryhmittelymääre group by <ryhmitysperusteet>. Ryhmitysperusteina annetaan sarakeluettelo ja ryhmät muodostetaan siten, että ryhmään kuuluvat kaikki sellaiset vastausaineiston rivit, joilla on yhtenevät arvot kussakin sarakeluettelon sarakkeessa (kuvat 20 ja 21) X= taulu T group by A (Muodostuu 3 ryhmää.) A B C D group 1 4 6 7 A=1 1 1 4 2 A=1 1 1 5 2 A=1 2 4 8 7 A=2 2 3 5 1 A=2 3 1 5 2 A=3 3 2 4 6 A=3 3 2 1 5 A=3 Kuva 20: Ryhmittely yhden sarakkeen perusteella Y= taulu T group by A,B (Muodostuu 6 ryhmää) A B C D group 1 1 4 2 A=1, B=1 1 1 5 2 A=1, B=1 1 4 6 7 A=1, B=4 2 3 5 1 A=2, B=3 2 4 8 7 A=2, B=4 3 1 5 2 A=3, B=1 3 2 4 6 A=3, B=2 3 2 1 5 A=3, B=2 Kuva 21: Ryhmittely 2 sarakkeen perusteella. 71

Ryhmitetystä aineistosta yhteenvetofuntioiden arvot lasketaan ryhmäkohtaisesti ja tulokseen tulevien rivien lukumäärä on sama kuin ryhmien lukumäärä. Ylläolevasta ryhmittelystä X tulisi siis 3 tulosriviä ja ryhmittelystä Y 6 tulosriviä. Esimerkkejä: select A, sum (B), count(c) from T; A sum(b) count(c) 1 6 3 2 7 2 3 5 3 select A,B, count(c), sum(d) from T; A B count(c ) sum(d) 1 1 2 4 1 4 1 7 2 3 1 1 2 4 1 7 3 1 1 2 3 2 2 11 Esimerkki: Kurssien harjoitusryhmiin ilmoittautuneiden opiskelijoiden lukumäärä Kysely select nimi, ryhmanro, count(*) from kurssi, ilmoittautuminen where ilmoittautuminen.kurssikoodi=kurssi.koodi group by nimi, ryhmanro; 72

tuottaa oikean halutun tuloksen, jos jokaiseen ryhmään on ilmoittautunut vähintään yksi opiskelija. Tyhjät ryhmät jäävät kuitenkin pois vastauksesta, sillä ne karsiutuvat liitosoperaation johdosta vastausaineistosta jo ennen ryhmitystä (kuva 22). kurssi 1132 1133 1135 Pariton putoaa ilmoittautuminen 1132 1 A 1132 1 B 1132 2 C 1135 1 D 1135 1 E 1135 1 F Ryhmät näiden where-ehdon täyttävien perusteella Kuva 22: where- ehto ennen ryhmitystä. Tyhjät ryhmätkin tuottava kysely olisi esimerkiksi: select nimi, ryhmanro, count(*) from kurssi, ilmoittautuminen where ilmoittautuminen.kurssikoodi=kurssi.koodi group by nimi, ryhmanro union (select nimi, ryhmanro, 0 from kurssi, harjoitusryhma H, where koodi=h.kurssikoodi and koodi,h.ryhmanro) not in (select kurssikoodi, ryhmanro from ilmoittautuminen)); Myös ulkoliitosta voisi käyttää: 73

select nimi, h.ryhmanro, count(distinct opisknro) from kurssi, harjoitusryhma h left outer join ilmoittautuminen i on h.kurssikoodi= i.kurssikoodi and h.ryhmanro=i.ryhmanro where kurssi.koodi= h.kurssikoodi group by nimi, h.ryhmanro Huomaa, että edellä ei voi käyttää funktiota COUNT(*), sillä tyhjistäkin ryhmistä tulee nyt yksi rivi vastausaineistoon. Ryhmittelyyn perustuvissa kyselyissä voidaan tulokseen ottaa mukaan vain group by osassa lueteltuja ryhmittelyssä käytettyjä sarakkeita sekä yhteenvetofunktioihin perustuvia lausekkeita. Jos esimerkiksi haluttaisiin kursseista tietoina koodi, nimi, luennoijan nimi ja ryhmien lukumäärä, täytyisi kysely esittää muodossa select koodi, kurssi.nimi, opettaja.nimi, count(*) ryhmia from kurssi, opettaja, harjoitusryhma where kurssi.koodi=harjoitusryhma.kurssikoodi and kurssi.luennoija=opettaja.opetunnus group by koodi, kurssi.nimi, opettaja.nimi; Ryhmät muodostuisivat täysin samoiksi vaikka ryhmittelyyn käytettäisiin vain kurssitaulun saraketta koodi. Kurssin nimi ja opettajan nimi on kuitenkin liitettävä group by osaan, jotta ne saataisiin mukaan tulokseen. Niiden poisjättäminen group by osasta johtaisi ylläolevan kyselyn kohdalla käännösaikaiseen virheilmoitukseen. Ryhmitetystä aineistosta tuotetaan vastaukseen yksi rivi jokaista ryhmää kohden. Ryhmien mukaanottamista voidaan kuitenkin säädellä liittämällä kyselyyn ryhmiä koskeva valintaehto. Tämä esitetään määreellä having <valintaehdot> Valintaehdot ovat rakenteeltaan samanlaisia kuin kyselyn where-osassa annettavat valintaehdot. Yleensä ehdot kuitenkin perustuvat yhteenvetofunktioiden arvoihin. 74

Esimerkki: Ryhmät, joihin on ilmoittautunut yli 20 opiskelijaa select nimi, ryhmanro, count(*) from kurssi, ilmoittautuminen where ilmoittautuminen.kurssikoodi=kurssi.koodi group by nimi, ryhmanro having count(*) >20; Käytännössä ryhmien muodostaminen tarkoittaa vastausaineiston järjestämistä ryhmityssarakkeiden perusteella. Vastauksen varsinaisen järjestyksen määrittelee kuitenkin order by määre. Esimerkki: Harjoitusryhmät, ilmoittautujalukumäärän mukaan laskevassa järjestyksessä. select nimi, ryhmanro, count(*) from kurssi, ilmoittautuminen where ilmoittautuminen.kurssikoodi=kurssi.koodi group by nimi, ryhmanro order by count(*) desc; Seuraavassa esimerkissä tarkastellaan kohtuullisen monimutkaista ryhmittelyyn perustuvaa kyselyä. Tällaisten kyselyjen yhteydessä kannattaisi jo harkita myöhemmin esiteltävien näkymien käyttöä helpottamaan kyselyn muodostusta. Esimerkki: Millä kurssilla on suurin keskimääräinen ryhmäkoko. Ensimmäinen yritys: select nimi, H.ryhmanro, max(avg(count(*))) from kurssi, harjoitusryhma H, ilmoittautuminen I where koodi=h.kurssikoodi and H.kurssikoodi=I.kurssikoodi and H.ryhmanro=I.ryhmanro group by nimi, H.ryhmanro 75

Yritys johtaa virheilmoitukseen, sillä yhteenvetofunktio ei voi olla toisen argumenttina. Lähdetään etsimään oikeaa kyselyä sen osasista. Seuraavassa ei huomioida tyhjiä ryhmiä. Ryhmään ilmoittautuneiden lukumäärät ei-tyhjien ryhmien osalta saadaan kyselyllä: select kurssikoodi,ryhmanro, count(*) lkm from ilmoittautuminen group by kurssikoodi, ryhmanro Oletetaan, että tämän suoritus tuottaa tuloksen KURSSIKOODI RYHMANRO LKM 1134 1 6 1134 2 4 1134 3 3 1135 1 6 1135 2 5 1135 3 2 1136 1 6 1137 1 5 Kysely voidaan ottaa alikyselyksi kyselyyn, joka laskee kullekin kurssille keskimääräisen ryhmäkoon: select koodi, nimi, avg(lkm) from kurssi, (select kurssikoodi,ryhmanro, count(*) lkm from ilmoittautuminen group by kurssikoodi, ryhmanro) A where koodi= A.kurssikoodi group by koodi, nimi; Suoritettuna samassa kannassa kuin edellinen kysely tämä tuottaisi tuloksen 76

KOODI NIMI AVG(LKM) 1134 Informaatiojärjestelmät 4,33333333 1135 Java ohjelmointi 4,33333333 1136 Oliotietokannat 6 1137 Tietorakenteet 5 Maksimi saadaan selville having-ehdolla, jolloin koko kysely olisi: select koodi, nimi, avg(lkm) from kurssi, (select kurssikoodi,ryhmanro,count(*) lkm from ilmoittautuminen group by kurssikoodi, ryhmanro) A where koodi= A.kurssikoodi group by koodi, nimi having avg(lkm)>= ALL (select avg(lkm) from kurssi, (select kurssikoodi,ryhmanro,count(*) lkm from ilmoittautuminen group by kurssikoodi, ryhmanro) A where koodi= A.kurssikoodi group by koodi) Having ehdon kyselyssä on tulokseen otettu vain yheenvetofunktion arvo ilman ryhmittelysaraketta. Alikyselyn tulos tässä tapauksessa on AVG(LKM) 4,33333333 4,33333333 6 5 77

Käytettäessä yhteenvetofunktioita yhdeksi merkittäväksi ongelmaksi muodostuu tuloksen oikeellisuuden todentaminen. Tulokseksi saadaan vain yksi rivi tai joukko ryhmäkohtaisia rivejä, jotka sisältävät yhteenvedon tuloksen. Miten voimme tietää onko tuo tulos oikein? Onko se laskettu oikean rivijoukon yli? Ovatko kyselyyn liittyvät ehdot oikein?. Tyypillinen valintaehdon puuttuminen johtaa siihen, että jokin luku tulee laskentaan mukaan moneen kertaan. Tällöin summa moninkertaistuu ja keskiarvo lasketaan painottuneesta joukosta. Etenkin, jos yritämme saada tietokannasta selville jotain uutta informaatiota tunnusluvulla, jonka oikeasta suuruusluokastakaan ei ole varmaa etukäteistietoa tilanne on ongelmallinen. Tunnuslukuun uskotaan ja sitä käytetään päätöksenteossa. Jonkin puuttuvan tai virheellisen valintaehdon perusteella voimme tuottaa merkittävästi virheellisen tunnusluvun. Tällä voi olla sitten päätöksenteossa suurikin merkitys. Tunnuslukuja tuottavien kyselyiden testaukseen tulisikin kiinnittää erityishuomiota. Testaamisessa pitää tuottaa apu- ja välituloksia, joilla varmistutaan kokonaistuloksen oikeellisuudesta. Suuren tietokannan kohdalla tällaiset apu- ja välitulokset voivat olla suuria, mutta niiden kohdalla virhe voi ilman aputuloksia jäädä kokonaan huomaamatta. Esimerkiksi vastausjoukon koko voi joissain tilanteissa olla riittävä aputulos. Joissain toisissa se ei riitä vaan lisäksi tarvitaan muita kyselyjä selvittämään millainen vastausjoukon tulisi olla. 4.3.4.11 Näkymät Näkymällä (view) tarkoitetaan kyselyn avulla määriteltyä johdettua taulua (derived table). Tällaista taulua voidaan käyttää kyselyissä kuten perustaulujakin. Myös näkymiin kohdistuvat ylläpito-operaatiot voivat olla rajoitetusti mahdollisia. Näkymä määritellään create view lauseella, joka on muotoa: create view <taulunimi> [(<sarakke1>, )] as <kysely>; Taulunimi nimeää näkymään liitetyn kyselyn tulostaulun. Määrittelyyn liittyvässä sarakeluettelossa sarakkeille voidaan antaa nimet. Jos sarakeluettelo puuttuu, käytetään sarakkeista kyselyn tuottamia nimiä. Kysely määrittelee taulun sisällön. 78

Esimerkki: Tyhjät harjoitusryhmät Create view tyhja_ryhma (kurssikoodi, nimi, ryhmanro) as select kurssikoodi, nimi, ryhmanro from kurssi, harjoitusryhma where koodi= kurssikoodi and (kurssikoodi, ryhmanro) not in (select kurssikoodi, ryhmanro from ilmoittautuminen) Näkymätaulun rivejä ei tallenneta minnekään. 1 Kyselyn kohdistuessa näkymätauluun kyselyn käsittelijä limittää kyselyn ja näkymämäärittelyssä olevan kyselyn ja suorittaa näin aikaansaadun yhdistetyn kyselyn. Esimerkiksi kysely select * from tyhja_ryhma; aiheuttaisi näkymän määrittelevän kyselyn suorituksen Näkymien käyttöön on kolme perussyytä: tietoriippumattomuuden aikaansaaminen, täsmäsuojauksen mahdollistaminen ja kyselyjen helpottaminen. Tietoriippumattomuudella tarkoitetaan sitä, että ohjelmaa ei tarvitse muuttaa, jos tietokantaa muutetaan. Hyvin laaditut kyselyt (ei select * muotoisia ) takaavat jo jonkin verran tietoriippumattomuutta. Jos ohjelma käsittelee kantaa näkymien kautta voidaan tietoriippumattomuutta yhä lisätä. Tietokantaan voidaan tehdä laajojakin muutoksia, esimerkiksi pilkkoa taulu useaksi tauluksi ja silti tarjota käyttäjälle samanlainen näkymä. Muutokset edellyttävät vain näkymän määrittelevän kyselyn muuttamista tuottamaan samanlainen taulu muuttuneen kannan pohjalta. Oletetaan, että tauluun ilmoittautuminen liittyisi näkymä ilmo, joka olisi määritelty seuraavasti: 1 Joissakin järjestelmissä on tarjolla näkymän kaltaisesti määriteltävä vedos (snapshot), joka muodostetaan kyselyllä mutta jonka rivit tallennetaan kantaan. Toisin kuin näkymä vedos ei muutu perustaulujen muuttuessa. 79

create view ilmo as select kurssikoodi, ryhmanro, opisknro, ilm_aika from ilmoittautuminen; Tämä on rakenteeltaan ja sisällöltään identtinen taulun ilmoittautuminen kanssa. Kun tauluun ilmoittautuminen on kertynyt ilmoittautumisia pitkältä ajanjaksolta runsaasti taulun käyttö alkaa hidastua. Tällöin voidaan päätyä ratkaisuun, jossa passiiviset vanhat ilmoittautumiset siirrettäisiin historiatauluun vanhat_ilmoittautumiset. Erilaiset tilastot edellyttävät kuitenkin sekä nykyisiä että vanhoja ilmoittautumisia. Jos tilastointi olisi perustettu näkymään ilmo, toimisivat tilastot edelleen kun näkymän määrittely muutettaisiin muotoon create view ilmo as select kurssikoodi, ryhmanro, opisknro, ilm_aika from ilmoittautuminen union (select kurssikoodi, ryhmanro, opisknro, ilm_aika from vanhat_ilmoittautumiset); Näkymien käyttö suojauksessa perustuu siihen, että käyttäjille annetaan käyttöoikeuksia näkymiin. Näin voidaan rajata käyttöoikeus vain joihinkin taulun riveihin tai vain yhteenvetotietoihin. Esimerkiksi kuka tahansa voisi saada tiedon kurssien opiskelijamääristä, mutta ei tietoa kurssien yksittäisistä opiskelijoista. Roolille javaopettaja voitaisiin antaa kaikki oikeudet näkymään create view javailmoittautuminen as select kurssikoodi, ryhmanro, opisknro, ilm_aika from ilmoittautuminen where kurssikoodi in (select koodi from kurssi 80

where nimi= Java ohjelmointi ) 2 Seuraava näkymämäärittely perustuu oletukseen, että opettaja-taulussa käytetään opettajatunnuksena (opetunnus) opettajan tietokantakäyttäjätunnusta. Funktio user antaa kyselyn suorittajan tietokantakäyttäjätunnuksen. create view oma_kurssi as select * from kurssi where luennoija=user; Näkymän käyttöoikeus voitaisiin tehdä julkiseksi. Luennoijille taulussa näkyvät hänen luennoimiensa kurssien rivit. Muille käyttäjille taulu on tyhjä. Vaikka käyttöoikeus on julkinen eivät muut kuin kurssin luennoija pääse tämän näkymän kautta kurssitietoihin. Näkymää voidaan käyttää myös kyselyjen yksinkertaistamiseen. Monimutkainen kysely upotetaan näkymämäärittelyyn ja käyttäjä muodostaa omat kyselynsä yksinkertaisina näkymätauluihin perustuvina kyselyinä. Edellä määriteltyä näkymää tyhjä_ryhma voisi käyttää näkymän ryhmakoko määrittelyssä: create view ryhmäkoko (koodi, nimi, ryhma, lkm) as select nimi, ryhmanro, count(*) from kurssi, ilmoittautuminen where ilmoittautuminen.kurssikoodi=kurssi.koodi group by nimi, ryhmanro union (select kurssikoodi, nimi, ryhmanro, 0 from tyhja_ryhma); Tätä käyttämällä sen kurssin selvittäminen, jonka keskimääräinen ryhmäkoko on suurin sujuisi seuraavasti: select koodi, nimi, avg(lkm) from ryhmakoko 2 Jos näkymää käytetään usin saattaisi olla parempi määritellä se kurssikoodin eikä kurssin nimeen perustuvana. 81

group by koodi, nimi having avg(lkm)>= ALL (select avg(lkm) from ryhmakoko group by koodi) 4.3.4.12 Tietokannan ylläpito SQL sisältää myös tiedon muokkausvälineet tietokannan ylläpitoa varten. Välineistö muodostuu lauseista: insert rivien lisäys, update rivien muutos ja delete rivien poisto. Lisäykset Insert-lauseella on kaksi muotoa. Toisella lisätään yksittäisiä vakioarvoista koostuvia rivejä, toisella usean rivin joukko. Yksittäisen rivin lisäävän insert-lauseen rakenne on insert into <taulu> [(<sarakenimet>)] values (<arvot>) Sarakenimet on luettelo sarakenimiä. Se on valinnainen ja sitä tarvitaan tilanteissa, joissa annetaan arvot vain osalle lisättävän rivin sarakkeista. Arvot osassa annetaan lisättävän rivin sarakkeiden arvot pilkulla eroteltuina. Ellei lauseeseen sisälly sarakenimiosaa, on sarakkeiden arvot annettava siinä järjestyksessä, missä sarakkeet esiintyvät taulussa. Jos lauseeseen sisältyy sarakenimiosa, se määrää annettavien arvojen järjestyksen. Esimerkki: Tarkastellaan taulua kurssi, joka on määritelty seuraavasti: CREATE TABLE kurssi ( koodi numeric(8) NOT NULL, nimi varchar(40) NOT NULL, opintoviikot numeric(5,1), luennoija varchar(12), PRIMARY KEY (koodi ), FOREIGN KEY (luennoija) REFERENCES opettaja); 82

Lause insert into kurssi values (1234, Tietokantojen perusteet,2, HLAINE ); lisää rivin tauluun kurssi. Rivin kaikille sarakkeille on annettu arvot. Jos kurssia lisättäessä ei vielä olisi tiedossa kurssin luennoijaa, voitaisiin lisäys tehdä lauseella insert into kurssi values (1234, Tietokantojen perusteet,2,null); tai käyttämällä sarakeluetteloa insert into kurssi (koodi, nimi, opintoviikot) (1234, Tietokantojen perusteet,2); Niille sarakkeille, joille ei anneta arvoa lisäyslauseessa, tulee arvoksi oletusarvo. Ellei tätä ole sarakkeelle määritelty create table lauseen default-määreellä, käytetään oletusarvona tyhjäarvoa. Edellisen esimerkin kaksi viimeistä insert-lausetta lisäävät siis samanlaisen rivin. Jos oletusarvoa ei ole määritelty ja puuttuvaan sarakkeeseen on liitetty taulumäärittelyssä määre NOT NULL, lisäys epäonnistuu. Samoin käy jos lisäys rikkoo muita eheysehtoja, esimerkiksi avaimen yksikäsitteisyyttä tai viiteeheyttä. Lisäyslauseen toisen vaihtoehdon avulla voidaan tauluun lisätä kyselyn tulosrivit. Lauseen muoto on seuraava: insert into <taulu> [(<sarakenimet>)] <kysely>. Tällä lauseella voidaan samoin kuin yksittäistenkin rivien tapauksessa lisätä sekä täydellisiä rivejä että rivejä joista puuttuu joitain sarakkeita. Kyselyn tulosrivit lisäävä lisäysoperaatio on hyödyllinen esimerkiksi kopioitaessa taulu, siirrettäessä tietoja aktiivisesta taulusta historiatauluihin, perustettaessa testiaineistoja ja suoritettaessa tietokannan uudelleenorganisointia. Esimerkki: Kurssin lisääminen kun ei tiedetä luennoijan tunnusta, mutta tiedetään nimi. 83

insert into kurssi select 1234, Tietokantojen perusteet, 2, opetunnus from opettaja where nimi = Laine Harri ; Tässä kyselyssä vain opetunnus tulee taulusta opettaja. Muut vastausrivin tiedot ovat vakioita. Esimerkki: Kurssin Ohjelmoinnin perusteet opiskelijoiden siirto kurssille Java ohjelmointi. Insert into ilmoittautuminen select java.kurssikoodi, ryhmanro, opisknro, sysdate 3 from kurssi java, kurssi ohpe, ilmoittautuminen where java.nimi= Java-ohjelmointi and ohpe.nimi= Ohjelmoinnin perusteet and ohpe.koodi=ilmoittautuminen.kurssikoodi; Rivien muutokset Rivien sisältöä muutetaan update-lauseella. Update lauseen rakenne on update <taulu> set <sijoitus>[, <sijoitus> ] [where <valintaehdot>] missä sijoitus on muotoa <sarake>=<lauseke> Lauseessa oleva valintaehto määrittelee muutoksen kohteena olevat rivi. Muutos tehdään kaikkiin niihin riveihin, joiden kohdalla valintaehto on tosi. Jos ehto puuttuu tehdään muutos kaikkiin taulun riveihin. Muutosta ei hyväksytä, jos se aiheuttaa eheysehdon rikkoutumisen. Esimerkki: Kurssin Java-ohjelmointi opintoviikkomäärä kasvatetaan yhdellä. 3 sysdate on Oraclen mukainen nykyhetken antava funktio. 84

update kurssi set opintoviikot= opintoviikot+1 where nimi= Java-ohjelmointi ; Esimerkki: Kaikki 4 opintoviikon kurssit jaetaan kahdeksi 2 opintoviikon kurssiksi (osa 1 ja osa 2). Kurssikoodiin ja nimeen lisätään osan tunnus. Tehdään aluksi uusi puolikaskurssi insert into kurssi select kurssikoodi*10+1, nimi, osa 1,2, luennoija) from kurssi where opintoviikot=4; ja muutetaan sitten vanha kurssi toiseksi puolikkaaksi update kurssi set kurssikoodi=kurssikoodi*10+2, nimi= nimi, osa 2 opintoviikot=2 where opintoviikot=4; Rivien poisto Poistolauseella delete from <taulu> [where <valintaehto>] poistetaan taulusta kaikki valintaehdon täyttävät rivit. Jos valintaehto puuttuu poistetaan kaikki taulun rivit. Poistot, jotka rikkovat eheysehtoja epäonnistuvat. Esimerkki Poistetaan harjoitusryhmät, joihin ei ole ilmoittautunut ketään. delete from harjoitusryhma where (kurssikoodi, ryhmanro) not in (select kurssikoodi, ryhmanro from ilmoittautuminen); 85

Jos ylläpito-operaatioita suoritetaan sarjassa useita on pidettävä huolta siitä, että ne suoritetaan oikeassa järjestyksessä tai niissä olevat valintaehdot ovat sellaiset, ettei synny odottamattomia sivuvaikutuksia sen takia, että sarjassa aiemmin ollut operaatio muuttaa sarjassa myöhemmin olevan operaation kohdejoukkoa joksikin muuksi kuin on ajateltu.. Esimerkki: Tarkastellaan taulua bonustili(asiakasnimi,..., saldo,...) Olkoon siellä rivit asiakasnimi saldo Roope Ankka 3100 Aku Ankka 2000 Ines Ankka 2700 Hannu Hanhi 2900 Tilille pitäisi antaa bonus siten, että saldoa korotetaan 10% jos saldo on 3000 tai alle ja 15% jos saldo on yli 3000. Jos bonuksen maksu hoidetaan seuraavasti update bonustili set saldo=1.1*saldo where saldo<=3000; update bonustili set saldo=1.15*saldo where saldo>3000; olisi tulos asiakasnimi saldo Roope Ankka 3565 Aku Ankka 2200 86

Ines Ankka 2970 Hannu Hanhi 3668 ja Hannu Hanhella olisi taas käynyt tuuri. Ongelma suoritusjärjestyksessä on se, että ensimmäinen päivitys voi muuttaa rivejä siten, että toisen päivityksen kohdejoukko muuttuu. Suoritettaessa muutokset oikeassa järjestyksessä update bonustili set saldo=1.15*saldo where saldo>3000; update bonustili set saldo=1.1*saldo where saldo<=3000; olisi tulos asiakasnimi saldo Roope Ankka 3565 Aku Ankka 2200 Ines Ankka 2970 Hannu Hanhi 3190 Rivien siirto ja kopiointi Joissakin tietokannanhallintajärjestelmissä on tarjolla mahdollisuus tallentaa kyselyn tulostaulu suoraan oikeaksi tauluksi create table tai create snapshot -komennoilla Esimerkiksi Oraclessa taulu voitaisiin luoda seuraavasti: create table ilmotilanne_09_20 as select * from ilmoittautuminen; Jos tämä suoritettaisiin 20.9. se vastaisi nimeään ja sisältäisi kaikki luontihetkellään taulussa ilmoittautuminen olleet ilmoittautumiset. Ellei tietokannanhallintajärjestelmä tarjoa tällaisia mahdollisuuksia, on kopioinnit suoritettava insert-lauseiden avulla. 87

Rivien siirtäminen taulusta toiseen edellyttää sekä insert- että delete-lauseiden käyttöä. Esimerkki: Vanhojen ilmoittautumisten siirto historiatauluun. insert into ilmohistoria select * from ilmoittautumiset where ilm_aika< 1.1.1999 ; delete from ilmoittautumiset where ilm_aika< 1.1.1999 ; Näkymiin perustuva ylläpito Joissakin järjestelmissä on mahdollista kohdistaa ylläpito-operaatio myös näkymään. Kaikki näkymät eivät kuitenkaan tule kyseeseen ylläpidon kohteina. Ylläpito on mahdollista vain, jos jokaiselle näkymän riville löytyy yksikäsitteinen vastinrivi näkymän perustaulusta ja muutos näkymäriviin on yksikäsitteisesti muunnettavaksi muutokseksi sen vastinriviin. Yhteenvetotietoja tarjoavat näkymät eivät ole tällaisia. Eivät myöskään näkymät joiden sarakkeet on määritelty lausekkeiden avulla. Yleensä edellytetään, että kaikki ylläpidettävän näkymän rivit kuuluvat samaan tauluun ja taulun pääavain on mukana näkymässä. Lisäksi voidaan vaaditaan, että näkymän määrittely sisältää pelkästään valintarajoituksia. Aiemmin esilläolleista näkymistä oma_kurssi täyttää nämä vaatimukset samoin ilman union-operaatiota määritelty ilmo. 4.3.4.13 Tietokantatapahtumat Usein joudutaan jonkin tietokantamuutoksen aikaansaamiseksi suorittamaan useita tietokantaoperaatioita. Esimerkiksi haluttaessa suorittaa tilisiirto halutaan ottaa rahaa yhdeltä tililtä ja siirtää ne toiselle. On oleellista että kumpikin operaatio suoritetaan. 88

Esimerkki: Tarkastellaan taulua: tili(tilinumero,,saldo, ) update tili set saldo=saldo-500 where tilinumero=123456; update tili set saldo=saldo+500 where tilinumero=654321; siirtää 500 markkaa tililtä 123456 tilille 654321. On oleellista, että ei tehdä vain tililtäottoa tai tilillepanoa, vaan molemmat. On myös oleellista, että mikään ei ulkopuolinen operaatio, vaikka pankin vastuiden laskentaohjelma, ei pääse näkemään tilien saldoja siirto-operaation ollessa kesken. Tietokantatransaktiolla (tapahtumalla) tarkoitetaan yhtenä jakamattomana kokonaisuutena pidettävää tietokantaoperaatioiden joukkoa. Edellä mainittu tilisiirto on tällainen. Tietokannanhallintajärjestelmän tulisi taata, että transaktio suoritetaan joko kokonaan tai sitä ei suoriteta lainkaan, ulkopuoliset näkevät vain kokonaisuudessaan suoritettujen transaktioiden lopputulokset. Tietokantatransaktion tietokantaan tekemät muutokset ovat peruttavissa siihen asti kunnes järjestelmä sitoutuu transaktioon. Käyttäjä voi luottaa siihen, että tietokantamuutokset, joita on tehty sellaisen transaktion kuluessa, johon järjestelmä on sitoutunut, eivät peruunnu. Käyttäjän kannalta järjestelmän sitoutuminen transaktioon tarkoittaa sen onnistunutta loppuunvientiä. SQL:ssä transaktion päättyminen ilmaistaan commit-komennolla, jonka muoto on commit [work]. Commit-komento ilmoittaa että transaktio päättyy ja esittää järjestelmälle pyynnön sitoutua viemään transaktio päätökseen. Samalla se on ilmoitus siitä, että mahdolliset jatkossa tulevat operaatiot kuuluvat eri transaktioon. Transaktioon kuuluvat siis kahden commit-komennon välissä annetut operaatiot (kuva 23). Käyttäjä voi lopettaa transaktion myös antamalla perumispyynnön rollback [work] Perumispyyntö aikaansaa kaikkien edellisen commit:in jälkeen tehtyjen muutosten perumisen. 89

COMMIT Operaatio 1 Operaatio n transaktio COMMIT Kuva 23: Transaktio Esimerkki: Yritetään poistaa tyhjät harjoitusryhmät, oletetaan, että ilmoittautumisten viiteavaimeen liittyy on delete cascade -määre commit; select count(*) from harjoitusryhma; >> 150 << select count(*) from ilmoittautuminen; >> 3500 << delete from harjoitusryhma where ryhmanro is not null; select count(*) from ilmoittautuminen; >>0<< (:-?) select count(*) from harjoitusryhma; >>0<< =(:-o) OHO! rollback; select count(*) from ilmoittautuminen; >>3500<< Tietokantatransaktiot pitäisi tehdä lyhytkestoisiksi ja vähän resursseja varaaviksi. Esimerkiksi varautuminen peruutukseen vie resursseja koska muutosta edeltänyttä tilaa on säilytettävä commit-operaation asti. 90

4.3.5 Tietokannan käyttö ohjelmasta SQL-pohjaiset relaatiotietokannat tarjoavat käyttäjille yleensä jonkinlaisen suorakäyttöliittymän, jonka kautta käyttäjä voi antaa SQL operaatioita. Suorakäyttö soveltuu hyvin satunnaisiin kyselyihin, erilaisiin ennakoimattomiin nopeaa ratkaisua vaativiin tilanteisiin, yksinkertaisiin ylläpito tehtäviin, kannanhallintatehtäviin ja yksinkertaiseen raportointiin. SQL on kielenä varsin suppea ja sen toiminnallisuus keskittyy tietokantaoperaatioihin. Se ei sisällä raporttien muotoiluun tarvittavia käskyjä. Se ei sisällä tavanomaisten ohjelmointikielten kontrollirakenteita. Vaikka sen eräänä tavoitteena on ollut helppokäyttöisyys, se on kuitenkin suhteellisen vaikea käyttää muihin kuin aivan yksinkertaisiin kyselyihin. Lisäksi se edellyttää käyttäjältä perehtymistä tietokannan teknisiin rakenteisiin kuten taulu- ja sarakenimiin. Tästä syystä sitä ei voi pitää loppukäyttäjän vaan pikemminkin atk-ammattilaisen työvälineenä. Markkinoilla on tarjolla SQL:ää helppokäyttöisempiä nimenomaan loppukäyttäjille suunnattuja välineitä kyselyjen laadintaan. Useimmat näistä perustuvat Query-by- Example -malliin. Qyery-by-Example (QBE) kehitettiin myös IBM:n tutkimuslaboratoriossa 70-luvun lopulla. 4 Perusideana kielessä on laatia kysely käyttäen hyväksi graafisia taulupohjia, Näihin kirjataan maskien avulla tulostusvalinnat, valintaehdot ja taulujen väliset kytkennät. Tämä kyselymalli on pohjana mm. Paradox:issa, Delphissä, ja Microsoft Access:issa. Kuvassa 24 on pieni esimerkki QBE-tyylisestä kyselystä.. QBE on SQL:ää helppokäyttöisempi satunnaisiin yksinkertaisiin raportointitarpeisiin. Monimutkaiset yhteenvetokyselyt ovat silläkin hankalia eikä QBE-tyyppisten kielten ilmaisuvoima näissä usein edes yllä SQL:n tasolle. 4 Zloof: Query by Example, National Computinc Conference, AFIPS,44, 1975 91

Kurssi Koodi Nimi Opintoviikot Luennoija Java-ohjelmointi _kuka Opettaja Opetunnus Nimi Puhelin Tyohuone _kuka <Print> Kuva 24: QBE- kysely : Java-ohjelmointi kurssin luennoijan nimi. Pääasiallisesti tietokantaa käytetään kuitenkin sovellusohjelmien kautta. Nämä tarjoavat käyttäjilleen paremman käyttöliittymän, paremmin muotoillut raportit, tarpeiden mukaan kehitetyt palvelut, jotka eivät edellytä käyttäjiltä tietokannan rakenteen yksityiskohtaista tuntemista. Sovellusohjelmat laaditaan jollakin ohjelmointikielellä. Yleiskäyttöiset ohjelmointikielet kuten Ada, C, C++, Cobol, Pascal, jne. eivät kuitenkaan peruskokoonpanossaan tarjoa mitään välineistöä tietokannan käsittelyyn. Sovellusten toteutusvälinevaihtoehdoiksi muodostuvat tällöin joko erityisen tietokantaohjelmointikielen käyttö tai yleisohjelmointikielen täydentäminen tietokantakäytön mahdollistavilla lisämoduuleilla. Yleisohjelmointikielten laajentamiseen on tarjolla kaksi tekniikkaa sulautettu SQL ja tietokantaliittymäkirjaston käyttö (database application programming interface, database API). 4.3.5.1 Sulautettu SQL Sulautetussa SQL:ssä SQL-lauseita voidaan kirjoittaa ohjelmointikielen lauseiden lomaan, esimerkiksi C-kielen lauseiden joukkoon. Jotta SQL-lauseet kyettäisiin erottamaan varsinaisista ohjelmointikielen lauseista (isäntäkielen lauseista), ne merkitään kuuluviksi SQL:ään. Standardin mukaan tämä tapahtuu siten, että SQL- 92

osuutta edeltää aina EXEC SQL alkumerkki. SQL-osuuden loppumerkki on kielikohtainen. Ohjelmointikielen normaali kääntäjä, esimerkiksi C-kääntäjä ei osaa käsitellä ohjelmaan upotettuja SQL-lauseita. Niiden käsittelyyn tarvitaan esikääntäjä. Esikääntäjä prosessoi ohjelman ja muokkaa sen sellaiseen muotoon, että se voidaan edelleen kääntää konekielelle ohjelmointikielen normaalikääntäjällä. Käytännössä tämä tarkoittaa sitä, että kieleen upotetut SQL-lauseet korvataan tietokantaliittymän toteuttavan ohjelmakirjaston funktioiden kutsuilla. Näissä kutsuissa kyselyt ja muut tietokantaoperaatiopyynnöt on muunnettu funktioiden parametreiksi. Liittymäkirjastojen funktiot ovat tietokannanhallintajärjestelmäkohtaisia samoin kuin esikääntäjätkin. Esimerkiksi Oraclen C-esikääntäjä ProC tuottaa tuloksenaan C-kielisen ohjelman, joka käyttää Oracle Call Level Interface kirjastoa. Tapa, jolla SQL-lauseet upotetaan isäntäkieleen on kuitenkin standardoitu. Joten periaatteessa on mahdollista vaihtaa tietokannanhallintajärjestelmää kääntämällä ohjelma uudelleen uuden järjestelmän esikääntäjällä. Alla on esimerkki Pascal funktiosta, johon on upotettu SQL-lauseita. function keskipalkka(dept:integer):real; var integer n; integer psumma; #include SQLCA.INC /* sql:n käyttämiä tietorakenteita*/ EXEC SQL BEGIN DECLARE SECTION var palkka: integer; os: integer; EXEC SQL END DECLARE SECTION begin EXEC SQL DECLARE pal CURSOR FOR SELECT salary from employee where department= :os; n:=0; psumma:=0; os:= dept; EXEC SQL open pal; EXEC SQL fetch pal into :palkka; while sqlcode = 0 do begin psumma := psumma + palkka; n := n + 1; EXEC SQL fetch pal into :palkka; end; EXEC SQL close pal; if n > 0 then keskipalkka := psumma/n else keskipalkka := 0; end; 93

4.3.5.2 Tietokannan käyttö liittymäkirjaston avulla Tietokannanhallintajärjestelmän liittymäkirjasto muodostuu joukosta funktioita. Näitä käyttämällä ohjelmoija voi kohdistaa operaatioita tietokantaan. Liittymäkirjasto on sama kirjasto, jonka tuntijoiden kutsuiksi esikääntäjä kääntää ohjelmaan upotetut SQL-lauseet. Kuten edellä jo todettiin jokaisella tietokannanhallintajärjestelmällä on oma liittymäkirjastonsa. Järjestelmäkohtaisten kirjastojen lisäksi on olemassa myös korkeamman tason abstraktioon perustuvia liittymäkirjastoja. Tällaisia ovat esimerkiksi ODBC (Microsoft Open Database Connection) ja JDBC (for Java) kirjastot. Nämä kirjastot tarjoavat tietokannanhallintajärjestelmästä riippumattoman ohjelmointirajapinnan tietokantapalvelujen käyttöön. Tietyn tietokannanhallintajärjestelmän käyttö ODBC tai JDBC rajapinnan kautta edellyttää kuitenkin tietonnanhallintajärjestelmäkohtaisen ajurin (driver) hankintaa. Esimerkiksi Oracle-kannan käyttäminen edellyttää Oracleajuria. Ajurin tehtävänä on muuntaa abstraktin kirjastorajapinnan kautta tapahtuvat kutsut tietokannanhallintajärjestelmän käyttämään muotoon. Esimerkkinä liittymäkirjastoihin perustuvasta tietokantaohjelmoinnista tarkastellaan seuraavassa tietokantasovelluksen toteutusta JDBC-kirjaston avulla. JDBC on Java ohjelmointikieleen liittyvä tietokantaohjelmointirajapinta. Käytöltään se on samankaltainen ODBC-rajapinnan kanssa. Kun ODBC muodostuu joukosta C-tyyppisiä funktioita, muodostuu JDBC Java-luokista ja niiden tarjoamista palveluista. Luokkarakenteen ansiosta rajapinta vaikuttaa ODBC:tä selkeämmältä ja jäsentyneemmältä. 94

JDBC:n perusteet 5 JDBC on Java luokkakirjasto tietokannan käsittelyyn. Kirjaston keskeiset perusluokat ovat: DriverManager, Connection, Statement, PreparedStatement ja ResultSet. DriverManager Pystyäkseen käyttämään hyväksi tietyn tietokannanhallintajärjestelmän palveluita on ohjelman käytettävä järjestelmäkohtaista ajuria. Ajuri, jos sellainen on olemassa, on saatavissa tietokannanhallintajärjestelmän toimittajalta. Ilman ajuria tietokantaa ei pysty käyttämään. Myös ODBC:ssä täytyy kiinnittää käytettävä ajuri, mutta tämä tehdään yleensä ohjelman ulkopuolisen hallintatyökalun avulla. Tämä hoidetaan DriverManager luokan avulla Luokkapalvelulla registerdriver ohjelma kytketään käyttämään tiettyä tietokanta-ajuria. Ajurin käyttö tapahtuu JDBC-kirjaston sisäisesti eikä ohjelmoijan tarvitse kytkemisen jälkeen olla sen kanssa missään tekemisissä. Ajurin kytkemiseen käytetään DriverManager luokan luokkapalvelua registerdriver. Myös ODBC:ssä täytyy kiinnittää käytettävä ajuri, mutta tämä tehdään yleensä ohjelman ulkopuolisen hallintatyökalun avulla. 5 Hyvä esitys JDBC:stä löytyy lähteestä: Hamilton G., Cattell R, Fisher M.: JDBC Database Access with Java -A tutorial and Annodated Reference, Addison-Wesley, 1997 95

Esimerkki: Otetaan käyttöön Oracle-ajuri DriverManager.registerDriver((Driver)Class.forName ("oracle.jdbc.driver.oracledriver").newinstance()); Connection DriverManager tarjoaa myös palvelun yhteyden luomiseksi tietokantaan. Tietokantayhteydellä (database connection) hallitaan ohjelman ja tietokannan välistä vuorovaikutusta. Yhteyden olemassaolo on edellytys kaikkien tietokantaan kohdistuvien operaatioiden suoritukselle. Tietokannan suorakäytössä yhteys vastaa tietokantaistuntoa, joka alkaa käyttäjän sisäänkirjoittautumisesta ja päättyy istunnon lopetukseen. Samoin tietokantayhteys alkaa yhteyden perustamisella. JDBC:ssä yhteyden perustaminen tarkoittaa Connection-luokan olion luomista. Yhteys tulisi lopettaa kun sitä ei enää tarvita. Yhteyden lopettaminen vapauttaa siihen kytkettyjä resursseja sekä ohjelman itsensä että tietokantapalvelimen puolelta. JDBC:ssä yhteys suljetaan Connection-olion close-palvelulla. Yhteyden perustaminen vie jonkin verran aikaa ja haluttaessa nopeaa palvelua voidaan joskus jättää yhteyksiä roikkumaan, jotta niitä voitaisiin nopeasti ottaa uudelleen käyttöön. Ohjelmalla voi olla käytössään useita tietokantayhteyksiä jopa eri tietokantoihin. Samaan tietokantaan tarvitaan yleensä vain yksi yhteys. Esimerkki: Yhteyden perustaminen Oracle-kantaan. Connection con = DriverManager.getConnection( "jdbc:oracle:thin:@dbserver.helsinki.fi:1521:kanta1, "info", "expert"); Tässä luodaan Oraclen thin-ajuriin perustuva yhteys koneessa dbserver.helsinki.fi olevaan porttiin 1521 kytkettyyn tietokantaan, jonka tunnus on kanta1. 6 Yhteys perustuu käyttäjätunnukseen info, jonka salasana on expert. 6 dbserver.helsinki.fi nimistä tietokonetta ei ole oikeasti olemassa. 96

Statement Tietokantaan kohdistuvat operaatiot suoritetaan JDBC:ssä Statement-olioiden avulla. Statement-olio tarjoaa suoritusympäristön operaatioille. Se tarjoaa mm. palvelut executequery kyselyiden suoritukseen ja executeupdate tulostaulua tuottamattomien operaatioiden suoritukseen Tietokantaoperaatio suoritetaan jonkin tietokantayhteyden puitteissa. Tämä kytkentä on JDBC:ssä hoidettu siten, että Statement-olio luodaan käyttäen Connection-olion palvelua createstatement. Nimestään huolimatta Statement-olio ei sisällä suoritettavaa tietokantaoperaatiota vaan tämä annetaan operaation executequery tai executeupdate parametrina. Täten jokaista operaatiota varten ei tarvitse välttämättä luoda omaa Statement-oliota. Yhden Statement-olion puitteissa voi kuitenkin olla vain yksi aktiivinen operaatio kerrallaan. Operaatiota suoritettaessa havaitusta virhetilanteesta, esimerkiksi kyselyssä olevasta syntaksivirheestä, ilmoitetaan aiheuttamalla SQLException-tyyppinen poikkeus. Poikkeus pitää sisällään tilaindikaattorin SQLState, jonka arvo ilmaisee minkä tyyppisestä virheestä on kyse. Samoin poikkeuksen kautta saadaan tietoon tietokantaohjelmiston generoima virhekoodi ja virheilmoitus. Tietokantaoperaation yhteydessä pitää aina varautua SQLException poikkeukseen. ResultSet JDBC:ssä kyselyn vastausta käsitellään ResultSet olion palveluilla. Nämä mahdollistavat vastausjoukon rivien käsittelyn rivi kerrallaan. ResultSet tarjoaa myös palveluita metatiedon saamiseksi vastauksesta. Metatietoa ovat esimerkiksi vastauksen sarakkeiden lukumäärä, sarakkeiden nimet ja tietotyypit. ResultSet toteuttaa toimintoja, jotka sulautetussa SQL:ssä liittyvät käsitteeseen kursori (cursor). Vastausjoukko (ResultSet-olio) syntyy tuloksena Statement-olion palvelun executequery suorituksesta, siis kyselyn suorittamisesta. Suoritettava kysely annetaan palvelun parametrina. 97

Esimerkki Statement stmt= con.createstatement(); ResultSet rs= stmt.executequery( "select nimi, puhelin from opettaja"); Tässä esimerkissä luodaan yhteyden con avulla kyselyn suoritusympäristöksi Statement-olio stmt. Sitä käyttäen suoritetaan kysely, joka tulos on käsiteltävissä Resultset olion rs palvelujen avulla. Vastausta käsitellään rivi kerrallaan. Keskeinen palvelu on Boolen funktio next(). Se ottaa aktiiviseksi vastausriviksi vastausjoukossa seuraavana olevan rivin. Jos tällainen rivi on olemassa eli vastausjoukoa ei ole käsitelty loppuun next() palauttaa arvon true, muuten se palauttaa arvon false. Ensimmäisellä kutsukerralla se aktivoi vastausjoukon ensimmäisen rivin. Sulautetun SQL:n fetch-lause toimii samalla tavoin kursorin käsittelyssä. JDBC:n versiossa 1.0 ei ole mitään tapaa siirtyä vastausjoukossa taaksepäin, joten vastaus voidaan ohjelmassa käydä läpi vain kertaalleen alusta loppuun.. Versio 2.0 sisältää palveluita myös taaksepäin, sekä vastausjoukon alkuun ja loppuun siirtymiseksi. Aktiivisen rivin käsittelyyn ResultSet-oliot tarjoavat tietotyyppikohtaisia get-funktioita sarakkeen arvon saamiseksi ohjelman käyttöön. Vastausriviin sisältyvän merkkijonon sisältö saadaan selville funktiolla getstring, kokonaisluvun funktiolla getint, päiväyksen funktiolla getdate, jne. Näistä funktioista on käytettävissä kaksi muotoa, joista toinen hakee arvon sarakenimen perusteella ja toinen vastaussarakkeen järjestysnumeron perusteella. Esimerkki: String nimi = rs.getstring("nimi"); // sarakkeen nimi arvo String puh = rs.getstring(2); // toisen sarakkeen arvo get-funktiot osaavat jossakin määrin tehdä tietotyyppikonversioita. Esimerkiksi getstring-funktiolla voi hakea kaiken tyyppisiä tietoja. 98

SQL-kyselyt voivat tuottaa tyhjäarvoja sisältäviä vastauksia. get-funktiot käsittelevät näitä eri tavoin. Olioarvoiset funktiot kuten getstring palauttavat Javan nullosoittimen Tietotyyppiarvoiset funktiot sensijaan palauttavat, jonkin todellisen arvon. Esimerkiksi getint palauttaa arvon 0. ResultSet-oliolla on palvelu wasnull, jolla voi tarvittaessa tutkia oliko palautettu arvo tyhjäarvo. Kyselyn vastaus käsitellään vastaussilmukassa, jossa funktiolla next() haetaan vuoroon seuraava vastausrivi kunnes kaikki on käsitelty. Esimerkki: Puhelinluettelo (ei kovin tyylikäs layout) try { Statement stmt= con.createstatement(); ResultSet rs= stmt.executequery( "select nimi, puhelin from henkilo"+ " order by nimi"); while (rs.next()) { System.out.println(getString(1)+", " getstring(2)); } } catch (SQLException ex) { do something}; Operaatiot, jotka eivät tuota tulosrivejä suoritetaan funktiolla executeupdate; Esimerkki: stmt.executeupdate( update opettaja + set palkka= palkka +100000 + where opetunnus=\ HLAINE\ ); korjaa hieman opettajan palkkaa. PreparedStatement Tietokannan käsittelyssä tarvitaan usein kyselyitä, joissa rakenteeltaan samanlainen kysely suoritetaan toistuvasti, mutta esimerkiksi hakuehdossa vaihtuu vakioarvo. 99

Tällainen kysely voidaan parametroida niin, että tuo muuttuva vakio annetaan kyselyn parametrina. Parametroidut kyselyt valmistellaan käyttöä varten. Valmistelussa ne käännetään valmiiksi odottamaan suoritusta vaihtuvin parametriarvoin. JDBC tarjoaa parametroitujen kyselyjen käsittelyyn luokan PreparedStatement, joka on luokan Statement aliluokka. Parametroitu SQL-operaatio annetaan parametrina PreparedStatement-olion luontifunktiolle Connection.prepareStatement. Operaatiossa parametri ilmaistaan kysymysmerkillä (?) Esimerkki: PreparedStatement pst = con.preparestatement( "select nimi, puhelin "+ "from opettaja"+ "where nimi like?" ); Tällainen kysely voisi sisältyä vaikkapa puhelinnumeroiden hakujärjestelmään, jossa käyttäjä voi antaa toistuvasti hakukriteerejä, joiden perusteella puhelinnumeroa haetaan. Parametrina tässä on nimeen liittyvä maski. Ennen parametroidun kyselyn suoritusta on parametreille kiinnitettävä arvot. PreparedStatement tarjoaa tietotyyppikohtaiset set-funktiot parametriarvojen kiinnittämiseksi, esimerkiksi setint, setstring, setdate, jne. Set-funktioilla on kaksi parametria: parametrin järjestysnumero ja arvo joka parametrille annetaan. Esimerkiksi pst.setstring(1,"möttö%"); kiinnittää arvon 'Möttö%' parameroidun kyselyn pst ensimmäiselle parametrille, joten kysely suoritettaessa vastaa kyselyä select nimi, puhelin from opettaja where nimi like Möttö% Parametroitu operaatio suoritetaan käyttäen palvelua executequery tai executeupdate. Nyt vain näiden yhteydessä ei anneta mitään parametria. 100

Esimerkkiohjelma: // Esimerkkiohjelma Tietokantojen perusteet // CLASSPATH polkumäärittelyssä täytyy olla polku hakemistoon // josta tietokanta ajuri löytyy import java.sql.*; import java.io.*; public class Esimerkki { public static void main(string args[]) throws Exception { String url = jdbc:oracle:thin:@dbserver.helsinki.fi:1521:kanta1"; Connection con; String query = "select KOODI, KURSSI.NIMI AS KNI, OPINTOVIIKOT, "+ "OPETTAJA.NIMI AS OPNI " + "from KURSSI, OPETTAJA " + "where KURSSI.LUENNOIJA= OPETTAJA.OPETUNNUS " + "order by OPETTAJA.NIMI, KURSSI.NIMI"; Statement stmt; try { DriverManager.registerDriver((Driver)Class.forName ("oracle.jdbc.driver.oracledriver").newinstance()); } catch(java.lang.classnotfoundexception e) { // Ilmoitetaan siitä, ettei yhteyttä saada aikaan. System.err.print("ClassNotFoundException: "); System.err.println(e.getMessage()); } try { // Luodaan yhteys kurssin käyttäjätunnuksella con = DriverManager.getConnection( url, "info", "expert"); // Luodaan tietokantaoperaation suoritusympäristö stmt = con.createstatement(); // Muuttujaa ope käytetään pitämään kirjaa edellisestä // opettajasta. Tällä kontrolloidaan opettajan nimen // tulostusta. String ope =" "; // Tulostetaan otsake System.out.println("OPETUSTEHTÄVÄT:"); System.out.println(); // Suoritetaan kysely ResultSet rs = stmt.executequery(query); // Käydään läpi vastausrivit while (rs.next()) { // Eristetään opettajan nimi, huom. alias-nimen // käyttö String opn = rs.getstring("opni"); // Jos opettaja vaihtuu tulostetaan tyhjä rivi ja // opettajan nimi, muuten vain kurssin tiedot if (ope.equals(opn)) { System.out.println(" " + rs.getstring("kni")+" ("+rs.getint("koodi")+")"); } else { 101

} } System.out.println(); ope= opn; System.out.println(ope); System.out.println(" " + rs.getstring("kni")+" ("+rs.getint("koodi")+")"); } } //end-while // Suljetaan 'kursori' - vapautetan vastausjoukon // käsittelyyn kiinnitetyt resurssit. Tälle // vastausjoukolle ei enää voi tehdä mitään, vaikka //sitä suljettaisikaan. rs.close(); stmt.close(); // Suljetaan tietokantayhteys. Yhteys suljetaan vasta // siinä vaiheessa kun ohjelmassa ei enää käsitellä // kantaa. con.close(); } catch(sqlexception ex) { // Imoitetaan virheestä System.err.print("SQLException: "); System.err.println(ex.getMessage()); } 4.3.5.3 Tietokantaohjelmointikielet Tietokantaohjelmointikielissä tietokanta ja sen käsittelyyn tarvittavat rakenteet ovat peruskäsitteenä mukana kielessä. Nämä kielet tarjoavat normaalin ohjelmointikielen kontrollirakenteet ja lisäksi tietokantaliittymän sulautettua SQL:ää miellyttävämmin. Kielten pohjana on jokin yleisohjelmointikieli. Esimerkiksi tietokantaproseduurien laadintaan tarkoitetun standardikielen pohjana on Ada-ohjelmointikieli. Tietokantaohjelmointikieli esiintyy usein osana sovelluskehitintä. (application generator). Esimerkkinä tietokantaohjelmointikielestä voidaan tarkastella Oraclen PL/SQL kieltä. Se pohjautuu Ada ohjelmointikieleen, mutta on toiminnallisuudeltaan hieman Adaa suppeampi. Kieltä voi käyttää Oraclen SQL-forms ja Developer kehitinten yhteydessä sekä tietokantaproseduurien laadinnassa. Tietokannan käsittelyyn PL/SQL tarjoaa sulautetun SQL:n käsitteet sisäänrakennettuina SQL:n tietotyyppit ohjelmointikielen tietotyyppeinä 102

Oraclen SQL:n funktiot ohjelmointikielen funktioina Tietokannan kaavion hyväksikäytön ohjelman tietorakenteiden määrittelyssä helpot vastauksen käsittelysilmukat for rivi in kysely loop... end loop tietokantavirheet esimääriteltyinä poikkeuksina. Perustietorakenteiltaan kieli on kuitenkin Adaa köyhempi. Kieli ei myöskään käy yleisohjelmointikieleksi koska siitä puuttuvat kokonaan esimerkiksi näytön- ja tiedostojen käsittelyyn liittyvät operaatiot. 103