Hinter dem ersten Türchen verbirgt sich die Technologie DuckDB, wobei es sich um eine moderne OLAP-Datenbank handelt. Hiermit können Entitäten mit einer Ente verwaltet werden, womit der Name des ersten Türchens auch erklärt wäre.
Die Inspiration für dieses Türchen ist eine Scripting-Challenge aus dem Jahr 2022, wo es darum ging Fussballergebnisse in Ligatabellen umzuwandeln. Die Daten wurden erweitert und aktualisiert; sie stehen unter leagues.zip zum Download zur Verfügung.
Das Ziel der Übung ist es, die Ligatabelle anhand der Beispieldaten mit SQL zu berechnen.
Ausgangslage und Setup
Wir beginnen mit einer jungfräulichen Installation von Debian 12 “Bookworm”, die für unsere Zwecke gut ohne GUI auskommt. Wir arbeiten mit dem Benutzer user, der über sudo-Rechte verfügt.
Von der Download-Seite können wir DuckDB direkt herunterladen und entpacken:
Code: Alles auswählen
wget https://github.com/duckdb/duckdb/releases/download/v1.1.3/duckdb_cli-linux-amd64.zip
Code: Alles auswählen
sudo apt install -y unzip
Code: Alles auswählen
unzip duckdb_cli-linux-amd64.zip
Code: Alles auswählen
sudo mv duckdb /usr/local/bin/
Als nächstes sollen die Spieldaten heruntergeladen werden:
Code: Alles auswählen
wget https://m346.frickelbude.ch/files/leagues.zip
Code: Alles auswählen
unzip leagues.zip
Code: Alles auswählen
sudo apt install -y tree
tree leagues
Code: Alles auswählen
leagues
├── bundesliga
│ ├── day01.json
│ ├── day02.json
│ ├── day03.json
│ ├── day04.json
│ ├── day05.json
│ ├── day06.json
│ ├── day07.json
│ ├── day08.json
…
Code: Alles auswählen
[
{
"homeTeam": "Bayern München",
"awayTeam": "FSV Mainz 05",
"homeGoals": 2,
"awayGoals": 3
},
{
"homeTeam": "RB Leipzig",
"awayTeam": "Werder Bremen",
"homeGoals": 1,
"awayGoals": 2
},
{
"homeTeam": "Eintracht Frankfurt",
"awayTeam": "VfL Wolfsburg",
"homeGoals": 0,
"awayGoals": 2
},
…
Ziel der Übung ist es, eine Ligatabelle mit den folgenden Spalten zu generieren:
- Rang
- Mannschaftsname
- Anzahl absolvierter Spiele
- Erreichte Punkte (Sieg: 3, Unentschieden: 1, Niederlage: 0)
- Anzahl Siege
- Anzahl Unentschieden
- Anzahl Niederlagen
- Erzielte Tore
- Kassierte Tore
- Tordifferenz (erzielte minus kassierte Tore)
Datenimport
Wir starten DuckDB auf einem noch nicht existierenden Verzeichnis namens leagues-db, wodurch man sogleich Persistenz erhält:
Code: Alles auswählen
duckdb leagues-db
Code: Alles auswählen
D
Code: Alles auswählen
describe from "leagues/bundesliga/day*.json";
Zunächst sollen die Daten von den JSON-Dateien in eine Tabelle übertragen werden. Hierzu erstellen wir einerseits eine Sequenz für die automatische ID-Nummerierung und andererseits eine Tabelle namens bundesliga_games, welche von dieser Sequenz Gebrauch macht:
Code: Alles auswählen
create sequence bundesliga_game_id;
create table bundesliga_games (
id integer primary key default(nextval('bundesliga_game_id')),
homeTeam varchar(100),
awayTeam varchar(100),
homeGoals integer,
awayGoals integer
);
Als nächstes werden die Daten aus den JSON-Dateien in die erstellte Tabelle eingelesen:
Code: Alles auswählen
insert into bundesliga_games
(homeTeam, awayTeam, homeGoals, awayGoals)
select * from "leagues/bundesliga/day*.json";
Nun folgt der erste interessante Kniff: Aus jedem Spielergebnis sollen zwei Einträge geschrieben werden; je einer für die Heim- und für die Auswärtsmannschaft. Dies lässt sich mit einer View auf die gerade erstellte Tabelle bewerkstelligen:
Code: Alles auswählen
create view bundesliga_per_team as (
select id as gameId, homeTeam as team, homeGoals as goals_scored,
awayGoals as goals_conceded from bundesliga_games
union
select id as gameId, awayTeam as team, awayGoals as goals_scored,
homeGoals as goals_conceded from bundesliga_games
);
Das Ergebnis kann nun folgendermassen bestaunt werden:
Code: Alles auswählen
select * from bundesliga_per_team;
Anhand der Spielergebnisse sollen nun sogenannte Minitabellen erstellt werden: Aus jedem Spielergebnis aus der Perspektive jeder Mannschaft soll eine “Tabelle” (technisch gesehen: eine Tabellenzeile) für den jeweiligen Spieltag berechnet werden. Hierzu kommt neben einfacher Arithmetik das case/when/then/else-Konstrukt zum Einsatz, wozu wiederum eine neue View erstellt wird:
Code: Alles auswählen
create view bundesliga_results_day as (
select team, goals_scored, goals_conceded, (goals_scored - goals_conceded) as goals_diff,
case
when goals_diff > 0
then 3
else case when goals_diff = 0 then 1 else 0 end
end as points,
case
when goals_diff > 0
then 1
else 0
end as wins,
case
when goals_diff = 0
then 1
else 0
end as ties,
case
when goals_diff < 0
then 1
else 0
end as defeats,
from bundesliga_per_team
);
Übung: Die Tabelle erstellen
Nun seid ihr gefragt: Wie erhält man aus den ganzen Minitabellen nun eine spieltagsübergreifende Ligatabelle? Hierzu einige Tipps:
- Die Aggregatsfunktionen count und sum im Zusammenspiel mit der group by-Klausel können hilfreich sein.
- Die Sortierung ist in SQL denkbar einfach mit order by zu bewerkstelligen.
- Am besten erstellt man eine weitere View für die Tabellenansicht, etwa mit dem Namen bundesliga_table.
- Die offizielle Dokumentation kann auch sehr hilfreich sein.
Code: Alles auswählen
select row_number() over() as '#', * from bundesliga_table;
PS: Wer noch ein Weihnachtsgeschenk braucht, dem kann ich das Buch DuckDB in Action vom Manning-Verlag empfehlen!