Tehtävät
Seitsemännen osan oppimistavoitteet

Tietää joitakin kirjastoja yksinkertaisten SQL-kyselyiden automatisointiin ja osaa käyttää yhteenvetokyselyjä osana web-sovellusta. Tuntee pinnallisesti muutamia NoSQL- ja NewSQL-tietokantoja.

Yhteenvetokyselyt osana tietokantaa käyttäviä sovelluksia

Yhteenvetokyselyiden käyttäminen osana tietokantaa hyödyntäviä sovelluksia ei juurikaan poikkea muiden tietokantakyselyiden käytöstä. Tässä tutustut yhteenvetokyselyiden käyttöön ohjelmallisesti.

Tehtäväpohjassa tulee mukana sovellus kurssin aikana tutuksi tulleen Chinook-tietokannan albumien, artistien ja kappaleiden läpikäyntiin. Tehtävänäsi on lisätä sovellukseen toiminnallisuus, joka listaa artistit sekä artistien kappaleiden lukumäärän. Listauksessa näkyvien artistien nimiä klikkaamalla tulee päästä artistin tietoihin käsiksi.

Tehtäväpohjassa on valmiina luokka ArtistData sekä html-tiedosto stats-artists.html, jotka auttanevat toiminnallisuuden rakentamisessa. Toiminnallisuuteen tulee päästä käsiksi tekemällä GET-tyyppinen pyyntö sovelluksen polkuun /stats/artists.

Listauksen tulee näyttää lopulta (kutakuinkin) seuraavalta.

Artists
Artist Albums
AC/DC 2
Accept 2
Aerosmith 1
...

Tehtäväpohjassa tulee edellisessä osassa käytetty Chinook-tietokantaa hyödyntävä sovellus. Tässä tehtävänäsi on lisätä sovellukseen toiminnallisuus, joka listaa asiakkaiden nimet, asiakkaiden tekemien tilausten lukumäärän sekä asiakkaiden tekemien tilausten yhteissumman.

Listauksen tulee näyttää lopulta (kutakuinkin) seuraavalta. Tehtäväpohjassa on tiedosto stats-invoices.html, joka auttaa näkymän oikeassa muotoilussa. Listaukseen tulee päästä käsiksi kun käyttäjä tekee GET-tyyppisen pyynnön osoitteeseen /stats/invoices.

Customer
Customer Total invoices Sum
Luís Gonçalves 7 39.62
Leonie Köhler 7 37.620000000000005
François Tremblay 7 39.62
Bjørn Hansen 7 39.62
...

Valmiit Dao-kirjastot

Nykyään löytyy huomattava määrä valmiita kirjastoja, jotka tarjoavat Dao-toiminnallisuuksia valmiina siten, että käyttäjän ei tarvitse kirjoittaa yksinkertaisimpia SQL-kyselyitä itse.

Eräs tällainen kirjasto on ORMLite, joka abstrahoi ja toteuttaa osan tietokantakyselyistä ohjelmoijan puolesta. ORMLite-kirjaston saa projektiin lisäämällä siihen liittyvän riippuvuuden Mavenin pom.xml-tiedostoon.

<dependency>
    <groupId>com.j256.ormlite</groupId>
    <artifactId>ormlite-jdbc</artifactId>
    <version>5.1</version>
</dependency>

ORMLiteä käytettäessä tietokantatauluja kuvaaville luokille lisätään annotaatiot @DatabaseTable(tableName = "taulun nimi"), jonka lisäksi oliomuuttujille lisätään @DatabaseField-annotaatiot, joissa määritellään sarakkeen nimi, johon oliomuuttuja liittyy. Jos oliomuuttuja on taulun pääavain, lisätään sille erillinen määrittely (id=true) annotaatioon @DatabaseField: @DatabaseField(id = true, columnName = "sarakkeen nimi").

Tiedon hakeminen yhdestä taulusta

Käytännössä ORMLite osaa luoda olioita tietokannasta haettavista riveistä annotaatioiden perusteella. Jokaisessa tietokantataulua kuvaavassa luokassa tulee olla myös tyhjä konstruktori.

Esimerkiksi luokka Pyora ORMLite-annotaatioilla olisi seuraavanlainen (tässä pyörään liittyviä varauksia ei ole otettu huomioon):

import com.j256.ormlite.field.DatabaseField;
import com.j256.ormlite.table.DatabaseTable;

@DatabaseTable(tableName = "Pyora")
public class Pyora {

    @DatabaseField(id = true, columnName = "rekisterinumero")
    private String rekisterinumero;
    @DatabaseField(columnName = "merkki")
    private String merkki;

    // jokaisella tallennettavalla oliolle tulee olla parametriton konstruktori
    public Pyora() {
    }

    public Pyora(String rekisterinumero, String merkki) {
        this.rekisterinumero = rekisterinumero;
        this.merkki = merkki;
    }

// getterit ja setterit
}
ORMLite ja annotaatio @DatabaseField

Jos tietokannassa olevan sarakkeen nimi on sama kuin oliomuuttujan nimi, voidaan annotaatiosta @DatabaseField jättää columnName-määrittely pois. Tietokantataulun sarakkeet, jotka ovat muotoa sarakkeen_nimi tulee olla määriteltynä camelCase-muodossa oliomuuttujina. Sarake sarakkeen_nimi olisi siis oliomuuttujana muotoa sarakkeenNimi.

Nyt kaikkien pyörien hakeminen tietokannasta onnistuu seuraavasti. Käytössä on useampia ORMLiten tarjoamia luokkia ja rajapintoja kuten ConnectionSource, JdbcConnectionSource, DaoManager ja Dao. ORMLiteä käyttäessä emme kirjoita erikseen ohjelmakoodia (suoraviivaisten) kyselyiden tulosten muuttamiseksi olioiksi.

ConnectionSource connectionSource
        = new JdbcConnectionSource("jdbc:sqlite:vuokraamo.db");

Dao<Pyora, String> pyoraDao
        = DaoManager.createDao(connectionSource, Pyora.class);

List<Pyora> pyorat = pyoraDao.queryForAll();
for (Pyora pyora : pyorat) {
    System.out.println(pyora.getMerkki() + " " + pyora.getRekisterinumero());
}

Käytännössä ORMLite lukee luokkaan määritellyt annotaatiot, ja tekee niiden perusteella käytettävät tietokantakyselyt, joita ohjelmoija käyttää ORMLiten toteuttaman Dao-rajapinnan kautta.

Viitteiden käsittely

Lisätään seuraavaksi toiminnallisuus pyörien hakemiseen Varaus-luokan kautta.

Osoitteessa http://ormlite.com/javadoc/ormlite-core/doc-files/ormlite_2.html oleva ORMLiten "How to Use"-dokumentaatio sisältää neuvoja ORMLiten käyttöön.

Viitteiden hakemisessa tarvittavien annotaatioiden määrittely tapahtuu kuten Pyora-luokalle. Viittausta pyörään määriteltäessä annotaatiolle @DatabaseField tulee kertoa, että sarake viittaa toiseen tauluun, ja että viitatusta taulusta tulee hakea oliolle arvo.

Tämä tapahtuu lisäämällä annotaatioon parametrit foreign = true ja foreignAutoRefresh = true. Tämän lisäksi, myös viitattuun tauluun tulee lisätä annotaatiot.

@DatabaseTable(tableName = "Varaus")
public class Varaus {

    @DatabaseField(id = true)
    private Integer varaustunnus;
    @DatabaseField(columnName = "pyora", canBeNull = false, foreign = true, foreignAutoRefresh = true)
    private Pyora pyora;
    private Varaaja varaaja;
    @DatabaseField(columnName = "varaus_alkaa")
    private Timestamp varausAlkaa;
    @DatabaseField(columnName = "varaus_loppuu")
    private Timestamp varausLoppuu;

    public Varaus() {
    }

// konstruktorit, getterit, setterit
}

Ylläolevassa esimerkissä Varaus-luokka on määritelty siten, että se liittyy tietokantatauluun Varaus. Sillä on lisäksi kenttä pyora, joka viittaa tauluun, johon Pyora-luokka liittyy. Luokalle Varaaja ei ole määritelty toiminnallisuutta.

Varauksiin liittyvien pyörien tulostaminen onnistuu nyt seuraavasti:

ConnectionSource connectionSource
        = new JdbcConnectionSource("jdbc:sqlite:vuokraamo.db");

Dao<Varaus, String> varausDao
        = DaoManager.createDao(connectionSource, Varaus.class);

List<Varaus> varaukset = varausDao.queryForAll();
for (Varaus varaus : varaukset) {
    System.out.println(varaus.getPyora().getRekisterinumero() + ", alkaa " + varaus.getVarausAlkaa());
}
Java Persistence API ja Hibernate

Vastaavanlaista toiminnallisuutta tarjoavia kirjastoja on huomattava määrä. Esimerkiksi Ruby on Railsille löytyy ActiveRecord ja NodeJS:lle löytyy Sequelize. Java-maailmassa vastaavia kirjastoja on niin monia, että niille on ehditty määrittelemään standardikin, mikä (saattaa) helpottaa kirjastojen käyttöä ja vaihtamista.

Javalle määritellyn standardin nimi on Java Persistence API, ja se määrittelee notaation luokkien annotoinnille ja kyselyiden kirjoittamiselle.

Edellisissä esimerkeissä käyttämämme ORMLite-kirjasto tarjoaa vain pienen määrän toiminnallisuutta, mutta samalla oleelliset osat on toteutettu melko hyvin. Jos toiminnallisuutta kaipaa enemmän, voi käyttöön valita esimerkiksi Hibernaten, joka on ehkäpä eniten käytetty vastaavaa toiminnallisuutta tarjoava Java-kirjasto.

Jotain muuta kuin relaatiomallia noudattavat tietokannat

Relaatiomalli ja SQL ovat hyvin ilmaisuvoimainen kombinaatio ja relaatiotietokannoilla pystytään ainakin teoriassa hoitamaan lähes kaikki mahdolliset tiedonhallintatarpeet. Relaatiotietokannat dominoivatkin yhä tietokantaskeneä. 2000-luvulla alkoi kuitenkin nousta esiin uudentyyppisiä tietokantaratkaisuja, joita kuvaamaan lanseerattiin vuonna 2009 termi NoSQL.

Syitä uusien tietokantaratkaisujen syntyyn

Motivaatiota NoSQL-tietokantojen syntyyn oli muutamia. Ehkä tärkeimpänä tekijänä olivat massiivisen skaalan internetpalveluiden, esim. Amazonin ja Googlen käsittelemät tietomäärät, jotka edellyttävät tiedon hajautettua tallentamista ja käsittelyä. Relaatiomallia oli mahdotonta saada skaalautumaan palveluiden tarpeeseen ja yhtiöt kehittivät omia, aivan uudenlaisia tietokantaratkaisuja. Yhteistä näille oli se, että ne skaalautuivat hyvin, eli niiden suorituskyky oli mahdollista pitää riittävällä tasolla liittämällä tietokantaan uusia "koneita" kuormituksen kasvaessa, ja myös se, että toiminnallisuudeltaan ratkaisut olivat paljon rajoittuneempia kuin relaatiotietokannat.

Useimmat uudet tietokantaratkaisut tarjoavat paljon suppeamman kyselykielen kuin SQL ja ne eivät tue ACID-ominaisuuksia takaavia transaktioita. Hyvin tavanomaista uusissa tietokannoissa on se, että ne eivät yritäkään tarjota samanlaista ajantasaisuutta kuin relaatiotietokannat, eli sen sijaan, että kaikki kannan käyttäjät näkisivät tietokannan tilan koko ajan samanlaisena (ACID:in C eli consistency), on käytössä eventual consistency -malli, jossa periaatteena on, että jokainen tietokantaan tehty muutos näkyy kaikille käyttäjille ennemmin tai myöhemmin, mutta ei välttämättä heti. Käytännössä jonkun aikaa tilanne voi olla se, että tietokannan eri käyttäjät näkevät tietokannan tilan hieman erilaisena. Jos ajatellaan monia internetpalveluita täydellinen konsistenssi ei ole kaikkien operaatioiden suhteen välttämätöntä. Ei esim. haittaa vaikka yksittäisen käyttäjän Facebook-päivitykset eivät ilmesty kaikille käyttäjille aivan samalla hetkellä.

Toisena vahvana motivaationa uusien tietokantamallien kehittymiselle oli tarve joustavammille tavoille tallettaa erimuotoista dataa. Relaatiomalli nojaa vahvasti siihen että kannan skeema, eli taulut ja taulujen sarakkeet on ennalta määritelty. Jos syntyy usein tarve tallettaa uudenlaista dataa, esim. tauluihin tulee viikoittain uusia sarakkeita, tai jopa uudenlaisia tauluja, on relaatiomalli kankeahko. Toisaalta myös tarve tallettaa jokainen "asia" omaan tauluunsa tekee relaatiomallista kankean ja kyselyllisestikin raskaan tiettyihin käyttötarkoituksiin. Lääkkeenä näihin ongelmiin on syntynyt tietokantaratkaisuja, joissa datan skeema on huomattavasti löyhemmin määritelty kuin relaatiomallissa. Monissa uusissa tietokantatyypeissä data on tietokannan kannalta jopa täysin skeematonta, eli "tauluihin" voi tallettaa vapaamuotoista dataa, ja vastuu tiedon muodon oikeellisuudesta on siirretty täysin tietokannan käyttäjäjälle.

Erityyppiset NoSQL-tietokannat

Kaikki relaatiotietokannat ovat enemmän tai vähemmän samanlaisia ja tarjoavat standardoidun tavan eli SQL:n tietojen kyselyyn, ylläpitoon sekä tietokantaskeemojen muokkaukseen. NoSQL-tietokantojen kohdalla tilanne on täysin erilainen, ne ovat tiedon organisaatiotavoiltaan hyvinkin erilaisia ja mitään SQL:ää vastaavaa standardoitua kyselykieltä ei ole, kaikilla NoSQL-tietokannoilla on oma tapansa kyselyjen muodostamiseen.

NoSQL-tietokannat voidaan jakaa tiedon organisointitapansa perusteella neljään eri luokkaan:

  • avain-arvotietokantoihin (engl. key value databases),
  • dokumenttitietokantoihin (engl. document databases),
  • saraketietokantoihin (engl. columnar databases) ja
  • verkkotietokantoihin (engl. graph databases)

Tarkastellaan nyt erilaisia NoSQL-tietokantoja hieman tarkemmin.

Avain-arvotietokannat, esim. Redis

Avain-arvotietokannat tarjoavat erittäin rajoitetun tietomallin. Kantaan talletetaan arvoja sekä arvon yksilöiviä avaimia. Tietokannan suhteen talletettavilla arvoilla ei ole (yleensä) mitään skeemaa eli rakennetta. Sovellusten on tulkittava kantaan talletettavat arvot haluamallaan tavalla esim. tietyn tyyppisenä oliona. Koska tietokanta on täysin skeematon, eivät avain-arvotietokannat tue viitteitä kantaan talletettujen arvojen välillä, eli mitään relaatiotietokantojen liitosta vastaavaa käsitettä ei avain-arvotietokannoilla ole.

Avain-arvotietokantojen tarjoamat kyselymahdollisuudet ovat erittäin rajoittuneet, yleensä on ainoastaan mahdollista hakea kannasta tiettyä avainta vastaava arvo.

Tarkastellaan nyt Redisiä joka on eräs suosituimmista avain-arvotietokannoista.

Redisin perusoperaatiot ovat set, get ja del joiden avulla käsitellään merkkijonomuotoisena talletettavia arvoja.

Seuraavassa esimerkissä käynnistetään Redis-konsoli, asetetaan arvo avaimille arto, aino ja olli. Haetaan kannasta muutamaa avainta vastaavia tietoja ja tuhotaan avaimeen arto liittyvä arvo.

melkki$ redis-cli
127.0.0.1:6379> set arto "olen arto 29 vuotta, yliopisto-opettaja"
OK
127.0.0.1:6379> set aino "olen aino 21 vuotta, pajaohjaaja"
OK
127.0.0.1:6379> set olli "olen olli 19 vuotta, fuksi"
OK
127.0.0.1:6379> get pekka
(nil)
127.0.0.1:6379> get arto
"olen arto 29 vuotta, yliopisto-opettaja"
127.0.0.1:6379> del arto
127.0.0.1:6379> get arto
(nil)
127.0.0.1:6379> get aino
"olen aino 21 vuotta, pajaohjaaja"
127.0.0.1:6379>

Redis on siis erittäin yksinkertainen ja toimii oikeastaan hyvin samaan tapaan kuin Javan HashMap sillä erotuksella, että Redisiin ei voi helposti tallentaa normaaleja oliota, ja että Redisiin tallennetut arvot säilyvät vaikka ohjelma uudelleenkäynnistettäisiin.

Redis tajoaa tuen myös arvoille jotka ovat lukuja, joukkoja tai hashejä eli itsessään avain-arvo-pareja.

Mitä järkeä avain-arvotietokannoissa on? Ne vaikuttavat ominaisuuksiltaan erittäin rajoittuneilta ja relaatiotietokannoilla pystyy tekemään varmasti kaikki ne asiat, joihin avain-arvotietokannat pystyvät. Rajoituksistaan johtuen avain-arvotietokannat ovat kuitenkin suorituskyvyltään ja skaalautuvuudeltaan huomattavasti parempia kuin relaatiotietokanta, ja niiden avulla pystytään kuitenkin ratkaisemaan monia sovellusten käyttötarpeita.

Viime aikoina on kuitenkin ollut nousussa trendi, jonka nimitys englanniksi on polyglot persistance, joka tarkoittaa suurinpiirtein sitä, että sovelluksessa on useita erityyppisiä tietokantoja ja kuhunkin käyttötarkoitukseen käytetään tarkoituksenmukaisinta ratkaisua.

Eräs hyvin yleinen käyttötarkoitus avain-arvotietokannoille on raskaiden operaatioiden tulosten väliaikainen talletus (engl. caching) mahdollisia uusia saman operaatioiden suorituksia varten.

Tarkastellaan tästä estimerkkinä internetistä Open Weather API:sta eri kaupunkien säätietoja hakevaa ohjelmaa. Ohjelma toiminta näyttää seuraavalta:

kaupunki: helsinki
few clouds, temperature 15.770000000000039 celcius
kaupunki: turku
Sky is Clear, temperature 16.0 celcius
kaupunki: vladivostok
scattered clouds, temperature 11.360000000000014 celcius
kaupunki:

Jokaisen kaupungin kohdalla ohjelma hakee kaupungin säätiedot internetistä. Tiedon haku verkosta on kuitenkin hidas ja resurssien kulutuksen suhteen "kallis" operaatio (asialla voisi olla merkitystä jos ohjelmallamme olisi satoja tai tuhansia yhtäaikaisia käyttäjiä). Koska säätiedot pysyvät suunnilleen samana useiden minuuttien ajan, ohjelmaa voi optimoida siten, että kun käydään kerran hakemassa jonkun kaupungin säätiedot, talletetaan tieto joksikin aikaa Redisiin. Jos kaupungin säätä kysytään pian uudelleen, saadaan vastaus nopeasti ilman kallista internetoperaatiota. Noudatetaan siis näytettävien säätietojen suhteen eventual consistency -mallia.

Seuraavassa sääpalvelun toteuttavan luokan WeatherService toteutus, joka hyödyntää Jedis-kirjastoa Redis-operaatioiden tekemiseen:

import redis.clients.jedis.Jedis;

public class WeatherService {
    private Jedis jedis;

    public WeatherService() {
        // luodaan yhteys paikallisen koneen Redisiin
        jedis = new Jedis("localhost");
    }

    public void weatherOf(String city) throws Exception {
        // kutsutaan metodia, joka hakee tiedot joko
        // Redisistä tai internetistä
        JsonElement weatherData = getDataFor(city);

        // haetaan vastauksen sisältä oikeat osat
        double temperature = getTemperatureFrom(weatherData);
        String desc = getDescriptionFrom(weatherData);

        System.out.println(desc + ", temperature "+temperature+ " celcius");
    }

    // metodi joka hakee tiedot joko Redisistä tai internetistä
    private JsonElement getDataFor(String city) throws Exception {
        // etsitään kaupungin city säätietoja Redisistä
        String weatherInfo = jedis.get(city);

        // jos ei löytyny
        if (weatherInfo==null) {
            // haetaan tiedot internetistä
            weatherInfo = readFromUrl("http://api.openweathermap.org/data/2.5/weather?q="+city);

            // ja talletetaan ne Redisiin
            jedis.set(city, weatherInfo);
            // määritellään tallennusajaksi 10 minuuttia eli 600 sekuntia
            jedis.expire(city, 600);
        }

        // palautetaan tuote oikein muotoiltuna
        return new JsonParser().parse(weatherInfo);
    }

    // apumetodeja...
}

Palvelua käytetään seuraavasti:

WeatherService weather = new WeatherService();
weather.weatherFor("Helsinki");

Kun haemme ensimmäistä kertaa esim. Helsingin tietoja, etsitään niitä (metodissa getDataFor) ensin rediksestä:

// nyt city = "Helsinki"
String weatherInfo = jedis.get(city);

tiedot eivät löydy, joten metodi palauttaa null. Tämän takia mennään if-haaraan, jossa tiedot haetaan apumetodin avulla internetistä. Haetut tiedot talletetaan ensin Redisiin:

// nyt city="Helsinki" ja weatherInfo Helsingin sään 'raakadata'
jedis.set(city, weatherInfo);

talletetulle datalle asetetaan myös elinaika sekunteina:

jedis.expire(city, 600);

tämän jälkeet data palautetaan kutsujalle.

Jos Helsingin säätietoja haetaan 600 sekunnin sisällä uudelleen, löytyvät tiedot suoraan Redisistä. 600 sekunnin kuluttua hakuoperaatio jedis.get('Helsinki') palauttaa jälleen null ja tuore säätilanne haetaan internetistä.

Ohjelman koodi kokonaisuudessan löytyy GitHubista

Lista suosituimmista avain-arvotietokannoista.

Dokumenttitietokannat, esim. MongoDB

Dokumenttitietokantojen voi ajatella sijoittuvan jonnekin relaatiotietokantojen ja avain-arvotietokantojen puolen välin tienoille. Dokumenttikannat perustuvat avain-arvotietokantojen tapaan arvojen tallettamiseen avaimen perusteella. Arvot tai dokumentit kuten niitä dokumenttikantojen kontekstissa nimitetään voivat kuitenkin olla itsessään hyvin monimutkaisia oliota, jotka sisältävät kenttiä, joiden arvona voi olla joko normaaleja arvoja kuten lukuja ja merkkijonoja tai muita olioita. Toisin kuin avain-arvotietokannoissa, dokumenttikannat "näkevät" tietokantaan talletettujen dokumenttien sisään, ja mahdollistavat talletettujen dokumenttien sisällön suhteen tehdyt kyselyt.

Käytetään seuraavassa esimerkkinä ylivoimaisesti suosituimman dokumenttitietokannan MongoDB:n merkintöjä.

Dokumenttikannoissa käytetään tiedon loogisena esitysmuotona yleensä JSON:ia. Seuraavassa kurssia Ohjelmoinnin perusteet esittävä JSON-dokumentti:

{
  "id": ObjectId("10"),
  "nimi": "Ohjelmoinnin perusteet",
  "laajuus": 5,
  "luennot": [ "Arto Vihavainen", "Matti Luukkainen" ]
}

JSON-dokumentti koostuu avain-arvo-pareista. Avainta vastaava arvo merkitään kaksoispisteellä erotettuna avaimen yhteyteen.

Kurssi-dokumentissa on siis neljä avain-arvo-paria. Voidaankin ajatella että kurssilla on neljä kenttää. Näistä kentistä erikoisasemassa on MongoDB:n dokumentille automaattisesti generoima avainkenttä id jonka arvo on tyypiltään ObjectId. Poikkeavaa relaatiotietokantoihin nähden on se, että kentän arvona voi olla taulukko.

Seuraavassa on opiskelijaa kuvaava dokumentti:

{
  "id" : ObjectId("59"),
  "nimi" : "Pekka Mikkola",
  "opiskelijanumero" : 14112345,
  "osoite" : {
                "katu" : "Tehtaankatu 10 B 1",
                "postinumero" : "00120",
                "postitoimipaikka" : "Helsinki"
             }
}

Nyt kentän osoite arvona on olio, jolla on itsellään omat kenttänsä.

Dokumenttitietokannassa dokumentit on lajiteltu kokoelmiin (engl. collection). Kokoelman merkitys on suunnilleen sama kuin taulun relaatiotietokannassa. Yhdessä kokoelmassa olevien dokumenttien ei kuitenkaa tarvitse olla kentiltään samanlaisia. Kenttiä voi olla vaihteleva määrä ja saman nimiset kentät voivat sisältää eri dokumenteilla eri tyyppisen arvon. Kokoelmille ei määritellä dokumenttikannoissa minkäänlaista skeemaa, eli on täysin sovellusten vastuulla, että kantaan talletetaan järkevää dataa, ja että kannasta luettava data tutkitaan oikein.

Kuten edellä opiskelijan kohdalla näimme, on dokumenttikannoissa mahdollista sisällyttää olioita toistensa sisään. Tilanne olisi myös voitu mallintaa "relaatiomallin tapaan" siten, että osoitteita varten olisi oma kokoelmansa, ja yksittäinen osoite mallinnettaisiin omana dokumenttina:

{
  "id" : ObjectId("123"),
  "katu" : "Tehtaankatu 10 B 1",
  "postinumero" : "00120",
  "postitoimipaikka" : "Helsinki"
}

Opiskelijadokumentti sisältäisi nyt ainoastaan viitteen osoitedokumenttiin:

{
  "id" : ObjectId("59"),
  "nimi" : "Pekka Mikkola",
  "opiskelijanumero" : 14112345,
  "osoite" : ObjectId("123")
  }

Toisin kuin relaatiotietokantojen tapauksessa, dokumenttikannat eivät tarjoa tietokannan tasolla tapahtuvia liitosoperaatiota, ja edellisen esimerkin tapauksessa sovelluksen olisi itse huolehdittava siitä, että opiskelijaa haettaessa haetaan myös opiskelijan osoite tietokannasta.

Vaikka operaatio ei olekaan dokumenttikannan tasolla tuettu, on olemassa monia kirjastoja (esim. Javalle Morphia), jotka toteuttavat ohjelmallisen liitosoperaation siten, että sovellusohjelman ei tarvitse siitä huolehtia.

Relaatiotietokannoissa kannan skeeman muodostaminen on sikäli helppoa, että normalisoituun ratkaisuun pyrittäessä useimmissa tilanteissa on olemassa noin yksi "järkevä" ratkaisu, joka toimii lähes yhtä hyvin riippumatta siitä miten kantaa käytetään.

Dokumenttikantojen suhteen tilanne on toinen. Tarkastellaan esimerkiksi Kursseja ja Opiskelijoiden kurssisuorituksia. Relaatiotietokannassa tilanne olisi suoraviivainen, Suoritus olisi Kurssin ja Opiskelijan liitostaulu.

Eräs mahdollisuus olisi tehdä täsmälleen sama ratkaisu dokumenttikannassa.

Kokoelma Opiskelija:

[
  {
    "id": ObjectId("10"),
    "nimi" : "Lea Kutvonen",
    "opiskelijanumero" : 13457678
  },
  {
    "id": ObjectId("11"),
    "nimi" : "Pekka Mikkola",
    "opiskelijanumero" : 14012345
  }
]

