Commit 67615473 authored by M1888's avatar M1888

access done

parent 72331bfb
No preview for this file type
# ttzc0800-harkka
# TTZC0800 - Tietokannat
Tietokannat (TTZC0800) kurssin harkkatyö
## Kendokanta
## Suunnitelmavaihe
Tietokanta jääkiekko-otteluiden tapahtumien tallentamiseen.
**[Vaatimusmäärittelyyn](vaatimusmaarittely.md)** tästä.
| Nimi | Email |
|---|---|
| Joeli Hokkanen | M1888@student.jamk.fi |
Vaatimusmäärittelyn pohjalta nousi ainakin seuraavanlaisia käsitteitä:
Versio: 0.9 (15.4.2019)
- Kaupunki (sisältää areenoja, joukkueilla kotikaupunki)
- Areena (sisältää otteluita)
- Joukkue (sisältää pelaajia)
- Pelaaja (kuuluu joukkueeseen)
- Ottelu (sisältää tapahtumia)
- Tapahtumat (maali & jäähy, eri tauluista)
# Sisällys
## Toteutus
1. [Johdanto](#johdanto)
2. [Suunnitelmavaihe ja vaatimusmäärittely](#suunnitelmavaihe-ja-vaatimusmaarittely)
3. [Toteutus](#toteutus)
4. [Lopuksi](#lopuksi)
# Johdanto
Ohjelmisto tehdään TTZC0800 Tietokannat -kurssin puitteissa harjoitustyönä. Tavoitteena on toteuttaa tietokanta, jonne voidaan tallentaa tietoa jääkiekkojoukkueista, pelaajista, areenoista, otteluista, sekä ottelutapahtumista.
Lisäksi toteutetaan MS Access -pohjainen käyttöliittymä osalle tietokannan ominaisuuksista.
# Suunnitelmavaihe ja vaatimusmäärittely
## Yleiskuvus
Järjestelmä sijoitetaan JAMKin student.labranet.jamk.fi -palvelimelle ja tietokantaratkaisuna on MySQL.
Olettamuksia arkkitehtuurista:
- Joukkueilla on kotikaupunki
- Joukkueilla on sopimuksia pelaajista, sopimukseen kuuluu alku- ja loppupäivä sekä pelinumero
- Kaupunki omistaa areenan
- Ottelu pelataan yhdellä areenalla koti- ja vierasjoukkueen kesken
- Ottelusta kirjataan ottelutapahtumia, jotka voivat olla ainakin maaleja, jäähyjä, rangaistuslaukauksia, aikalisiä
- Maalilla voi yksi syöttäjä, mutta ei sen enempää
- Maali-taululle piti määritellä myös maalin tehnyt joukkue, koska muuten omaan maaliin menevä kiekko tulisi väärän joukkueen maaliksi
- Otteluilla on aina voittaja (se, kumpi on tehnyt enempi maaleja). SM-Liigan nykyisten sääntöjen mukaan ottelut eivät voi päättyä tasan.
Myös käsitemalli muovautui tämän pohjalta:
- Areena
- Kaupunki
- Joukkue
- Sopimus
- Pelaaja
- Ottelu
- Ottelutapahtuma:
- Maali
- Jäähy
- Rankkari
- Aikalisä
Alustava ER-kaavio tietokannasta:
![](er.png)
Ottelutapahtumat toimivat tyypin mukaan ottamalla tapahtuma_id:n halutusta taulusta.
| Numero | Tapahtumatyyppi |
|---|---|
| 1 | Maali |
| 2 | Jäähy |
| 3 | Rangaistuslaukaus |
| 4 | Aikalisä |
Aikamääreet (esim. ottelutapahtumassa ja jäähyssä), ovat sekunteina
Toteuttamisvaiheessa ilmeni, että tämä suunnitelma ei toiminutkaan. MySQL valitti ottelutapahtumia lisätessä vierasavaimien konfliktista, kun kaikista taulukoista ei löydykään sopivaa id:tä:
```SQL
mysql> INSERT INTO ottelutapahtuma (tyyppi, tapahtuma_id, ottelu_id, aika) VALUES
-> (2, 1, 1, 3*60+14);
ERROR 1452 (23000): Cannot add or update a child row: a foreign key constraint fails (`M1888`.`ottelutapahtuma`, CONSTRAINT `fk_ottelutapahtuma_maali1` FOREIGN KEY (`tapahtuma_id`) REFERENCES `maali` (`maali_id`) ON DELETE NO ACTION ON UPDATE NO ACTION)
```
Suunnittelupöydältä pyörähti sitten ulos kakkosversio, jossa ottelutapahtuma-taulussa on suoraan oma erillinen foreign key jokaiselle ottelutapahtumatyypille, ja näistä rajoitteen avulla täytetään vain yksi. Taulun pääavain tulee ottelu_id:stä, sekä tästä yhdestä täytetystä foreign keystä.
Jotta varmistuttiin, että vain yksi näistä vierasavaimista täytetään kerralla, tuli ottelutapahtuma-taululle kirjoitettua vielä seuraavanlainen constraint *(myöhemmin tajusin, että eihän nämä CHECKit toimineet MySQL:ssä)*:
```SQL
CONSTRAINT `CK_tapahtumaid` CHECK (
CASE WHEN aikalisa_id IS NULL THEN 0 ELSE 1 END +
CASE WHEN jaahy_id IS NULL THEN 0 ELSE 1 END +
CASE WHEN rankkari_id IS NULL THEN 0 ELSE 1 END +
CASE WHEN maali_id IS NULL THEN 0 ELSE 1 END = 1)
```
![](er_v2.png)
Mutta edelleen törmättiin samankaltaiseen ongelmaan:
```SQL
mysql> INSERT INTO ottelutapahtuma (ottelu_id, jaahy_id, aika) VALUES (1, 1, 3*60+14);
ERROR 1452 (23000): Cannot add or update a child row: a foreign key constraint fails (`M1888`.`ottelutapahtuma`, CONSTRAINT `fk_ottelutapahtuma_aikalisa1` FOREIGN KEY (`aikalisa_id`) REFERENCES `aikalisa` (`aikalisa_id`) ON DELETE NO ACTION ON UPDATE NO ACTION)
```
Googlesta ja StackOverflow'sta aikani luettuani, kokeilin pariakin eri lähestymistapaa:
- Vaihdoin ottelutapahtuma-taulun vierasavaimet UNSIGNED INT -tyyppiin
- Kokeilin määrittää kaikki muut vierasavaimet INSERTin aikana erikseen NULL-arvolle
- Vielä paria erilaista constraintia ja muuta määrettä taululle
Mikään ei auttanut vaikka tämän piti toimia hyvin. Olin jo varma, että Student-palveimen MySQL on liian vanha ja buginen eikä koodissani mitään vikaa ole. Sitten tajusin vielä kokeilla, että jätän FK-määreet näille ottelutapahtuman eri id:ille, mutta teen taululle erillisen oman id:n primary keyksi, näiden neljän fuusion sijaan. Tämä tuntui vihdoin toimivan, ja tietokannan skeema oli vihdoin valmis. Lopullinen ongelma oli siis siinä, että primary keyssä ei saa olla NULL-kenttiä, vaikka INSERT-rivin virhe tulikin foreign key constraintista.
![ER final](er_final.png)
## Toiminnot
Pakollisia toimintoja ovat ainakin:
1) Joukkueiden lisäys ja ylläpito (nimi, kaupunki, pisteet ym.)
2) Pelaajien lisäys ja muokkailu (nimi, pelinumero, paikka, joukkue, tehopisteet, ..)
3) Otteluiden lisääminen (joukkue vs joukkue, tapahtumat..)
4) Pelitapahtumien lisääminen otteluun (maalit, jäähyt, jne)
Lisäksi tulisi saada koosteita mm. seuraavista asioista:
1) Ottelun aikana tehdyt maalit
2) Joukkueiden pistepörssi (= voitetut ottelut)
3) Pelaajapörssi (= maalit + syötöt)
## Ulkoiset liittymät
Kannalle toteutetaan MS Access -pohjainen käyttöliittymä tietojen tarkastelua ja lisäilyä varten.
## Muut ominaisuudet
Suorituskyky: Vasteajat tulisi olla käyttämällämme datamäärällä käytännössä välittömiä. Kannan tulisi skaalautua suuremmillekkin datamäärille.
# Toteutus
### 1. [create-kendokanta.sql](create-kendokanta.sql)
......@@ -203,7 +322,7 @@ Näiden välille lähdettiin sitten lisäämään otteluita. Ensimmäisen keksin
Kun ottelut oli saatu lisättyä, oli aika lähteä työstämään erilaisia kyselyitä kannasta.
### [views.sql](views.sql)
### 3. [views.sql](views.sql)
Näin saatiinkin aikaiseksi kätevät pistepörssit joukkueille ja pelaajille, sekä statistiikkaa jäähyminuuteista.
......@@ -297,3 +416,98 @@ mysql> SELECT * FROM Jaahyt_joukkue ORDER BY minuutit DESC;
+------------+---------+----------+
4 rows in set (0.03 sec)
```
### [Database.accdb](Database.accdb)
Lopuksi oli vielä aika työstää käyttöliittymää tietokannalle MS Acessilla. Puolen tunnin jälkeen olin täynnä vihaa ja raivoa.
Yksinkertaisinkin kysely piti kirjoittaa uudelleen ihmeellisin Access-kohtaisin säännöin ja oikuin.
Esimerkiksi views.sql:stä pistepörssi, joka siis MySQL:ssä toimi vallan mainiosti:
```SQL
SELECT
ot.ottelu_id,
m.joukkue_id,
COUNT(m.joukkue_id) AS maalit
FROM ottelu AS o
INNER JOIN ottelutapahtuma AS ot
ON o.ottelu_id = ot.ottelu_id
INNER JOIN maali AS m
ON ot.maali_id = m.maali_id
GROUP BY m.joukkue_id, o.ottelu_id;
```
Access:
![](access_error1.png)
Google kertoi, että Access ei osaa useampaa JOINia, mikäli niitä ei laita sulkeisiin:
```SQL
SELECT
ot.ottelu_id,
m.joukkue_id,
COUNT(m.joukkue_id) AS maalit
FROM (ottelu AS o
INNER JOIN ottelutapahtuma AS ot
ON o.ottelu_id = ot.ottelu_id)
INNER JOIN maali AS m
ON ot.maali_id = m.maali_id
GROUP BY m.joukkue_id, o.ottelu_id;
```
![](access_error2.png)
Nyt Accessin logiikka meni niin, että koska kyselyssä on GROUP BY -määre, niin mikään valittu kenttä ei voi tulla muualta kuin aggregaattifunktioista (min, max, count, jne). Miksi ihmeessä? No mistäpä minä tietäisin.
```SQL
SELECT
MAX(ot.ottelu_id) AS ottelu_id,
MAX(m.joukkue_id) AS joukkue_id,
COUNT(m.joukkue_id) AS maalit
FROM (ottelu AS o
INNER JOIN ottelutapahtuma AS ot
ON o.ottelu_id = ot.ottelu_id)
INNER JOIN maali AS m
ON ot.maali_id = m.maali_id
GROUP BY m.joukkue_id, o.ottelu_id;
```
Tähän nyt vihdoin hädintuskin toimimaan saatuun kyselyyn perustuva seuraava kysely (views.sql näkymä *Voittajat*) meni myös heti päin seiniä.
```SQL
SELECT jm.ottelu_id, j.joukkue_id, MAX(j.nimi), MAX(jm.maalit) AS maalit
FROM Joukkue_maalit AS jm INNER JOIN joukkue AS j ON jm.joukkue_id = j.joukkue_id
GROUP BY jm.ottelu_id, j.joukkue_id;
```
Kysely näytti kyllä maalien ja joukkueiden määrän, mutta koska ryhmittely oli sekä ottelu_id:n että joukkue_id:n mukaan, tuli joka ottelusta molemmat joukkueet. Jos taas j.joukkue_id:n otti pois GROUP BY -määreestä, tuli tuo tuttu "Your query does not include the specified expression as part of an aggregate function" -virhe. MAX(j.joukkue_id) taas antoi tietysti sen joukkueen, jolla oli suurempi id, eikä sitä, joka oli enempi maaleja tehnyt. Joukkue_id:tä ei siis saanut näkymään mukaan millään.
MAX(j.nimi) ei toiminut myöskään, vaan antoi aakkosissa myöhemmän joukkueen nimen voittajaksi. MIN(j.nimi) teki päinvastoin, ja pelkkä j.nimi antoi... *rumpujen pärinää*... saman aggregate function -virheen. GROUP BY:hyn jos laittoi joukkueen nimen, törmäsi samaan ongelmaan, kun joukkue_id:n mukaan järjestellessä, eli joka ottelusta mukaan tulivat molemmat joukkueet.
Päätin luovuttaa ylemmän kyselyn suhteen, ja kokeilla tehdä Accessin kautta edes pelaajien pistepörssistä raporttia.
Pistepörssi-kyselyn saikin melko pienillä muutoksilla toimimaan Accessin puolella. Yritin rohkeana vielä liittää kyselyyn sopimus-taulun Accessin käyttöliittymän kautta, ja sitä kautta saada pelaajien numerot näkyville. Mutta sepä oli virhe:
![](access_error3.png)
Jatketaan siis pistepörssillä ilman pelaajanumeroita ja joukkueita. Kysely kun toimi, niin sen pohjalta sai tehtyä pistepörssistä tulostettavan raportin:
![](access_porssi.png)
Lisäksi Acessiin toteutettiin formi, jolla voi selata joukkueissa olevia pelaajia:
![](access_sopparit.png)
# Lopuksi
Harjoitustyöhön tuli käytettyä aikaa n. 30 tuntia (tarkemmin: issue #1). Tästä karkeasti 10 h oli skeeman suunnittelua ja toteutusta, ja loput sen jälkeistä: triggereiden tekoa, tietojen syöttöä, näkymien ja kyselyiden tekoa jne. Access-osioon kului näistä yhteensä n. 5 tuntia, ja ne olivat ehdottomasti kaikista turhauttavimmat tunnit.
Arvosanaehdotukseni on **5**.
Perustelut:
- Tietokannan skeema on mielestäni huolella tehty ja suunniteltu
- Samoin triggerit
......@@ -29,6 +29,7 @@ Olettamuksia arkkitehtuurista:
- Ottelusta kirjataan ottelutapahtumia, jotka voivat olla ainakin maaleja, jäähyjä, rangaistuslaukauksia, aikalisiä
- Maalilla voi yksi syöttäjä, mutta ei sen enempää
- Maali-taululle piti määritellä myös maalin tehnyt joukkue, koska muuten omaan maaliin menevä kiekko tulisi väärän joukkueen maaliksi
- Otteluilla on aina voittaja (se, kumpi on tehnyt enempi maaleja). SM-Liigan nykyisten sääntöjen mukaan ottelut eivät voi päättyä tasan.
Alustava ER-kaavio tietokannasta:
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment