Tehtävät
Neljännen osan oppimistavoitteet

Tuntee käsiteanalyysin askeleet. Osaa luoda ongelma-alueen kuvauksesta luokkakaavion sekä johtaa siitä relaatiokaavion. Tietää käsitteet tietokannan normalisointi ja tietokannan denormalisointi. Tuntee tietokannan normalisointiin liittyvät ensimmäisen, toisen ja kolmannen normaalimuodon. Osaa perustella noudattaako annettu tietokanta em. normaalimuotoja. Osaa esittää tietoa XML ja HTML-kielellä.

Neljännen osan tehtävien lataaminen

Huom! Jos tehtävien lataaminen epäonnistuu, lataa NetBeansiin "Java EE Base" tai "Java Web and EE" -liitännäinen. Tämä onnistuu valitsemalla Tools -> Plugins -> Available Plugins ja etsimällä kyseisen liitännäisen listasta.

Käsiteanalyysi

Käsiteanalyysia (conceptual modeling, domain modeling) käytetään ongelma-alueen käsitteellistämiseen ja kielentämiseen, mikä edesauttaa ongelma-alueeseen liittyvää keskustelua sekä päätöksentekoa. Käsiteanalyysi tehdään iteratiivisesti esimerkiksi ongelma-alueen tekstuaalista kuvausta läpikäyden.

Käsiteanalyysin tuloksena saadaan aikaan ongelma-aluetta kuvaava tietomalli, joka sisältää ongelma-alueen käsitteet ja niiden yhteydet sekä käsitteisiin liittyvät attribuutit selkeästi ja kuvaavasti nimettyinä. Käsiteanalyysin lopputuloksessa ei ole sellaisia käsitteitä tai attribuutteja, jotka ovat ongelma-aluetta varten rakennettavan järjestelmän tai ratkaisun kannalta epäoleellisia.

Käsiteanalyysin lopputulos voi olla esimerkiksi luokkakaavio tai ER-kaavio. Tällä kurssilla käsiteanalyysin lopputuloksena on luokkia, luokkien attribuutteja, sekä luokkien välisiä yhteyksiä, jotka kuvataan luokkakaaviossa.

Käsite ja käsitteiden väliset yhteydet

Arkielämässä näkee useita saman tyyppisiä esineitä. Esimerkiksi suurin osa älypuhelimista on karkeasti ottaen saman mallisia -- jokainen puhelin on rakennettu tiettyä mallia noudattaen. Olio-ohjelmoinnin termejä noudattaen voimme sanoa, että omistamasi puhelin on Puhelin-luokasta tehty ilmentymä eli olio. Luokka on rakennuspiirrustus, jonka perusteella yksittäiset oliot luodaan.

Käsitteet ovat samalla tavalla abstrakteja kuin luokat, eli käsitteestä voi olla useampia ilmentymiä. Käsitteistä luodut ilmentymät voidaan toisaalta myös erottaa toisistaan jollain tavalla, tai niille tulee olla vähintään mahdollista määritellä jonkinlainen yksilöivä tunnus.

Käsitteiden välisillä yhteyksillä tarkoitetaan käsitteiden välisiä suhteita. Esimerkiksi puhelimella voi olla omistaja, ja toisaalta puhelimen omistaja voi opiskella jossain opinahjossa.

Käsitteitä voidaan ajatella myös hetkellisen olemassaolon kautta. Hyvät käsitteet eivät tyypillisesti ole (pysyvästi) olemassaoloriippuvaisia. Esimerkiksi puhelimen olemassaolo ei ole riippuvainen omistajasta, ja puhelimen omistaja ei ole riippuvainen opinahjosta. Käsitteen olemassaolosta riippuvaiset asiat -- kuten esimerkiksi henkilön nimi -- ovat hyviä attribuuttiehdokkaita.

Käsiteanalyysin vaiheet

Käsiteanalyysi koostuu viidestä vaiheesta, jotka ovat seuraavat:

  1. Tunnista käsite-ehdokkaat. Käsite-ehdokkaat tunnistetaan etsimällä ongelma-alueen kuvauksesta oleellisia substantiiveja ja ilmiöitä. Tässä vaiheessa myös rajataan pois käsitteitä, jotka eivät ole oleellisia ongelma-alueen kannalta.
  2. Tunnista käsitteiden väliset yhteydet. Yhteydet tunnistetaan etsimällä ongelma-alueen kuvauksesta verbejä, käsitteiden yhteyksiä sekä käsitteitä kuvaavia lausahduksia.
  3. Tunnista ja määrittele osallistumisrajoitteet. Osallistumisrajoitteet tarkentavat lopputuloksena saatavaa tietomallia. Osallistumisrajoitteita saadaan selville ongelma-alueen kuvauksessa esiintyvien adjektiivien ja määreiden kautta.
  4. Tunnista attribuutit ja lisää ne käsitteille. Tunnista käsitteisiin liittyvät tiedot eli attribuutit, joita halutaan tallentaa tietokantaan. Käsitteisiin liittyvät attribuutit tunnistaa muunmuassa olemassaoloriippuvaisista substantiiveista sekä käsitteiden yleisistä ominaisuuksista. Attribuutti saattaa olla joko yksittäinen arvo tai arvojoukko -- arvojoukot tunnistetaan tyypillisesti lukumäärien kuvauksista.
  5. Yleistä ja eriytä käsitteitä. Tunnista käsitteistä yliluokkia ja aliluokkia. Näiden tunnistaminen tapahtuu esimerkiksi käsitteitä tarkastelemalla ja miettimällä "onko käsite toisen käsitteen erikoistapaus".

Sovelletaan käsiteanalyysin askeleita seuraavaan Uimaseuraesimerkkiin.

Tunnista käsite-ehdokaat

Käsite-ehdokkaita tunnistaessa laaditaan luettelo ongelma-alueen oleellisista tietokohteista. Luettelon laatiminen alkaa substantiivien tunnistamisesta. Ensimmäisessä vaiheessa oleelliset käsite-ehdokkaat alleviivataan.

Substantiiveja tarkastelemalla luotu lista on seuraavanlainen. Alla olevassa listassa käsite-ehdokkaat on muutettu yksikkömuotoon.

  • Uimaseura
  • Paperi
  • Uimari
  • Tulos
  • Valmennuspäällikkö
  • Kirjanpito
  • Tietokone
  • Seura
  • Miesuimari
  • Naisuimari
  • Selkäuinti
  • Laji
  • Kilpailu

Käsite-ehdokkaiden karsinta tapahtuu harkitsemalla jokaista ehdokasta erikseen ja miettimällä onko se oleellinen ongelma-alueen ratkaisun kannalta. Alla kuvattu eräs karsinta.

  • Uimaseura -- seuralle tehdään järjestelmää, voidaan jättää pois ainakin toistaiseksi.
  • Paperi -- tästä haluttiin päästä eroon, tulokset kirjattiin aiemmin paperille.
  • Uimari
  • Tulos
  • Valmennuspäällikkö -- valmennuspäällikkö haluaa uuden järjestelmän, mutta ei oleellinen käsite tietomallin kannalta.
  • Kirjanpito -- järjestelmä tulee sisältämään kirjanpidon, mutta kirjanpito ei käsite järjestelmässä.
  • Tietokone -- kts. edellinen
  • Seura -- kts. uimaseura.
  • Miesuimari -- Uimari on valittuna käsitteeksi, sukupuoli voi esim. olla uimarin attribuuttina.
  • Naisuimari -- kts. edellinen
  • Selkäuinti -- Laji on valittuna käsitteeksi.
  • Laji
  • Kilpailu

Ehdokkaiden karsinnan jälkeen seuraavat käsitteet ovat jäljellä:

  • Kilpailu
  • Laji
  • Uimari
  • Tulos

Tunnista käsitteiden väliset yhteydet

Yhteydet tunnistetaan etsimällä tekstistä verbejä, käsitteiden yhteyksiä sekä käsitteitä kuvaavia lausahduksia. Tämän lisäksi aiempi aihealueen tietämys on tässä hyödyksi.

Edellä tarkastellusta kuvauksesta nousee esille seuraavat tekstit:

  • Meillä on noin sata mies- ja naispuolista uimaria
  • Uimarit kilpailevat yleensä yhdessä lajissa
  • jotkut uimarit kilpailevat useammassakin lajissa
  • Tuloksia kirjataan sekä kuukausittain järjestettävistä seuran sisäisistä "kuukauden vesihiisi"-kisoista, että jokaisesta seuran ulkopuolella järjestettävästä kilpailusta

Teksteistä voidaan päätellä seuraavat yhteydet:

  • Meillä on noin sata mies- ja naispuolista uimaria: seuraan liittyy uimareita. Poistimme aiemmin käsitteen seura, joten tämä yhteys ei ole relevantti.
  • Uimarit kilpailevat yleensä yhdessä lajissa: uimariin liittyy laji, uimariin liittyy kilpailu, kilpailuun liittyy laji.
  • jotkut uimarit kilpailevat useammassakin lajissa: (sama kuin yllä).
  • Tuloksia kirjataan sekä kuukausittain järjestettävistä seuran sisäisistä "kuukauden vesihiisi"-kisoista, että jokaisesta seuran ulkopuolella järjestettävästä kilpailusta: tulos liittyy kilpailuun.

Aiempi tieto aihealueeseen liittyen antaa olettaa, että tulokseen liittyy kilpailun lisäksi myös laji ja uimari.

Esille nousee siis seuraavat yhteydet:

  • Uimariin liittyy laji
  • Uimariin liittyy kilpailu
  • Kilpailuun liittyy laji
  • Tulokseen liittyy kilpailu
  • Tulokseen liittyy laji
  • Tulokseen liittyy uimari
[Uimari]-[Laji]
						   [Laji]-[Kilpailu]
						   [Kilpailu]-[Uimari]
						   [Tulos]-[Uimari]
						   [Tulos]-[Laji]
						   [Tulos]-[Kilpailu]

 

Kun käsitteet on tunnistettu, hahmotellaan niiden välisiä yhteyksiä. Yllä on kuvattuna eräs mahdollisuus ongelma-alueen käsitteiden yhteyksiksi.

Tunnista ja määrittele osallistumisrajoitteet

Osallistumisrajoitteilla tarkoitetaan lukumäärällisiä rajoitteita käsitteiden välillä. Osallistumisrajoitteet merkitään luokkakaavioon käsitteitä yhdistävien viivojen päätyihin. Osallistumisrajoitteita saadaan selville ongelma-alueen kuvauksessa esiintyvien adjektiivien ja määreiden kautta, jonka lisäksi aihealueeseen liittyvä tietämyksestä on hyötyä.

Edellisessä askeleessa tunnistetuista yhteyksistä saadaan selville seuraavat tiedot: uimari voi osallistua yhteen tai useampaan lajiin, eli uimariin voi liittyä monta lajia. Toisaalta, yhtä lajia voi harrastaa useampi uimari. Kilpailussa voi olla monta lajia, ja lajia voidaan todennäköisesti uida monessa kilpailussa. Kilpailussa voi olla monta uimaria, ja uimari voi uida useammassa kilpailussa. Yksittäiseen tulokseen taas liittyy yksi uimari, yksi laji, ja yksi kilpailu -- mutta, yhteen uimariin voi liittyä monta tulosta, yhteen lajiin voi liittyä monta tulosta, ja yhteen kilpailuun voi liittyä monta tulosta.

Ehdotus käsitekaavioksi osallistumisrajoitteiden kanssa on seuraavanlainen:

[Uimari]*-*[Laji]
								[Laji]*-*[Kilpailu]
								[Kilpailu]*-*[Uimari]
								[Tulos]*-1[Uimari]
								[Tulos]*-1[Laji]
								[Tulos]*-1[Kilpailu]

 

Kun käsitteiden väliset yhteydet on tunnistettu, lisätään yhteyksiin osallistumisrajoitteet. Yllä pohdittu erästä mahdollisuutta osallistumisrajoitteiksi.

Tunnista attribuutit ja lisää ne käsitteille

Käsitteisiin liittyvät attribuutit tunnistaa muunmuassa olemassaoloriippuvaisista substantiiveista sekä käsitteiden yleisistä ominaisuuksista. Attribuutti saattaa olla joko yksittäinen arvo tai arvojoukko -- arvojoukot tunnistetaan tyypillisesti lukumäärien kuvauksista. Samalla kuitenkin halutaan tallentaa vain ne attribuutit (ja käsitteet), jotka ovat ongelma-alueen kannalta oleellisua.

Ongelma-alueen kuvauksesta tiedämme, että kilpailuilla on paikka (esim. "paikalliset kilpailut", "seuran ulkopuoliset kilpailut") ja nimi (esim. "kuukauden vesihiisi"), jonka lisäksi nimestä voi päätellä, että kilpailuun liittyy aika. Vastaavasti uimareihin liittyy todennäköisesti nimi ja syntymäaika, vaikkei kuvauksessa kumpaakaan suoraan pyydetä. Nimen perusteella on helppo tarkastaa kenestä on kyse, ja syntymäaika auttaa seuraamaan tuloskehitystä. Tulokseen tarvitaan jonkinlainen tieto tuloksesta -- uimakisoissa kyseessä on tarkka aika, ja laji kerrotaan tässä nimenä.

[Uimari|nimi:String;syntymäaika:Date]
								 [Laji|nimi:String]
								 [Kilpailu|nimi:String;paikka:String;aika:Date]
								 [Tulos|millisekuntia:Double]
								 [Uimari]*-*[Laji]
								 [Laji]*-*[Kilpailu]
								 [Kilpailu]*-*[Uimari]
								 [Tulos]*-1[Uimari]
								 [Tulos]*-1[Laji]
								 [Tulos]*-1[Kilpailu]

 

Kun käsitteiden väliset yhteydet ja osallistumisrajoitteet on tunnistettu, lisätään käsitteille attribuutit.

Yleistä ja eriytä käsitteitä

Tunnista käsitteistä yliluokkia ja aliluokkia. Näiden tunnistaminen tapahtuu esimerkiksi käsitteitä tarkastelemalla ja miettimällä "onko käsite toisen käsitteen erikoistapaus". Vastaavasti toistuvat attribuutit saattavat antaa ilmi yli- ja aliluokkia.

Yli- ja aliluokkien etsintä kannattaa tehdä matriisina, missä jokaista käsitettä verrataan jokaiseen muuhun käsitteeseen. Käymällä läpi käsitteemme, huomaamme, ettei niissä ole yli- tai aliluokille tarvetta.

- Kilpailu Laji Tulos Uimari
Kilpailu - Kilpailu ei ole lajin erikoistapaus. Kilpailu ei ole tuloksen erikoistapaus. Kilpailu ei ole uimarin erikoistapaus.
Laji Laji ei ole kilpailun erikoistapaus. - Laji ei ole tuloksen erikoistapaus. Laji ei ole uimarin erikoistapaus.
Tulos Tulos ei ole kilpailun erikoistapaus. Tulos ei ole lajin erikoistapaus. - Tulos ei ole uimarin erikoistapaus.
Uimari Uimari ei ole kilpailun erikoistapaus. Uimari ei ole lajin erikoistapaus. Uimari ei ole tuloksen erikoistapaus. -

Luokkakaaviota ei siis tarvitse tässä tapauksessa muuttaa.

Tarkastellaan vielä tilannetta, missä luokkakaaviosta löytyy tapaus, missä toinen käsite on toisen käsitteen erikoistapaus. Oletetaan, että käytössämme ovat käsitteet Henkilö ja Opiskelija. Henkilöllä on nimi, syntymäaika ja sähköpostiosoite. Opiskelijalla on nimi, syntymäaika, sähköpostiosoite ja opiskelijanumero.

[Henkilo|nimi:String;syntymäaika:Date;email:String]
							 [Opiskelija|nimi:String;syntymäaika:Date;email:String;opiskelijanumero:String]

 

Henkilö ja opiskelija luokkakaaviossa.

Huomaamme, että opiskelija on henkilön erikoistapaus. Opiskelijalla on muuten samat ominaisuudet kuin henkilöllä, mutta sillä on lisäksi opiskelijanumero. Voimme luoda tämän perusteella luokkakaavion, missä opiskelija perii henkilön. Tämä merkitään seuraavasti.

[Henkilo|nimi:String;syntymäaika:Date;email:String]
								 [Opiskelija|opiskelijanumero:String]
								 [Henkilo]^-[Opiskelija]

 

Henkilö ja opiskelija luokkakaaviossa. Opiskelija perii henkilön, eli opiskelijalla on kaikki henkilön attribuutit, jonka lisäksi opiskelijalla on myös omat attribuuttinsa.
Perintä ja luokkakaaviosta tietokantakaavioksi

Perintä käsitellään tietokantakaaviossa yhden suhde yhteen -tyyppisenä yhteytenä. Kun perintänä merkittyä yhteyttä muunnetaan tietokantakaavioksi, lisätään perivään käsitteeseen viiteavain, joka viittaa perittävään käsitteeseen. Edellinen opiskelija-henkilö -esimerkki muuntuu seuraavanlaiseksi tietokantakaavioksi.

[Henkilo|(pk) id: Integer; nimi:String;syntymäaika:Date;email:String], [Opiskelija|(pk) id: Integer; (fk) henkilo_id: Henkilo; opiskelijanumero:String], [Henkilo]-[Opiskelija]

 

Tietokannan normalisointi ja denormalisointi

Tietokannan normalisointi on askeleittainen prosessi, mikä sisältää mahdollisten ongelmakohtien tunnistamisen ja niiden korjaamisen. Tietokannan normalisointiprosessin tuloksena tietokanta sisältää hyvin vähän toisteista tietoa. Tietokannan denormalisointi on käänteinen prosessi, missä tietokannassa sijaitsevan toisteisuuden määrä lisääntyy. Samalla tietokantakyselyiden tehokkuus tyypillisesti kasvaa.

Tarkastellaan näitä kahta seuraavaksi.

Tietokannan normalisointi

Tietokannan normalisoinnin tavoite on vähentää tietokantatauluissa esiintyvää toisteista tietoa. Pääpiirteittäin tavoite on sama kuin käsiteanalyysissä: lopulta jokainen taulu liittyy vain tiettyyn käsitteeseen ja taulun attribuutit liittyvät vain kyseisen taulun esittämään käsitteeseen. Lähestymistapa on kuitenkin toisenlainen: tietokannan normalisoinnissa etsimme epäkohtia olemassaolevista tietokantatauluista, jonka jälkeen näitä epäkohtia korjataan.

Tietokannan normalisointi tapahtuu askeleittain normaalimuotojen avulla.

Ensimmäinen normaalimuoto

Tietokantataulu on ensimmäisessä normaalimuodossa, jos se täyttää seuraavat ehdot:

  1. Sarakkeen arvot eivät saa sisältää listoja.
  2. Taulun sarakkeet eivät muodosta toistuvia ryhmiä.
  3. Sarakkeen arvojen tulee olla saman tyyppisiä.
  4. Jokaisen sarakkeen nimen tulee olla tietokantataulussa uniikki.
  5. Sarakkeiden järjestyksen ei tule vaikuttaa tietokantataulun toimintaan.
  6. Tietokantataulussa ei saa olla kahta täsmälleen samanlaista riviä.
  7. Rivien järjestyksen ei tule vaikuttaa tietokantataulun toimintaan.

Alla on esimerkki henkilöitä sisältävästä tietokantataulusta. Jokaiseen henkilöön liittyy tunnus (id), nimi sekä pilkuilla eroteltu lista puhelinnumeroita. Esimerkki rikkoo ensimmäistä normaalimuotoa, sillä puhelinnumerot sisältävät listoja.

Henkilo((pk) id, nimi, puhelinnumerot)
id nimi puhelinnumerot
1 Larry 555-1024, 555-2048
2 Moe 555-0512, 555-0256, 555-0128
3 Curly 555-0001, 555-0002, 555-0004

Ensimmäinen korjaus ylläolevaan tietokantatauluun on eritellä puhelinnumerot erillisiksi sarakkeikseen (tehty alla). Tämä ei ole kovin hyvä ratkaisu -- koko tietokantataulun rakennetta tulee muuttaa jos jollain on esimerkiksi neljä tai viisi erillistä numeroa. Tämä myös rikkoo ensimmäistä normaalimuotoa, sillä puhelinnumero muodostaa toistuvan ryhmän.

Henkilo((pk) id, nimi, puhelinnumero1, puhelinnumero2, puhelinnumero3)
id nimi puhelinnumero1 puhelinnumero2 puhelinnumero3
1 Larry 555-1024 555-2048
2 Moe 555-0512 555-0256 555-0128
3 Curly 555-0001 555-0002 555-0004

Sopivampi korjaus ongelmaan on muodostaa erillinen tietokantataulu puhelinnumeroille. Henkilön ja puhelinnumeron välillä on yhden suhden moneen -yhteys, eli yhteen henkilöön liittyy monta puhelinnumeroa, mutta jokainen puhelinnumero liittyy yhteen henkilöön.

Henkilo((pk) id, nimi)
id nimi
1 Larry
2 Moe
3 Curly

 

Puhelinnumero((pk) id, (fk) henkilo_id -> Henkilo, puhelinnumero)
id henkilo_id puhelinnumero
1 1 555-1024
2 1 555-2048
3 2 555-0512
4 2 555-0256
... ... ...

Toinen normaalimuoto

Ensimmäisessä normaalimuodossa kyse on ensiaskeleista tietokannan rakenteen järkevöittämiseen. Muissa normaalimuodoissa käsite funktionaalinen riippuvuus sarakkeiden välillä on oleellinen.

Funktionaalinen riippuvuus