Kokoelma kurssi:

[
  {
    "id": ObjectId("34"),
    "nimi" : "Ohjelmoinnin perusteet",
    "laajuus" : 5
  },
  {
    "id": ObjectId("35"),
    "nimi" : "Tietokone työvälineenä",
    "laajuus" : 1
  }
]

Suoritus olisi nyt "liitostaulumainen" kokoelma:

[
  {
    "id": 55
    "kurssi_id" : ObjectId("34"),
    "opiskelija_id" : ObjectId("10"),
    "arvosana" : 4
  },
  {
    "id": 56
    "kurssi_id" : ObjectId("35"),
    "opiskelija_id" : ObjectId("10"),
    "arvosana" : 5
  },
  {
    "id": 57
    "kurssi_id" : ObjectId("35"),
    "opiskelija_id" : ObjectId("11"),
    "arvosana" : 2
  }
]

Vaihtoehtoja on kuitenkin myös muita. Käyttötapauksista riippuen saattaisi olla edullista tallettaa tieto suorituksista ("liitosdokumentin" id) myös kurssin ja opiskelijan yhteyteen:

Kokoelma Opiskelija:

[
  {
    "id": ObjectId("10")
    "nimi" : "Lea Kutvonen",
    "opiskelijanumero" : 13457678,
    "suoritukset" : [ ObjectId("55"), ObjectId("56") ]
  },
  {
    "id": ObjectId("11")
    "nimi" : "Pekka Mikkola",
    "opiskelijanumero" : 14012345,
    "suoritukset" : [ ObjectId("57") ]
  }
]

Kokoelma kurssi:

[
  {
    "id": ObjectId("34")
    "nimi" : "Ohjelmoinnin perusteet",
    "laajuus" : 5,
    "suorittajat" : [ObjectId("10")]
  },
  {
    "id": ObjectId("35")
    "nimi" : "Tietokone työvälineenä",
    "laajuus" : 1,
    "suorittajat" : [ObjectId("10"), ObjectId("11")]
  }
]

Jossain tapauksessa paras ratkaisu olisi luopua liitoksena toimivista dokumenteista eli kokoelmasta suoritukset ja tallettaa suoritukset kokonaisuudessaan opiskelija-dokumentteihin:

[
  {
    "id": ObjectId("10")
    "nimi" : "Lea Kutvonen",
    "opiskelijanumero" : 13457678,
    "suoritukset" : [
      {
        "id": 55
        "kurssi_id" : ObjectId("34"),
        "arvosana" : 4
      },
      {
        "id": 56
        "kurssi_id" : ObjectId("35"),
        "arvosana" : 5
      }
    ]
  },
  {
    "id": ObjectId("11")
    "nimi" : "Pekka Mikkola",
    "opiskelijanumero" : 14012345,
    "suoritukset" : [
      {
        "id": 57
        "kurssi_id" : ObjectId("35"),
        "arvosana" : 2
      }
    ]
  }
]

Tämä ratkaisu vaikeuttaisi kurssin suorittajien selvittämistä, joten joissain käyttötapauksissa saattaisi olla edullista sisällyttää suoritukset molempiin opiskelijoihin ja kurssiin.

Yhtä "oikeaa" vastausta miten sovelluksen data kannattaa mallintaa dokumenttikannan kokoelmiksi ja dokumenteiksi ei ole olemassa. Parhaaseen tapaan vaikuttaa suuresti se minkälainen käyttöprofiili rakennettavalla sovelluksella on: datamalli kannattaa valita siten, että se tekee yleisimpien operaatioiden suorituksen nopeaksi ja helpoksi.

Kuten jo totesimme, dokumenttikannat eivät tue liitosoperaatioita, ja kyselyt kohdistuvat aina vain yhteen kokoelmaan. Dokumenttikannoilla ei ole mitään standardoitua kyselykieltä, jokaisen kannan kyselykieli on täysin omanlaisensa. Esim. MongoDB:n kyselykieli ei muistuta kovinkaan läheisesti SQLää.

Dokumenttikannat eivät myöskään tue useamman kokoelman yhtäaikaista muuttamista transaktionaalisesti. Kaikki yhteen kokoelmaan suoritettavat tapahtumat tehdään kuitenkin aina transaktionaalisesti.

Lisää MongoDB:stä ja sen käytöstä eri ohjelmointikielistä käsin löydät esim. osoitteesta https://docs.mongodb.org/manual/

Lista suosituimmista dokumenttitietokannoista.

Saraketietokannat

Relaatiomalli sopii suhteellisen hyvin tilanteisiin, joissa tietoa käsitellään lyhyin, pääasiassa taulun yksittäisiin riveihin kohdistuvin operaatioin (englanniksi tälläisestä tiedonkäsittelystä käytetään nimitystä online transaction processing, OLTP). Näin tapahtuu esimerkiksi pankin asiakastietokannassa kun asiakkaat tekevät saldokyselyjä, nostavat rahaa tai tekevät tilisiirtoja.

Tietokantojen käyttö on aivan erilaista silloin kun tavoitteena on luoda raportteja tai analysoida dataa eri tavoin, esim. selvittää pankin asiakkaiden keskimääräinen saldo tietyllä aikavälillä. Tällöin kyselyt kohdistuvat lähes koko tauluun, mutta usein vain pieneen osaan taulun sarakkeissa (englanniksi tälläisestä tiedonkäsittelystä käytetään nimitystä online analytical processing, OLAP).

Analyysitietokannoissa tilanne on usein se, että tieto ei ole normalisoidussa muodossa, yksittäiset taulut saattavat sisältää satojakin sarakkeita, mutta toisaalta läheskään kaikilla sarakkeilla ei ole kannassa arvoja. Näissä tilanteissa relaatiotietokantojen suorituskyky saattaa olla huono, ja saraketietokannat (engl. columnar databases) voivat tarjota huomattavasti paremman vaihtoehdon.

Tarkastellaan tilannetta esimerkin kautta. Oletetaan, että analyysiin käytettyyn tietokantaan on talletettu firman työntekijöitä:

EmpId Lastname  Firstname Sex Salary  YearsEmployed
10    Smith     Joe       M   40000   1
12    Jones     Mary      F   50000   6
11    Johnson   Cathy     F   44000   3
22    Jones     Bob       M   55000   9

Relaatiotietokannat tallettavat tiedon levylle riveittäin, eli taulu tallentuisi levylle seuraavasti:

10;Smith;Joe;M;40000;1;12;Jones;Mary;F;50000;6;11;Johnson;Cathy;F;44000;3;...

Jos nyt haluttaisiin selvittää yrityksessä vähintään 5 vuotta työskennelleiden keskipalkka, tehtäisiin kysely

SELECT AVG(Salary)
  FROM Employees
  WHERE YearsEmployed > 4

