351 lines
15 KiB
Markdown
351 lines
15 KiB
Markdown
|
---
|
||
|
geometry: margin=30mm
|
||
|
author: Felix Albrigtsen
|
||
|
...
|
||
|
|
||
|
# Øving 4
|
||
|
|
||
|
## a. List ut all informasjon (ordrehode og ordredetalj) om ordrer for leverandør nr 44.
|
||
|
|
||
|
```sql
|
||
|
SELECT *
|
||
|
FROM ordrehode oh
|
||
|
LEFT JOIN ordredetalj od
|
||
|
ON oh.ordrenr = od.ordrenr
|
||
|
WHERE levnr = 44;
|
||
|
```
|
||
|
|
||
|
## b. Finn navn og by ("LevBy") for leverandører som kan levere del nummer 1.
|
||
|
|
||
|
```sql
|
||
|
SELECT li.navn, li.levby
|
||
|
FROM prisinfo pi
|
||
|
LEFT JOIN levinfo li
|
||
|
ON li.levnr = pi.levnr
|
||
|
WHERE pi.delnr = 1;
|
||
|
```
|
||
|
|
||
|
## c. Finn nummer, navn og pris for den leverandør som kan levere del nummer 201 til billigst pris.
|
||
|
|
||
|
```sql
|
||
|
SELECT li.levnr, li.navn, pi.pris
|
||
|
FROM prisinfo pi
|
||
|
LEFT JOIN levinfo li
|
||
|
ON li.levnr = pi.levnr
|
||
|
WHERE pi.delnr = 201
|
||
|
ORDER BY pi.pris
|
||
|
ASC
|
||
|
LIMIT 1;
|
||
|
```
|
||
|
|
||
|
## d. Lag fullstendig oversikt over ordre nr 16, med ordrenr, dato, delnr, beskrivelse, kvantum, (enhets-)pris og beregnet beløp (=pris*kvantum).
|
||
|
|
||
|
Her har jeg brukt NATURAL JOIN i stedet for en equijoin for kortere spørringer, men de kunne like gjerne skrives som `JOIN ON a.egenskap = b.egenskap` for hver av egenskapene.
|
||
|
```sql
|
||
|
SELECT ordrenr, dato, delnr, beskrivelse, kvantum, pris AS stykkpris, kvantum * pris AS beløp
|
||
|
FROM prisinfo
|
||
|
NATURAL JOIN ordredetalj
|
||
|
NATURAL JOIN ordrehode
|
||
|
NATURAL JOIN delinfo
|
||
|
WHERE ordrenr = 16;
|
||
|
```
|
||
|
|
||
|
## e. Finn delnummer og leverandørnummer for deler som har en pris som er høyere enn prisen for del med katalognr X7770.
|
||
|
```sql
|
||
|
SELECT delnr, levnr
|
||
|
FROM prisinfo pi
|
||
|
WHERE pi.pris > (
|
||
|
SELECT pris
|
||
|
FROM prisinfo
|
||
|
WHERE katalognr = "X7770"
|
||
|
);
|
||
|
```
|
||
|
|
||
|
## f.
|
||
|
### i) Tenk deg at tabellen levinfo skal deles i to. Sammenhengen mellom by og fylke skal tas ut av tabellen. Det er unødvendigå lagre fylkestilhørigheten for hver forekomst av by. Lag én ny tabell som inneholder byer og fylker. Fyll denne med data fra levinfo. Lag også en tabell som er lik levinfo unntatt kolonnen Fylke. (Denne splittingen av tabellen levinfo gjelder bare i denneoppgaven. I resten av oppgavesettet antar du at du har den opprinnelige levinfo-tabellen.)
|
||
|
|
||
|
Jeg lager nye tabeller og overfører all relevant data fra levinfo.
|
||
|
```sql
|
||
|
|
||
|
CREATE TABLE byer(
|
||
|
levby VARCHAR(20) NOT NULL,
|
||
|
fylke VARCHAR(20) NOT NULL,
|
||
|
CONSTRAINT pk_byer PRIMARY KEY(levby)
|
||
|
);
|
||
|
|
||
|
INSERT INTO byer (levby, fylke)
|
||
|
SELECT levby, fylke
|
||
|
FROM levinfo
|
||
|
GROUP BY levby;
|
||
|
|
||
|
CREATE TABLE levinfo_nofylke(
|
||
|
levnr INTEGER,
|
||
|
navn VARCHAR(20) NOT NULL,
|
||
|
adresse VARCHAR(20) NOT NULL,
|
||
|
levby VARCHAR(20) NOT NULL,
|
||
|
postnr INTEGER NOT NULL,
|
||
|
CONSTRAINT levinfo_nofylke_pk PRIMARY KEY(levnr));
|
||
|
|
||
|
INSERT INTO levinfo_nofylke (levnr,navn,adresse,levby,postnr)
|
||
|
SELECT levnr,navn,adresse,levby,postnr
|
||
|
FROM levinfo;
|
||
|
|
||
|
```
|
||
|
|
||
|
### ii) Lag en virtuell tabell (view) slik at brukerne i størst mulig grad kan jobbe på samme måte mot de to nye tabellene som den gamle. Prøv ulike kommandoer mot tabellen (select, update, delete, insert). Hvilke begrensninger, hvis noen, har brukerne i forhold til tidligere?
|
||
|
|
||
|
```sql
|
||
|
CREATE VIEW levinfo_medfylke AS
|
||
|
SELECT li.*, b.fylke
|
||
|
FROM levinfo_nofylke li
|
||
|
LEFT JOIN byer b
|
||
|
ON li.levby = b.levby;
|
||
|
|
||
|
```
|
||
|
|
||
|
Select fungerer akkurat som før, ingen merkbar forskjell.
|
||
|
```sql
|
||
|
MariaDB [idatt2103_ov4]> SELECT * FROM levinfo_medfylke WHERE fylke = "Oslo";
|
||
|
+-------+---------------------+-------------+-------+--------+-------+
|
||
|
| levnr | navn | adresse | levby | postnr | fylke |
|
||
|
+-------+---------------------+-------------+-------+--------+-------+
|
||
|
| 6 | Kontorekspressen AS | Skolegata 3 | Oslo | 1234 | Oslo |
|
||
|
| 44 | Billig og Bra AS | Aveny 56 | Oslo | 1222 | Oslo |
|
||
|
+-------+---------------------+-------------+-------+--------+-------+
|
||
|
2 rows in set (0.051 sec)
|
||
|
|
||
|
MariaDB [idatt2103_ov4]> SELECT * FROM levinfo_medfylke WHERE levnr > 20;
|
||
|
+-------+-------------------+-----------------+-----------+--------+--------------+
|
||
|
| levnr | navn | adresse | levby | postnr | fylke |
|
||
|
+-------+-------------------+-----------------+-----------+--------+--------------+
|
||
|
| 44 | Billig og Bra AS | Aveny 56 | Oslo | 1222 | Oslo |
|
||
|
| 81 | Kontorbutikken AS | Gjennomveien 78 | Ål | 3345 | Telemark |
|
||
|
| 82 | Kontordata AS | Åsveien 178 | Trondheim | 7023 | S-Trøndelag |
|
||
|
+-------+-------------------+-----------------+-----------+--------+--------------+
|
||
|
3 rows in set (0.001 sec)
|
||
|
|
||
|
```
|
||
|
|
||
|
Update fungerer akkursat som før når man endrer egenskaper som ikke er fylke/by.
|
||
|
|
||
|
Om man bruker UPDATE til å endre en verdi i kolonnen "by", vil byen endre seg, og fylket vil automatisk følge etter, og bli satt til det fylket som hører til den nye byen, gitt i `byer`.
|
||
|
Dette er ønsket oppførsel, og gjør oppdateringen lettere.
|
||
|
```sql
|
||
|
+-------+---------------------+------------------+-----------+--------+--------------+
|
||
|
| levnr | navn | adresse | levby | postnr | fylke |
|
||
|
+-------+---------------------+------------------+-----------+--------+--------------+
|
||
|
| 6 | Kontorekspressen AS | Skolegata 3 | Oslo | 1234 | Oslo |
|
||
|
| 9 | Kontorutstyr AS | Villa Villekulla | Ås | 1456 | Østfold |
|
||
|
| 12 | Mister Office AS | Storgt 56 | Ås | 1456 | Østfold |
|
||
|
| 44 | Billig og Bra AS | Pilestredet 4 | Oslo | 1222 | Oslo |
|
||
|
| 81 | Kontorbutikken AS | Gjennomveien 78 | Ål | 3345 | Telemark |
|
||
|
| 82 | Kontordata AS | Åsveien 178 | Trondheim | 7023 | S-Trøndelag |
|
||
|
+-------+---------------------+------------------+-----------+--------+--------------+
|
||
|
|
||
|
MariaDB [idatt2103_ov4]> UPDATE levinfo_medfylke SET levby = "Oslo" WHERE levnr = 82;
|
||
|
Query OK, 1 row affected (0.011 sec)
|
||
|
Rows matched: 1 Changed: 1 Warnings: 0
|
||
|
|
||
|
+-------+---------------------+------------------+-------+--------+----------+
|
||
|
| levnr | navn | adresse | levby | postnr | fylke |
|
||
|
+-------+---------------------+------------------+-------+--------+----------+
|
||
|
| 6 | Kontorekspressen AS | Skolegata 3 | Oslo | 1234 | Oslo |
|
||
|
| 9 | Kontorutstyr AS | Villa Villekulla | Ås | 1456 | Østfold |
|
||
|
| 12 | Mister Office AS | Storgt 56 | Ås | 1456 | Østfold |
|
||
|
| 44 | Billig og Bra AS | Pilestredet 4 | Oslo | 1222 | Oslo |
|
||
|
| 81 | Kontorbutikken AS | Gjennomveien 78 | Ål | 3345 | Telemark |
|
||
|
| 82 | Kontordata AS | Åsveien 178 | Oslo | 7023 | Oslo |
|
||
|
+-------+---------------------+------------------+-------+--------+----------+
|
||
|
```
|
||
|
|
||
|
Et problem dukker derimot opp dersom man prøver å endre "fylke" direkte. Om man endrer på "fylke" i den nye viewet/vinduet, vil vi endre på tabellen `byer`i stedet for å endre på tabellen `levinfo_nofylke`. Altså kan man ved å endre på "fylke" på levnr 9 i eksempelet over fra "Østfold" til "Vestfold", vil det også endre på levnr 12.
|
||
|
|
||
|
```
|
||
|
+-------+---------------------+------------------+-------+--------+----------+
|
||
|
| levnr | navn | adresse | levby | postnr | fylke |
|
||
|
+-------+---------------------+------------------+-------+--------+----------+
|
||
|
| 6 | Kontorekspressen AS | Skolegata 3 | Oslo | 1234 | Oslo |
|
||
|
| 9 | Kontorutstyr AS | Villa Villekulla | Ås | 1456 | Østfold |
|
||
|
| 12 | Mister Office AS | Storgt 56 | Ås | 1456 | Østfold |
|
||
|
| 44 | Billig og Bra AS | Pilestredet 4 | Oslo | 1222 | Oslo |
|
||
|
| 81 | Kontorbutikken AS | Gjennomveien 78 | Ål | 3345 | Østfold |
|
||
|
| 82 | Kontordata AS | Åsveien 178 | Oslo | 7023 | Oslo |
|
||
|
+-------+---------------------+------------------+-------+--------+----------+
|
||
|
|
||
|
MariaDB [idatt2103_ov4]> UPDATE levinfo_medfylke SET fylke = "Vestfold" WHERE levnr = 9;
|
||
|
Query OK, 1 row affected (0.011 sec)
|
||
|
Rows matched: 1 Changed: 1 Warnings: 0
|
||
|
|
||
|
+-------+---------------------+------------------+-------+--------+----------+
|
||
|
| levnr | navn | adresse | levby | postnr | fylke |
|
||
|
+-------+---------------------+------------------+-------+--------+----------+
|
||
|
| 6 | Kontorekspressen AS | Skolegata 3 | Oslo | 1234 | Oslo |
|
||
|
| 9 | Kontorutstyr AS | Villa Villekulla | Ås | 1456 | Vestfold |
|
||
|
| 12 | Mister Office AS | Storgt 56 | Ås | 1456 | Vestfold |
|
||
|
| 44 | Billig og Bra AS | Pilestredet 4 | Oslo | 1222 | Oslo |
|
||
|
| 81 | Kontorbutikken AS | Gjennomveien 78 | Ål | 3345 | Østfold |
|
||
|
| 82 | Kontordata AS | Åsveien 178 | Oslo | 7023 | Oslo |
|
||
|
+-------+---------------------+------------------+-------+--------+----------+
|
||
|
```
|
||
|
|
||
|
De to tabellene i viewet henger ikke sammen med en triviell relasjon som en-til-en, så om en prøver å slette en tuppel får man en feilmelding.
|
||
|
|
||
|
```sql
|
||
|
MariaDB [idatt2103_ov4]> DELETE FROM levinfo_medfylke WHERE levnr = 44;
|
||
|
ERROR 1288 (HY000): The target table levinfo_medfylke of the DELETE is not updatable
|
||
|
```
|
||
|
|
||
|
Inserts fungerer heller ikke i mitt tilfelle, da tabellen bruker OUTER JOIN som ikke er tillatt i følge [Dokumentasjonen](https://mariadb.com/kb/en/inserting-and-updating-with-views/).
|
||
|
```
|
||
|
MariaDB [idatt2103_ov4]> INSERT INTO levinfo_medfylke (levnr, navn, adresse, levby, postnr) VALUES (85, "Utstyrsbutikken AS", "Sem Sælandsvei 1", "Trondheim", 7034);
|
||
|
ERROR 1471 (HY000): The target table levinfo_medfylke of the INSERT is not insertable-into
|
||
|
```
|
||
|
|
||
|
Alle endringer som INSERT, UPDATE og DELETE kan fortstatt gjøres uten problem i de opprinnelige tabellene, de som vinduet baserer seg på.
|
||
|
|
||
|
|
||
|
### g. Anta at en vurderer å slette opplysningene om de leverandørene som ikke er representert i Prisinfo-tabellen. Finn ut hvilke byer en i tilfelle ikke får leverandør i. (Du skal ikke utføre slettingen.) (Tips: Svaret skal bli kun én by, "Ål".)
|
||
|
|
||
|
```sql
|
||
|
SELECT DISTINCT levby FROM levinfo WHERE levby NOT IN (SELECT levby FROM prisinfo LEFT JOIN levinfo ON prisinfo.levnr = levinfo.levnr);
|
||
|
```
|
||
|
|
||
|
... men hvorfor er `SELECT DISTINCT levby FROM levinfo WHERE levnr NOT IN (SELECT levnr FROM prisinfo);` feil?
|
||
|
|
||
|
### h. Finn leverandørnummer for den leverandør som kan levere ordre nr 18 til lavest totale beløp (vanskelig).
|
||
|
|
||
|
deler og kvantum i ordren:
|
||
|
> SELECT delnr, kvantum FROM ordredetalj WHERE ordrenr = 18;
|
||
|
|
||
|
Alle leverandører som kan levere delnr(minst en av) i ordre 18:
|
||
|
> SELECT * FROM prisinfo RIGHT JOIN ordredetalj ON prisinfo.delnr = ordredetalj.delnr WHERE ordredetalj.ordrenr = 18;
|
||
|
|
||
|
Antall delnr (individuelle deltyper) hver leverandør kan levere:
|
||
|
> SELECT levnr, COUNT(*) FROM kan_levere_deler GROUP BY levnr;
|
||
|
|
||
|
Finn alle levnr fra kan_levere_deler som kan levere like mange delnr som det er i ordre 18.
|
||
|
> SELECT levnr FROM kan_levere_deler GROUP BY levnr HAVING (COUNT(*) = (SELECT COUNT(*) FROM ordredetalj WHERE ordrenr = 18));
|
||
|
|
||
|
Finn totalpris per del, fra leverandørene som kan levere alle delene
|
||
|
> SELECT kld.levnr, pris*kvantum FROM kan_levere_deler kld RIGHT JOIN kan_levere_alle kla ON kld.levnr = kla.levnr;
|
||
|
|
||
|
Finn totalpris for hele ordren per leverandør:
|
||
|
> SELECT kld.levnr, SUM(pris*kvantum) AS totalpris FROM kan_levere_deler kld RIGHT JOIN kan_levere_alle kla ON kld.levnr = kla.levnr GROUP BY kld.levnr ORDER BY totalpris;
|
||
|
|
||
|
```sql
|
||
|
-- levnr og prisinfo for alle deler i ordre 18 hos alle leverandører som har delen
|
||
|
CREATE VIEW kan_levere_deler AS
|
||
|
SELECT pi.levnr, pi.delnr, pi.pris, kvantum
|
||
|
FROM ordredetalj od LEFT JOIN prisinfo pi ON od.delnr = pi.delnr
|
||
|
WHERE od.ordrenr = 18;
|
||
|
|
||
|
-- levnr og totalsum for alle leverandører som har alle delene
|
||
|
CREATE VIEW kan_levere_alle AS
|
||
|
SELECT levnr, SUM(pris*kvantum) AS totalpris
|
||
|
FROM kan_levere_deler
|
||
|
GROUP BY levnr HAVING (
|
||
|
COUNT(*) = (SELECT COUNT(*) FROM ordredetalj WHERE ordrenr = 18)
|
||
|
);
|
||
|
|
||
|
-- Hent ut kun leverandøren med den laveste totalprisen
|
||
|
SELECT * FROM kan_levere_alle ORDER BY totalpris ASC LIMIT 1;
|
||
|
|
||
|
```
|
||
|
|
||
|
|
||
|
# Oppgave 2: SQL med NULL-verdier
|
||
|
|
||
|
### a. Sett opp en SELECT-setning som er UNION mellom alle forlag med Oslo-nummer (telefonnummer begynner med 2) og alle som ikke er Oslo-nummer. Får du med forlaget med NULL-verdi på telefonnummer? Hvis ikke,utvid unionen med en mengde til.
|
||
|
|
||
|
```sql
|
||
|
SELECT *
|
||
|
FROM forlag
|
||
|
WHERE telefon LIKE '2%'
|
||
|
UNION
|
||
|
SELECT *
|
||
|
FROM forlag
|
||
|
WHERE telefon NOT LIKE '2%'
|
||
|
UNION
|
||
|
SELECT *
|
||
|
FROM forlag
|
||
|
WHERE telefon IS NULL;
|
||
|
```
|
||
|
|
||
|
### b. Sett opp SQL-setninger som finner gjennomsnittlig alder på forfattere der fødselsåret er oppgitt. For forfattere der dødsåret ikke er oppgitt, skal du kun ta med de som er født etter 1900.
|
||
|
|
||
|
#### Kan løses med view, enkelt å lese:
|
||
|
```sql
|
||
|
CREATE VIEW forfatter_fra_til AS
|
||
|
SELECT forfatter_id, fornavn, etternavn, fode_aar AS fra, dod_aar AS til
|
||
|
FROM forfatter
|
||
|
WHERE fode_aar IS NOT NULL AND dod_aar IS NOT NULL
|
||
|
|
||
|
UNION
|
||
|
|
||
|
SELECT forfatter_id, fornavn, etternavn, fode_aar AS fra, YEAR(current_date()) as til
|
||
|
FROM forfatter
|
||
|
WHERE fode_aar IS NOT NULL AND dod_aar IS NULL AND fode_aar > 1900;
|
||
|
|
||
|
-- eksempel: se alle aldre:
|
||
|
SELECT forfatter_id, fornavn, etternavn, til-fra AS alder FROM forfatter_fra_til;
|
||
|
|
||
|
-- finn gjennomsnittsalderen:
|
||
|
SELECT SUM(til-fra)/COUNT(*) FROM forfatter_fra_til;
|
||
|
```
|
||
|
|
||
|
#### Kan også løses uten view, med en lang spørring.
|
||
|
```sql
|
||
|
SELECT SUM(alder) / COUNT(*) FROM (
|
||
|
SELECT dod_aar - fode_aar AS alder
|
||
|
FROM forfatter
|
||
|
WHERE fode_aar IS NOT NULL AND dod_aar IS NOT NULL
|
||
|
|
||
|
UNION
|
||
|
|
||
|
SELECT YEAR(current_date()) - fode_aar AS alder
|
||
|
FROM forfatter
|
||
|
WHERE fode_aar IS NOT NULL AND dod_aar IS NULL AND fode_aar > 1900
|
||
|
) AS forfatter_alder;
|
||
|
```
|
||
|
|
||
|
Uttrykkene summerer de fem forfatterne som stemmer med kravene, og gir resultatet 68.0 år.
|
||
|
|
||
|
### c. Sett opp SQL-setninger som finner hvor stor andel av forfatterne som ble med i beregningene under b).
|
||
|
|
||
|
```sql
|
||
|
SELECT COUNT(forfatter_alder.forfatter_id), COUNT(forfatter.forfatter_id) FROM
|
||
|
(
|
||
|
-- forfattere som blir telt med i gjennomsnittet
|
||
|
SELECT * FROM forfatter
|
||
|
WHERE fode_aar IS NOT NULL
|
||
|
AND ( dod_aar IS NOT NULL OR fode_aar > 1900 )
|
||
|
) AS forfatter_alder
|
||
|
RIGHT JOIN forfatter
|
||
|
ON forfatter.forfatter_id = forfatter_alder.forfatter_id;
|
||
|
```
|
||
|
|
||
|
Gir resultatet
|
||
|
```
|
||
|
+-------------------------------------+-------------------------------+
|
||
|
| COUNT(forfatter_alder.forfatter_id) | COUNT(forfatter.forfatter_id) |
|
||
|
+-------------------------------------+-------------------------------+
|
||
|
| 5 | 12 |
|
||
|
+-------------------------------------+-------------------------------+
|
||
|
|
||
|
```
|
||
|
|
||
|
Alternativ løsning som bruker vinduet opprettet tidligere, viser andel som prosent:
|
||
|
```sql
|
||
|
SELECT CONCAT(100 * COUNT(fft.forfatter_id) / COUNT(f.forfatter_id), '%') AS prosent_aldersbereging
|
||
|
FROM forfatter_fra_til fft
|
||
|
RIGHT JOIN forfatter f
|
||
|
ON fft.forfatter_id = f.forfatter_id;
|
||
|
|
||
|
+------------------------+
|
||
|
| prosent_aldersbereging |
|
||
|
+------------------------+
|
||
|
| 41.6667% |
|
||
|
+------------------------+
|
||
|
```
|
||
|
|