Sarake B on funktionaalisesti riippuvainen sarakkeesta A (A määrää funktionaalisesti B:n), jos sarakkeen A arvon perusteella voidaan yksikäsitteisesti selvittää sarakkeen B arvo. Tällöin kirjoitetaan A -> B, ja sanotaan, että "sarake B on funktionaalisesti riippuvainen sarakkeesta A". Huom! A voi olla myös kokoelma sarakkeita!

Esimerkiksi henkilön nimi on funktionaalisesti riippuvainen henkilötunnuksesta, sillä henkilötunnuksen perusteella voidaan yksikäsitteisesti selvittää nimi. Toisaalta, henkilötunnus ei ole funktionaalisesti riippuvainen henkilön nimestä, koska useammalla henkillä voi olla sama nimi.

Selvittäminen voi tapahtua kyselyllä "SELECT DISTINCT b FROM Taulu WHERE a=tiedetty_arvo", missä avainsana DISTINCT palauttaa uniikit rivit. Jos attribuutti b on funktionaalisesti riippuva a:sta, tuottaa ylläoleva kysely joko yhden tai ei yhtään tulosriviä, mutta ei koskaan enempää. Tämän ehdon on oltava voimassa aina, ei vain hetkellisesti.

Tietokantataulu on toisessa normaalimuodossa jos (1) se on ensimmäisessä normaalimuodossa ja (2) tietokantataulun sarakkeet (poislukien avaimet) ovat funktionaalisesti riippuvaisia tietokantataulun (yhdellä sarakkeella määritellystä) pääavaimesta.

Jos tietokantataulun pääavain on määritelty yhden sarakkeen avulla, ovat kaikki tietokantataulun sarakkeet automaattisesti funktionaalisesti riippuvaisia pääavaimesta. Käytännössä siis, jos taulu on ensimmäisessä normaalimuodossa ja sillä on yhden sarakkeen avulla määritelty pääavain, on se automaattisesti toisessa normaalimuodossa.

Jos taas tietokantataulun pääavain on määritelty useamman sarakkeen avulla, tulee tietokantataulun jokaisen sarakkeen olla riippuvainen koko avaimesta, eli osittaista riippuvuutta pääavaimesta ei sallita. Tarkastellaan tilannetta, missä tietokantataulun pääavain on määritelty useamman sarakkeen kautta ja tällainen tilanne tapahtuu.

Oletetaan seuraavat tietokantataulut, joissa pääavaimet on alleviivattu. Ensimmäisessä kahdessa tietokantataulussa pääavain on id, kolmannessa taulussa pääavain on määrätty kahden viiteavaimen yhdistelmänä.

  • Asiakas ((pk) id, nimi)
  • Kauppa ((pk) id, nimi, osoite)
  • Ostos ((fk) asiakas_id -> Asiakas, (fk) kauppa_id -> Kauppa, hinta, kaupunki)

Taulut Asiakas ja Kauppa ovat ensimmäisessä ja toisessa normaalimuodossa.

Tarkastellaan taulua Ostos. Taulun Ostos sarake hinta kertoo ostoksen hinnan. Sarake kaupunki kertoo missä ostos tehtiin.

Ostos ((fk) asiakas_id -> Asiakas, (fk) kauppa_id -> Kauppa, hinta, kaupunki)
asiakas_id kauppa_id hinta kaupunki
1 1 14.90 Helsinki
1 3 15.20 Vantaa
2 1 8.40 Helsinki
3 2 19.20 Espoo
3 3 10.40 Vantaa
4 1 12.20 Helsinki
... ... ... ...

Kun tarkastelemme taulua Ostos, huomaamme, että tietokantataulun sarake kaupunki on funktionaalisesti riippuvainen sarakkeesta kauppa_id. Koska sarake kauppa_id on osa tietokantataulun pääavaimesta, tämä rikkoo toista normaalimuotoa. Yksi ratkaisu ongelmaan on kaupungin siirtäminen tauluun Kauppa.

  • Asiakas ((pk) id, nimi)
  • Kauppa ((pk) id, nimi, osoite, kaupunki)
  • Ostos ((fk) asiakas_id -> Asiakas, (fk) kauppa_id -> Kauppa, hinta)

Nyt jokainen ylläolevista tietokantatauluista on ensimmäisessä ja toisessa normaalimuodossa.

Kandidaattiavain

Toisen normaalimuodon voi määritellä myös kandidaattiavain-käsitteen kautta. Tietokantataulun kandidaattiavaimet määritellään niiden tietokantataulun sarakkeiden joukkona, joiden avulla tietokantataulun rivit voidaan yksilöidä. Toisin sanoen, kandidaattiavainjoukko mahdollistaa tietokantatauuln rivin yksilöimisen.

Tietokantataululle voidaan määritellä tyypillisesti useampia kandidaattiavaimia, mutta näistä valitaan vain yksi tietokantataulun pääavaimeksi. Tarkastellaan taulua Henkilö, joka sisältää sarakkeet syntymäaika, etunimi, sukunimi ja puhelinnumero.

Kandidaattiavaimia etsitään sarakkeiden avulla muodostetusta joukkojen joukosta: {{syntymäaika}, {etunimi}, {sukunimi}, {puhelinnumero}, {syntymäaika, etunimi}, {syntymäaika, sukunimi}, {syntymäaika, puhelinnumero}, {etunimi, sukunimi}, {etunimi, puhelinnumero}, {syntymäaika, etunimi, sukunimi}, {syntymäaika, etunimi, puhelinnumero}, {syntymäaika, sukunimi, puhelinnumero}, {etunimi, sukunimi, puhelinnumero}, {syntymäaika, etunimi, sukunimi, puhelinnumero}}.