Tässä olisi relaatiotietokannan tapauksessa luettava taulun koko data levyltä siitä huolimatta, että kysely ei tarvitse kuin pientä osaa taulun datasta. Jos taulussa olisi satoja sarakkeita (mikä on varsin tyypillistä analytiikkatietokannoissa), olisi kyselyn tekeminen erittäin hidasta johtuen juuri tarpeettoman raskaasta, kaiken datan hakevasta levyoperaatiosta.

Saraketietokannoissa tiedot talletetaan sarakkeittain, karkeasti ottaen jokainen sarake tai usein yhdessä käytettyjen sarakkeiden ryhmä omaan tiedostoonsa. Edellinen tietokanta siis talletettaisiin kutakuinkin seuraavasti

EmpId: 10;12;11;22

Lastname:Smith;Jones;Johnson;Jones

Firstname:Joe;Mary;Cathy;Bob

Sex:M;F;F;M

Salary:40000;50000;44000;55000

YearsEmployed:1;6;3;9

Tehtäessä sama kysely, riittäisi että levyltä luettaisiin ainoastaan kyselyn kannalta tarpeellisten sarakkeiden Salary ja YearsEmployed tieto. Jos sarakkeita olisi suuri määrä, ero riveittäin talletettuun tietokantaan olisi suorituskyvyn suhteen huomattava.

Vanhemmman sukupolven saraketietokannoissa data on organisoitu relaatiotietokantojen tapaan tauluihin ja dataa hallitaan SQL:llä. Vanhemman polven saraketietokantoja ei välttämättä edes luokitella NoSQL-kannoiksi. Uudemman polven saraketietokannat taas noudattavat enemmän yhden tai muutaman ison tai "leveän" taulun skeematonta mallia. Tauluissa on sarakkeita erittäin suuri määrä, mutta läheskään kaikilla sarakkeilla ei ole arvoa. Näiden esikuvana on Googlen vuodesta 2004 asti kehittämä BigTable. Uuden polven ratkaisut mahdollistavat massiivisten datamäärien rinnakkaiskäsittelyn.

Suosituimmat uuden sukupolven saraketietokannat.

Verkkotietokannat

Relaatiotietokannat ja esittelemämme NoSQL-kantatyypit keskittyvät dataentiteettien esittämiseen. Relaatiotietokannat esittävät entiteetit taulujen riveinä, esim. Henkilö-taulussa jokainen ihminen esitettään omana rivinään. Yhteydet ja suhteet eri entiteettien välillä esitetään epäsuorasti vierasavaimien ja liitostaulujen avulla. Itse yhteys, esim. missä henkilö Arto on töissä saadaan selville vasta kyselyn aikana tapahtuvan liitosoperaation avulla.

Joissain tilanteissa entiteettien suhteiden selvittäminen relaatiotietokannassa saattaa olla erittäin hankalaa. Oletetaan, että meillä on Henkilöitä kuvaava taulu:

CREATE TABLE Henkilo (
  id integer not null PRIMARY KEY,
  nimi string not null
)

sekä taulu, joka liittää vanhemmat ja lapset toisiinsa:

CREATE TABLE Vanhemmuus (
  id integer not null PRIMARY KEY,
  lapsi_id integer,
  vanhempi_id integer,
  FOREIGN KEY (lapsi_id) references Henkilo(id),
  FOREIGN KEY (vanhempi_id) references Henkilo(id)
)

Jos nyt haluaisimme selvittää henkilön "Arto" kaikki sukulaiset, huomaamme, että kyselyn tekeminen SQL:llä olisi erittäin hankalaa.

Tilanne mutkistuisi entisestään jos haluaisimme kuvata myös muunlaisia suhteita, esim. henkilöiden työsuhteita firmoihin, jäsenyyksiä yhdistyksiin, ystävyyttä, omistussuhteita erilaisiin asioihin sekä asioista tykkäämisiä ja vihaamisia. Yksi vaikeuttava tekijä olisi se, että kaikki erilaiset suhteet pitäisi mallintaa omina liitostauluinaan. Jos ohjelmassa käytettävät suhdetyypit lisääntyisivät, tulisi tietokantaskeemaan lisätä koko ajan uusia erityyppisiä liitostauluja. Myös kyselyt muuttuisivat koko ajan hankallimmaksi ja vaatisivat yhä monimutkaisempia, raskaita liitosoperaatioita. Esim. seuraavien asioiden selvittäminen olisi SQL:llä melko työlästä:

  • Arton kaikkien esivanhempien työpaikat
  • Kirjat joista Arton esivanhemmat pitivät
  • Arton ystävistä ja ystävien ystävistä, ja näiden ystävistä jne kaikki ne, jotka ovat opiskelleet samassa paikassa kun Arto

Ratkaisun tämänkaltaisiin tilanteisiin tuovat verkkotietokannat, jotka mallintavat eksplisiittisesti sekä entiteetit eli esim. henkilöt ja niiden ominaisuudet että entiteettien väliset suhteet kuten sukulaisuuden henkilöiden välillä. Kuten nimestä voi päätellä, on verkkotietokannan pohjalla olevana tietorakenteena verkko (engl. graph), joka koostuu entiteettejä kuvaavista solmuista (engl. node) ja niiden välisiä suhteita kuvaavista kaarista (engl. edge). Sekä solmuilla, että kaarilla voi olla attribuutteja. Verkko, joka kuvaa yllä olevan esimerkin mallintamista verkkotietokannan solmuiksi ja kaariksi:

Verkkotietokanta

Verkkotietokannat tarjoavat kyselykielen, jonka avulla on helppo "navigoida" verkossa. Toisin kuin relaatiotietokannoissa, jotka edellyttävät yhteyden muodostamiseen laskennallisesti kallista join-operaatiota, yhteyksien navigointi verkkotietokannassa on nopeaa. Verkkotietokannoille ei ole olemassa yhtä vakiintunutta kyselykieltä. On kuitenkin tiettyjä kyselykieliä, kuten tämän hetken suosituimman verkkotietokannan Neo4J:n käyttämä Cypher, joita jotkut muutkin verkkotietokannat tukevat.

Seuraavassa muutama esimerkki ylläolevaan verkkotietokantaan kohdistetuista Cypherillä tehdyistä kyselyistä. Haetaan ensin Arton vanhemmat

MATCH ({ name:"Arto" }) -[:CHILD_OF]-> (parent)
RETURN parent

MATCH-määre hakee ensin solmun, jonka nimenä on Arto ja sen jälkeen seurataan kaarta :CHILD_OF pitkin solmun vanhempiin, jotka kysely palauttaa. Kysely siis palauttaa ne solmut parent joille pätee ehto: solmuun johtaa kaari CHILD_OF sellaisesta solmusta johon liittyy attribuutti nimi, jonka arvo on Arto.

Kirjat joista Arton esivanhemmat pitävät:

MATCH ({ name:"Arto" }) -[:CHILD_OF*1..]-> (relative) -[:LIKES]-> (book:Book)
RETURN book

Nyt kysely palauttaa sellaiset solmut book joille pätee:

  • solmun tyyppi on Book
  • solmuun on :LIKES-tyyppinen kaari jostain solmusta johon päästään Artosta yhtä tai useampaa :CHILD_OF kaarta pitkin kulkemalla

