Adventskalender 1. Dezember 2024 - Ent(ität)en
Verfasst: 01.12.2024 06:43:30
Ent(ität)en
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:
Zum Entpacken wird das Paket unzip benötigt:
Womit das heruntergeladene Archiv auch sogleich entpackt wird:
Das Ergebnis ist die Binärdatei duckdb, welche ca. 48 Megabyte wiegt. Wir verschieben diese an einen passenden Ort:
Beispieldaten
Als nächstes sollen die Spieldaten heruntergeladen werden:
Auch dieses Archiv soll entpackt werden:
Wodurch ein Verzeichnis namens leagues erstellt worden ist. Darüber lässt sich mit tree ein erster Überblick erhalten:
Was dann ungefähr folgendermassen aussieht:
Wir haben mehrere Unterordner mit verschiedenen Ligen, und pro Liga gibt es mehrere JSON-Dateien, welche ungefähr folgendermassen aussehen (Beispiel: leagues/bundesliga/day01.json):
Die Dateien wurden zufällig mit einigermassen realistischen Stärkeverhältnissen generiert; die Struktur sollte selbsterklärend sein.
Ziel der Übung ist es, eine Ligatabelle mit den folgenden Spalten zu generieren:
Datenimport
Wir starten DuckDB auf einem noch nicht existierenden Verzeichnis namens leagues-db, wodurch man sogleich Persistenz erhält:
Es sollte der Prompt D erscheinen:
DuckDB kann das Schema von JSON-Dateien automatisch ermitteln, was wir mit describe from versuchen wollen (auf Prompts und Ausgaben wird ab hier grösstenteils verzichtet):
Wir stellen fest, dass DuckDB sowohl Feldnamen als auch Datentypen korrekt erkannt hat.
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:
Quizfrage: Warum ist die Vergabe einer künstlichen ID hier sinnvoll?
Als nächstes werden die Daten aus den JSON-Dateien in die erstellte Tabelle eingelesen:
Es wird das insert/select-Muster verwendet, wobei die Ergebnismenge aus der JSON-Datei die Grundlage für das Einfügen der Daten bildet.
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:
Mithilfe der union werden die Ergebnisse aus den beiden Perspektiven (Heim- und Auswärtsmannschaft) aneinandergehängt.
Das Ergebnis kann nun folgendermassen bestaunt werden:
Ergebnisberechnung
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:
Damit wäre der erste Teil der Berechnung auch schon erledigt.
Übung: Die Tabelle erstellen
Nun seid ihr gefragt: Wie erhält man aus den ganzen Minitabellen nun eine spieltagsübergreifende Ligatabelle? Hierzu einige Tipps:
Ich bin auf eure Lösungen gespannt!
PS: Wer noch ein Weihnachtsgeschenk braucht, dem kann ich das Buch DuckDB in Action vom Manning-Verlag empfehlen!
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!