Jokaista joukkoa tarkastellaan niiden sisältämien sarakkeiden arvojoukkojen kautta. Jos joukolle on mahdollista löytää useampia rivejä, joissa kandidaattiavainjoukon arvot ovat samat, hylätään kandidaattiavain. Esimerkiksi useammalla henkilöllä voi olla sama syntymäaika, useammalla henkilöllä voi olla sama etunimi, ja useammalla henkilöllä voi olla sama sukunimi, joten {syntymäaika}, {etunimi}, {sukunimi} eivät ole kandidaattiavaimia. Vastaavasti joukko {etunimi, sukunimi} ei voi olla kandidaattiavain, sillä useammalla henkilöllä voi olla sama etunimi ja sukunimi.

Tätä prosessia jatkamalla tunnistetaan lopullinen kandidaattiavainten joukko. Edellisessä taulussa oikeastaan yksikään esitellyistä joukoista ei ole kandidaattiavainjoukko jos oletamme, että useammalla henkilöllä voi olla sama puhelinnumero.

Kandidaattiavainten avulla määriteltynä taulu on toisessa normaalimuodossa jos ja vain jos se on ensimmäisessä normaalimuodossa ja jokainen taulun kandidaattiavaimeen kuulumaton sarake on riippuvainen koko kandidaattiavaimen joukosta, mutta ei yksittäisestä joukon jäsenestä (jos joukkoon kuuluu useampi sarake).

Kolmas normaalimuoto

Kolmanteen normaalimuotoon liittyy oleellisesti käsite transitiivinen riippuvuus.

Transitiivinen riippuvuus

Transitiivisella riippuvuudella tarkoitetaan sitä, että sarake A on funktionaalisesti riippuvainen sarakkeesta C jonkun toisen sarakkeen kautta. Sarake A on transitiivisesti riippuvainen sarakkeesta C, jos sarake A on funktionaalisesti riippuvainen sarakkeesta B ja sarake B on funktionaalisesti riippuvainen sarakkeesta C. Tässä A, B ja C voivat olla myös sarakejoukkoja.

Tietokantataulu on kolmannessa normaalimuodossa jos se on toisessa normaalimuodossa ja siinä olevat sarakkeet eivät ole transitiivisesti riippuvaisia taulun pääavaimesta.

Jos tietokantataulu rikkoo kolmannen normaalimuodon, eli tietokantataulusta tunnistetaan sarakkeita, jotka ovat transitiivisesti riippuvaisia pääavaimesta, eriytetään ne omaksi taulukseen. Eräs klassinen esimerkki tällaisesta tilanteesta liittyy postinumeroon -- tarkastellaan seuraavaa taulua Osoite.

Osoite((pk) id, katuosoite, postinumero, postitoimipaikka)
id katuosoite postinumero postitoimipaikka
1 Työpajankatu 13 00580 Helsinki
2 Työpajankatu 2 R1 C 00580 Helsinki
3 Siltavuorenranta 18 00170 Helsinki
... ... ... ...

Yllä olevassa tietokantataulussa havaitaan funktionaalinen riippuvuus postinumero -> postitoimipaikka, eli postitoimipaikan saa selvitettyä postinumeron perusteella. Samalla kaikki sarakkeet ovat selvitettävissä taulun pääavaimen kautta, joten taulusta löytyy myös transitiivinen riippuvuus. Ratkaisu tähän on -- esimerkiksi -- luoda erillinen taulu postinumeroille.

  • Osoite((pk) id, katuosoite, postinumero)
  • Postinumero((pk) postinumero, postitoimipaikka)
Muita normaalimuotoja

Ensimmäisen, toisen ja kolmannen normaalimuodon lisäksi tietokannan normalisointiin käytetään Boyce-Codd -normaalimuotoa, Neljättä normaalimuotoa ja Viidettä normaalimuotoa.

Tämän kurssin puitteissa ensimmäiset kolme normaalimuotoa riittävät tietokannan suunnitteluun.

Tietokannan denormalisointi

Tietokannan normalisointi johtaa tyypillisesti tilanteeseen, missä tietokannassa on useita tietokantatauluja, joista jokainen kuvaa jotain selkeää käsitettä. Tietokantataulujen väliset yhteydet tunnistetaan pää- ja viiteavainten avulla, ja taulujen attribuutit ovat selkeitä. Tietokannasta puuttuu toisteinen tieto.

Yleisesti ottaen yllä kuvattu tilanne on hyvä, mutta absoluuttinen hyvyys liittyy paljolti myös käyttötarkoitukseen. Esimerkiksi raportointiin tarkoitettujen järjestelmien ei kannata todennäköisesti -- jos raportin luonti on hidas operaatio -- luoda samoja raportteja yhä uudelleen ja uudelleen, vaan voi olla mielekästä luoda erillinen tietokantataulu (tai muutama), jotka sisältävät raporteille oleelliset tiedot valmiiksi laskettuna.

Myös tietokannan (tai tietokantataulun) käyttötarkoitus vaikuttaa normalisoinnin tarpeeseen. Esimerkiksi sivukäyntien kirjaamiseen tarkoitettu logitusjärjestelmä toimii tehokkaammin jos sivukäyntien tallentamiseen tarkoitetut osat järjestelmästä on denormalisoitu. Tarkastellaan tätä seuraavan esimerkin kautta.