Arton ystävistä ja ystävien ystävistä, ja näiden ystävistä jne kaikki ne, jotka ovat opiskelleet samassa paikassa kun Arto:

MATCH (arto: { name:"Arto" }) -[:FRIENDS_WITH*1..]-> (friend) -[:STUDIED_IN]-> (school)
WHERE arto -[:STUDIED_IN]-> (school)
RETURN friend

Vielä yksi esimerkki. Miten löytäisimme lyhimmän ystävien ketjun, joka yhdistää Arton ja Barack Obaman?

MATCH (arto: { name:"Arto" }) (barack:{ name:"Barack Obama" })
p = shortestPath( (arto) -[:FRIEND*1..]-> (barack) )

RETURN p

Eli ensin etsitään solmut joiden nimenä on Arto ja Barack, ja sen jälkeen Neo4J:n valmis funktio shortestPath etsii lyhimmän polun solmujen välillä. Tämä kysely olisi todennäköisesti mahdoton tehdä SQL:llä tai ainakin äärimmäisen vaikea muotoilla ja todella hidas suorittaa. Verkkotietokannat sopivatkin erittäin hyvin muutamiin sellasiiin käyttöskenaarioihin, joissa muut tietokantatyypit ovat lähes käyttökelvottomia. Verkkotietokantojen käyttö onkin yleistynyt esim. sosiaalisen median sovelluksissa ja suosittelujärjestelmissä.

Suosituimmat verkkotietokannat.

NOSQL ja NewSQL

NoSQL-tietokannat löivät läpi suuren kohun saattamina ja erityisesti startupeissa oli muodikasta ottaa käyttöön helpommin suurille käyttäjämäärille skaalautuvia NoSQL-kantoja kuten MongoDB. Pikkuhiljaa kohu on laantunut, ja enenevissä määrin ollaan menossa jo aiemmin mainittuun polyglot persistancen nimellä kulkevaan suuntaan, eli valitaan oikea työkalu kuhunkin käyttötarkoitukseen, ja erittäin tyypillistä onkin että jo hieman suuremmissa sovelluksissa on käytössä dokumentti-, avain-arvo- ja relaatiotietokanta.

Uusimpana kehityssuuntana on ollut myös se, että vanhat relaatiotietokannat ovat ottaneet vaikutteita muista tietokantatyypeistä. Esim. tämän hetken suosituin Open Source -relaatiotietokanta PostgeSQL sisältää paljon dokumenttitietokantoja vastaavaa toiminnallisuutta. Kehitystä on tapahtunut myös toiseen suuntaan, jotkut dokumenttitietokannat ovat mahdollistaneet SQL:n käytön kyselykielenä.

Kahtiajaon hieman liudennuttua termin NoSQL sijaan onkin alettu puhua Not Only SQL -tietokannoista, ja termi on muokkautunut muotoon NOSQL. Päätään nostaa esille myös vielä melko epämääräisesti määritelty termi NewSQL. Wikipedian mukaan NewSQL:llä tarkoittaa seuraavaa:

NewSQL is a class of modern relational database management systems that seek to provide the same scalable performance of NoSQL systems for online transaction processing (OLTP) read-write workloads while still maintaining the ACID guarantees of a traditional database system.

Although NewSQL systems vary greatly in their internal architectures, the two distinguishing features common amongst them is that they all support the relational data model and use SQL as their primary interface.

Eräs melko paljon huomiota saanut NewSQL-tietokanta on vuonna 2015 Applen ostama FoundationDB, joka sisäiseltä organisoinniltaan on avain-arvotietokanta ja tarjoaa perinteistä relaatiotietokantaa skaalautuvamman ratkaisun, mutta tarjoaa kyselykieleksi (myös) SQL:n ja ACID-ominaisuudet takaavat transaktiot eli käyttäytyy sovellusohjelmien kannalta kuten normaali relaatiotietokanta.

Viimeinen tehtävä käsittelee koko kurssin aihepiiriä ja on laajempi, kertaava tehtävä.

Tehtävässä tulee luoda pieni foorumi, missä on viestiketjuja. Jokaisella viestiketjulla on aloitusviesti sekä aloitusviestin jälkeen lähetetyt viestit. Kun käyttäjä menee foorumille, näkee hän listauksessa viestiketjujen aloitusviestit sekä viestiketjun koon.

  
  Viestiketju                      Viestejä
  Java on jees!                    8	
  Python on jeesimpi!              6	
  LISP on parempi kuin...          2		
  Ohjelmointikielet on turhia..    1	
  ...

  Lisää viestiketju:

  [käyttäjä voisi aloittaa uuden viestiketjun] 
  

Viestiketjut on järjestetty viimeiseksi aktiivisena olleen viestiketjun mukaan. Listauksessa ylimpänä on siis aina se viestiketju, minkä viimeisin viesti on tuorein. Sivulla tulee näyttää korkeintaan 100 uusinta viestiketjua.

Yksittäiseen viestiketjuun taas käsiksi valitsemalla viestiketjun listalta. Esimerkiksi ylläolevalta listalta voisi valita keskustelun "Java on jees!", jolloin viestiketjun sisältämät viestit näytetään uudella sivulla:

  Viestiketju: Java on jees

  Mun mielestä Java on just hyvä kieli. t. Arto
  No eipäs, Ruby on parempi. t. Matti
  Ada on selkeästi parempi kuin kumpikin noista. t. Ada
  ...

  Lisää viesti: 

  [käyttäjä voisi kirjoittaa viestin..]
  

Tehtävän tekeminen kannattaa aloittaa yksittäisen viestiketjun toteuttamisesta, eli siitä, että käyttäjä voi lisätä järjestelmään viestejä. Tämän jälkeen viestit voi kytkeä johonkin viestiketjuun, jonka jälkeen viestiketjuille lluodaan listaus sovelluksen "etusivulle". Tämän jälkeen kannattaa hahmotella viestien lukumäärän laskemista etusivua varten sekä viestien järjestämistä.

Huom! Käytä tehtävässä tietokannanhallintajärjestelmänä SQLiteä ja toteuta sovelluksen tarvitsema tietokanta projektin kansion "db" sisältämään "forum.db"-tiedostoon.

Koeasiaa

Kurssikokeen aikataulu löytyy kurssin etusivulta.

Kokeeseen saa ottaa mukaan "lunttilapun". Lunttilappu on kaksipuolinen, A4-kokoinen, itse käsin täytetty paperi.

Aiemmissa tietokantojen perusteiden kurssikokeissa ovat esiintyneet ainakin kurssilla esiintyneiden käsitteiden (ml. harjoitukset) selitystehtävät, erilaiset käsiteanalyysi- ja tietokantataulujen suunnitteluun liittyvät tehtävät, sekä sql-tehtävät. Myös tietokannan ohjelmalliseen käyttöön liittyviä kysymyksiä on esiintynyt, mutta kokeessa ei tarvitse osata esim. Javan tai HTML:n syntaksia, eikä kokeessa myöskään tentata esimerkiksi Dao-suunnittelumallin yksityiskohdista (yleisiä kysymyksiä näistä saattaa toki olla).

Alla on eräs vanha kurssikoe, jota voi käyttää osana tenttiin kertausta. Alla oleva kurssikoe ei luonnollisesti sisällä kaikkea kokeessa kysyttävää..

Vanha koe

Tehtävä 1

Kaverisi aikoo osallistua tietokantojen perusteet-kurssin seuraavaan kokeeseen. Hänellä on kuitenkin vielä vaikeuksia muutamaan kurssiin liittyvän termin kanssa. Kerro kaverillesi mitä kukin alla oleva termi tarkoittaa.

  • xml
  • normalisointi
  • tietokannanhallintajärjestelmä
  • tietokanta
  • tietokantataulun avain
  • viiteavain

Tehtävä 2

Puhelinmyyntiyrityksen "Luuri ja Pojat" toimitusjohtaja Matti Luuri kertoo seuraavaa:

Puhelinmyyntiyhtiö "Luuri ja Pojat" tarjoaa päivälehdille palvelua, jossa yhtiön puhelinmyyjät myyvät lehtiä kotitalouksiin. Palvelun hinnoittelu päivälehdille on joko provisioperustaista tai resurssiperustaista. Provisioperustaisessa hinnoittelussa päivälehti maksaa jokaisesta tilauksesta ennalta määritellyn summan, kun taas resurssiperustaisessa hinnoittelussa taas maksetaan soitteluun käytetystä työajasta. Palvelut myydään päivälehdille kampanjoina, joissa myyntiä varten sovitaan aina kattosumma.

Suurin osa "Luuri ja Pojat"-yrityksen työntekijöistä on osa-aikaisia työntekijöitä vaihtelevalla taustalla. Palkkaus on lähes poikkeuksetta provisioperustaista. Työntekijöiden vaihtuvuus on hyvin suurta, joten hyvistä "myyvät vaikka jääkaappeja eskimoille" ja ei niin hyvistä "käytä resurssiperustaiseen myyntiin" työntekijöistä on hyvin vaikeaa pitää kirjaa.

Asiakaskunta eli kohdeyleisö, jonne "Luuri ja Pojat"-yrityksen työntekijät soittelevat, haalitaan Fonectan ja Eniron puhelinluetteloista. Tulikokeena uusille työntekijöille on kahden puhelinluetteloaukeaman läpisoittaminen, asiakaspalautteen kuuntelu ja toiminta siten, että ei-ostaviin asiakkaisiin käytetään mahdollisimman vähän aikaa. Jokaisen uuden työntekijän tulisi pystyä solmimaan vähintään muutama tilaus ensimmäisenä päivänä.

"Luuri ja Pojat"-yritys tarvitsee järjestelmän, joka mahdollistaa paremman kirjanpidon työntekijöiden tekemistä myynneistä. Jokaisesta myyntitapahtumasta tulee tulla selville ostava asiakas ja kampanja johon myynti liittyi. Järjestelmän tulee mahdollistaa myöhemmin erilaiset yhteenvetokyselyt, esimerkiksi "mitkä ovat suosituimpia päivälehtiä?", "Mitkä työntekijät saavat vähiten myyntitapahtumia aikaan?", "Mihin aikaan mitkäkin lehdet myyvät parhaiten?", "Mihin kaupunkeihin myydään minkälaisia lehtiä", ja "Millä kampanjoilla on vielä rahaa jäljellä?".

Tee ylläolevasta kuvauksesta tietosisältöanalyysi, eli etsi järjestelmään liittyvät käsitteet. Luo käsitteiden pohjalta UML-luokkakaavio. Yhteyksien ja osallistumisrajoitteiden merkintä on oleellista, attribuuteista tarvitsee merkitä vain tärkeimmät.

Tehtävä 3

Ohjelmistojen mallintamiseen ja suunnitteluun erikoistunut yritys "Softa ja Mallit" on luonut eräälle kiinteistöjen hallintaan erikoistuneelle yritykselle seuraavanlaisen UML-luokkakaavion. Koska yrityksen "Softa ja Mallit" erikoisosaaminen on mallinnuspuolella, sinun tehtävänäsi on suunnitella järjestelmää tukeva tietokanta. Muuta alla oleva luokkakaavio tietokantakaavioksi. Voit käyttää joko harjoituksissa tutuksi tullutta kaaviotyyppiä tai tehtävän 4 merkintätapaa. Merkitse kaikki attribuutit, avaimet ja viiteavaimet.

[Talo|osoite,postinumero,postitoimipaikka]
[Huoneisto|numero]
[Vikailmoitus|aika,vikakuvaus,ilmoittaja]
[Huoltotapahtuma|aika,toimenpidekuvaus]
[Huoltomies|nimi]
[Talo]1-sisältaa-1..*[Huoneisto]
[Huoneisto]1-*[Vikailmoitus]
[Huoltotapahtuma]*-1[Huoneisto]
[Huoltotapahtuma]*-1..*[Vikailmoitus]
[Huoltomies]1-*[Huoltotapahtuma]

Tehtävä 4

Juhlien organisointiin erikoistunut saksalainen yritys "Schöne Feste und Feuerkumpeln mit ganz gut Platzorganizierung" on rakentanut tietokantaekspertti Prof. Dr. auf der Relationin avulla plaseerauksissa eli vieraiden pöytiin asettelussa auttavan järjestelmän prototyypin. Järjestelmä mahdollistaa vieraan kirjautumisen ja itselle sopivan pöydän valitsemisen. Järjestelmän tietokanta on seuraavanlainen:

Poyta(id, nimi, tilavuus)
Plaseeraus(vieras_id -> Vieras, poyta_id -> Poyta)
Vieras(id, nimi, kayttajatunnus, salasana)

Prof. Dr. auf der Relation on vihjannut saksalaiselle yritykselle että olet tuleva tietokantaekspertti ja he haluavat antaa sinulle näyttöpaikan. Luo seuraavat SQL-kyselyt. Osassa kyselyissä on annettu esimerkki siitä, miltä tulostaulun tulisi näyttää.

  • Listaa pöydät ja niiden tilavuudet
  • Listaa vieraiden nimet aakkosjärjestyksessä
  • Listaa vieraat joita ei ole vielä asetettu mihinkään pöytään
  • Listaa vieraat jotka on asetettu johonkin pöytään. Listauksessa tulee olla pöydän nimi jokaisen vieraan nimen vieressä. Esimerkkitulostus:
    VIERAS    POYTA
    arto      punainen
    matti     keltainen
    leena     punainen
        
  • Listaa vieraiden määrä pöytää kohti. Esimerkkitulostus:
    POYTA      VIERAITA
    punainen   6
    keltainen  4
    vihreä     5
        
  • Listaa kaikki pöydät joihin mahtuu vielä vieraita

Bonus: Voiko sama vieras olla plaseerattu useampaan pöytään? Miksi tai miksi ei?

Sisällysluettelo