Alla on annettuna kaksi tietokantaa, toinen on normalisoitu ja toinen denormalisoitu. Kumpaakin käytetään järjestelmässä kävijöiden tekemien tapahtumien kirjaamiseen.

Alla olevassa versiossa käyttäjä ja sivu on eriytetty omaksi käsitteekseen, johon tapahtuma-taulu viittaa. Kun tapahtumaa luodaan, tulee tapahtuman lisäämisen yhteydessä hakea käyttäjän tunnus taulusta Kayttaja sekä osoitetta vastaavan sivun tunnus taulusta Sivu.

  • Kayttaja ((pk) id, kayttajatunnus)
  • Sivu ((pk) id, osoite)
  • Tapahtuma ((pk) id, (fk) kayttaja_id -> Kayttaja, (fk) sivu_id -> Sivu, aika, operaatio, ip-osoite, laite)

Toinen vaihtoehto on tallentaa käyttäjätunnus ja sivun osoite sellaisenaan.

  • Tapahtuma ((pk) id, kayttajatunnus, osoite, aika, operaatio, ip-osoite, laite)

Lisää SQLiten avulla tehtäväpohjan kansioon db kaksi yllä kuvattua tietokantaa, joita käytetään järjestelmässä tapahtuvien tapahtumien tallentamiseen. Tee tämän jälkeen ohjelma, joka testaa tiedon lisäämisen nopeutta edellä mainittuihin tietokantatauluihin.

Voit olettaa, että järjestelmä saa jokaisen tapahtuman yhteydessä tietoonsa käyttäjätunnuksen, osoitteen, ajan, tehdyn operaation, ip-osoitteen sekä käyttäjän käyttämän laitteen. Tehtäväpohjassa on valmiit metodit satunnaisen tiedon luomiseen.

Kun tietoa lisätään ensimmäiseen tietokantaan, tapahtuman lisäämisen yhteydessä tulee hakea käyttäjätaulusta tieto käyttäjästä (ja tallentaa käyttäjä tauluun jos kyseistä käyttäjää ei vielä ole), jonka lisäksi sivutaulusta tulee hakea tieto sivusta osoitteen perusteella (sekä lisätä sivu jos sitä ei vielä ole).

Kun tietoa lisätään toiseen tietokantaan, riittää tiedon tallentaminen tietokantatauluun.

Kun ohjelma lisää tietoa kumpaankin tietokantatauluun ja tulostaa lisäysoperaatioiden kestot, palauta ohjelma. Testaa lisäyksen nopeutta vähintään 25000 rivin lisäyksellä.

Normalisoida vai eikö normalisoida?

Lue CodingHorror.com-blogista kirjoitus Maybe Normalizing Isn't Normal.

Ensimmäisen, toisen ja kolmannen normaalimuodon lisäksi tietokannan normalisointiin käytetään Boyce-Codd -normaalimuotoa, Neljättä normaalimuotoa ja Viidettä normaalimuotoa.

Tämän kurssin puitteissa ensimmäiset kolme normaalimuotoa riittävät tietokannan suunnitteluun.

Muita tiedon esitysmuotoja

Tarkastellaan vielä muutamia muita yleisiä tiedon esitysmuotoja.

XML

XML (EXtensible Markup Language) on kieli, jolla kuvataan rakenteellista tietoa. Se koostuu kahdesta osasta, otsakkeesta ja rungosta. Otsake on valinnainen, ja sisältää esimerkiksi dokumentin versionumeron, tiedon käsittelyohjeita, sekä mahdollisia tietoja tiedon rakenteesta. XML-dokumentin runko taas alkaa juurielementistä, joita on vain yksi, jonka alla on yksi tai useampia elementtejä, joilla voi olla arvoja.

Alla on esimerkki XML-dokumentista, jossa on kuvattu opiskelijoiden nimiä.

<?xml version="1.0"?>
<opiskelijat>
  <opiskelija>
    <nimi>Ada Lovelace</nimi>
  </opiskelija>
  <opiskelija>
    <nimi>Edgar F. Codd</nimi>
  </opiskelija>
  <opiskelija>
    <nimi>Lixia Zhang</nimi>
  </opiskelija>
</opiskelijat>

XML-dokumentin elementit voivat sisältää useita elementtejä. Alla on kuvaus kahdesta kurssista sekä niihin liittyvistä opettajista.

<?xml version="1.0"?>
<kurssit>
  <kurssi>
    <nimi>Ohjelmoinnin perusteet</nimi>
    <luento>Ti 10-12</luento>
    <opettaja>
      <nimi>Charles Babbage</nimi>
    </opettaja>
  </kurssi>
  <kurssi>
    <nimi>Tietokantojen perusteet</nimi>
    <luento>Pe 12-14</luento>
    <opettaja>
      <nimi>Edgar F. Codd</nimi>
    </opettaja>
  </kurssi>
</kurssit>

HTML

HTML on kieli web-sivustojen luomiseen. HTML on kuvauskieli, jonka avulla kuvataan sekä web-sivun rakenne että sivun sisältämä teksti. HTML-sivujen rakenne määritellään HTML-kielessä määritellyillä elementeillä, ja yksittäinen HTML-dokumentti koostuu sisäkkäin ja peräkkäin olevista elementeistä.

Sivujen rakenteen määrittelevät elementit erotellaan pienempi kuin (<) ja suurempi kuin (>) -merkeillä. Elementti avataan elementin nimen sisältävällä pienempi kuin -merkillä alkavalla ja suurempi kuin -merkkiin loppuvalla merkkijonolla, esim. <html>, ja suljetaan merkkijonolla jossa elementin pienempi kuin -merkin jälkeen on vinoviiva, esim </html>. Yksittäisen elementin sisälle voi laittaa muita elementtejä.

Suurin osa elementeistä tulee sulkea lopuksi. Osa HTML5:n elementeistä – esimerkiksi <br> – on kuitenkin ns. tyhjiä ("void"), eikä niille kirjoiteta erillistä lopetusta. Halutessaan tyhjät elementit voi lopettaa X(HT)ML-tyyliseen /-merkkiin, esimerkiksi seuraavasti: <br />.

HTML-dokumentin runko

Tyypillisen HTML-dokumentin runko näyttää seuraavalta. Kun klikkaat allaolevassa iframe-elementissä Result-tekstiä, näet HTML-sivun, ja kun painat HTML-tekstiä, näet HTML-koodin. Klikkaamalla elementin oikeassa ylälaidassa olevasta Edit in JSFiddle-linkistä, pääset muokkaamaan elementtiä suoraan JSFiddlessä.

Yllä olevassa HTML-dokumentissa on dokumentin tyypin kertova erikoiselementti <!DOCTYPE html>, joka kertoo dokumentin olevan HTML-sivu. Tätä seuraa elementti <html>, joka aloittaa HTML-dokumentin. Elementti <html> sisältää yleensä kaksi elementtiä, elementit <head> ja <body>. Elementti <head> sisältää sivun otsaketiedot, eli esimerkiksi sivun käyttämän merkistön <meta charset="utf-8" /> ja otsikon <title>. Elementti <body> sisältää selaimessa näytettävän sivun rungon. Ylläolevalla sivulla on ensimmäisen tason otsake-elementti h1 (header 1) ja tekstielementti p (paragraph).

Elementit voivat sisältää attribuutteja, joilla voi olla yksi tai useampi arvo. Yllä olevassa HTML-dokumentissa elementille meta on määritelty erillinen attribuutti charset, joka kertoo dokumentissa käytettävän merkistön: "utf-8". Attribuuttien lisäksi elementit voivat sisältää tekstisolmun. Esimerkiksi yllä olevat elementit title, h1 ja p kukin sisältävät tekstisolmun eli tekstiä. Tekstisolmulle ei ole erillistä elementtiä tai määrettä, vaan se näkyy tekstinä.

Puhe tekstisolmuista antaa viitettä jonkinlaisesta puurakenteesta. HTML-dokumentit, aivan kuten XML-dokumentit, ovat rakenteellisia dokumentteja, joiden rakenne on usein helppo ymmärtää puumaisena kaaviona. Ylläolevan web-sivun voi esittää esimerkiksi seuraavanlaisena puuna (attribuutit ja dokumentin tyyppi on jätetty merkitsemättä).

                   html
               /          \
             /              \
          head              body
        /       \         /      \
     meta       title     h1      p
                 :        :       :
              tekstiä  tekstiä tekstiä

Koska HTML-dokumentti on rakenteellinen dokumentti, on elementtien sulkemisjärjestyksellä väliä. Elementit tulee sulkea samassa järjestyksessä kuin ne on avattu. Esimerkiksi, järjestys <body><p>whoa, minttutee!</body></p> on väärä, kun taas järjestys <body><p>whoa, minttutee!</p></body> on oikea.

Kaikki elementit eivät kuitenkaan sisällä tekstisolmua, eikä niitä suljeta erikseen. Yksi näistä poikkeuksista on link-elementti.

Kun selaimet lataavat HTML-dokumenttia, ne käyvät sen läpi ylhäältä alas, vasemmalta oikealle. Kun selain kohtaa elementin, se luo sille uuden solmun. Seuraavista elementeistä luodut solmut menevät aiemmin luodun solmun alle kunnes aiemmin kohdattu elementti suljetaan. Aina kun elementti suljetaan, puussa palataan ylöspäin edelliselle tasolle.

Listaelementit

Sivuille voi lisätä listoja mm. ol (ordered list) ja ul (unordered list) -elementtien avulla. Elementeillä ol tai ul aloitetaan lista, ja listan sisälle asetettavat yksittäisiin listaelementteihin käytetään li (list item)-elementtiä. Yksittäiset listaelementit voivat taas sisältää esimerkiksi tekstisolmun tai lisää html-elementtejä.

Linkit toisille sivuille

Elementin a (anchor) avulla voi luoda linkin sivulta toiselle. Sivu, jolle käyttäjä siirtyy, merkitään elementin a attribuutin href arvolla. Jos sovelluksessasi on kaksi sivua, index.html ja oma.html, voi sivulta oma.html luoda linkin sivulle index.html komennolla <a href="index.html">index.html</a>.

Lisää HTML-sivujen tekemisestä!

Lisää tietoa HTML-sivujen tekemisestä löytyy mm. osoitteesta http://www.w3schools.com/html/.

Huom! Jos tehtävien lataaminen epäonnistuu, lataa NetBeansiin "Java EE Base" tai "Java Web and EE" -liitännäinen. Tämä onnistuu valitsemalla Tools -> Plugins -> Available Plugins ja etsimällä kyseisen liitännäisen listasta.

Luo tehtäväpohjassa olevaan kansioon src (tai Site Root) uusi sivu index.html. Muokkaa sivua siten, että se näyttää seuraavalta selaimessa:

Ei haittaa jos tekstin leveys on eri kuin yllä olevassa kuvassa! Kun olet valmis ja sivusi näyttää oikealta selaimessa, palauta tehtävä TMC:lle.

Sisällysluettelo