SQL-DQL: Aufgaben (Lösungen)#

import duckdb
import hashlib


class Check:
    def __init__(self, con, hashes=None):
        self.con = con
        self.hashes = hashes or []

    def get_hash(self, df, consider_col_name: bool = False, consider_sorting: bool = False):
        """
        Get the hash of a DataFrame.

        consider_col_name: If True, the column names are not considered for the hash.
        consider_sorting: If True, the DataFrame is sorted before hashing.
        """
        df_str = df.astype(str)
        remaining = list(df_str.columns)
        ordered = []
        while remaining:
            min_cell, min_col = None, None
            for col in remaining:
                this_min = min(df_str[col].tolist())
                if min_cell is None or this_min < min_cell:
                    min_cell, min_col = this_min, col
            ordered.append(min_col)
            remaining.remove(min_col)

        df_ord = df_str[ordered]
        if not consider_sorting:
            df_sorted = df_ord.sort_values(by=ordered, ignore_index=True)
        else:
            df_sorted = df_ord

        dfString = df_sorted.to_csv(index=False, header=consider_col_name)
        if consider_sorting:
            dfString = f"sorted:{dfString}".encode("utf-8")
        else:
            dfString = dfString.encode("utf-8")

        digest = hashlib.md5(dfString).hexdigest()

        return digest

    def sql(self, sql: str, hashPrint: bool = False):
        """
        Execute SQL and check the result against a hash.
        """
        sqlResult = self.con.sql(sql)

        if sqlResult is None:
            return sqlResult

        sqlDF = sqlResult.df()
        for consider_col_name in [True, False]:
            for consider_sorting in [True, False]:
                digest = self.get_hash(
                    sqlDF, consider_col_name=consider_col_name, consider_sorting=consider_sorting
                )
                if hashPrint:
                    print(
                        f"Hash: {digest} (consider_col_name={consider_col_name}, consider_sorting={consider_sorting})"
                    )
                for h in self.hashes:
                    if isinstance(h, str):
                        h = [h]
                    if digest == h[0]:
                        if len(h) > 1:
                            print(f"🎉 Richtiges Ergebnis für Aufgabe {h[1]} 🎉")
                        else:
                            print("🎉 Richtiges Ergebnis für eine Aufgabe 🎉")
        return sqlResult

    def close(self):
        self.con.close()
duck_con = duckdb.connect(database="../resources/06_sql_dql/uni_ue.duckdb", read_only=False)
duck_con.sql("SELECT * FROM sqlite_master").to_df()
type name tbl_name rootpage sql
0 table assistenten assistenten 0 CREATE TABLE assistenten(persnr INTEGER PRIMAR...
1 table hoeren hoeren 0 CREATE TABLE hoeren(matrnr INTEGER, vorlnr INT...
2 table professoren professoren 0 CREATE TABLE professoren(persnr INTEGER PRIMAR...
3 table pruefen pruefen 0 CREATE TABLE pruefen(matrnr INTEGER, vorlnr IN...
4 table studenten studenten 0 CREATE TABLE studenten(matrnr INTEGER PRIMARY ...
5 table voraussetzen voraussetzen 0 CREATE TABLE voraussetzen(vorgaenger INTEGER, ...
6 table vorlesungen vorlesungen 0 CREATE TABLE vorlesungen(vorlnr INTEGER PRIMAR...
from IPython.display import display_html

css = "style='display:inline; margin: 0px 20px 0px 0px; vertical-align: top;'"


display_html(
    duck_con.sql("select * from professoren")
    .to_df()
    .style.set_table_attributes(css)
    .set_caption("<strong>Professoren</strong>")
    ._repr_html_()
    + duck_con.sql("select * from studenten")
    .to_df()
    .style.set_table_attributes(css)
    .set_caption("<strong>Studenten</strong>")
    ._repr_html_()
    + duck_con.sql("select * from vorlesungen")
    .to_df()
    .style.set_table_attributes(css)
    .set_caption("<strong>Vorlesungen</strong>")
    ._repr_html_()
    + duck_con.sql("select * from voraussetzen")
    .to_df()
    .style.set_table_attributes(css)
    .set_caption("<strong>Voraussetzungen</strong>")
    ._repr_html_()
    + duck_con.sql("select * from hoeren")
    .to_df()
    .style.set_table_attributes(css)
    .set_caption("<strong>Hören</strong>")
    ._repr_html_()
    + duck_con.sql("select * from pruefen")
    .to_df()
    .style.set_table_attributes(css)
    .set_caption("<strong>Prüfen</strong>")
    ._repr_html_()
    + duck_con.sql("select * from assistenten")
    .to_df()
    .style.set_table_attributes(css)
    .set_caption("<strong>Assistenten</strong>")
    ._repr_html_(),
    raw=True,
)
Professoren
  persnr name rang raum
0 2125 Sokrates C4 226
1 2126 Russel C4 232
2 2127 Kopernikus C3 310
3 2133 Popper C3 52
4 2134 Augustinus C3 309
5 2136 Curie C4 36
6 2137 Kant C4 7
Studenten
  matrnr name semester
0 24002 Xenokrates 18
1 25403 Jonas 12
2 26120 Fichte 10
3 26830 Aristoxenos 8
4 27550 Schopenhauer 6
5 28106 Carnap 3
6 29120 Theophrastos 2
7 29555 Feuerbach 2
Vorlesungen
  vorlnr titel sws gelesenvon
0 4052 Logik 4 2125
1 4630 Die 3 Kritiken 4 2137
2 5001 Grundzuege 4 2137
3 5022 Glaube und Wissen 2 2134
4 5041 Ethik 4 2125
5 5043 Erkenntnistheorie 3 2126
6 5049 Maeeutik 2 2125
7 5052 Wissenschaftstheorie 3 2126
8 5216 Bioethik 2 2126
9 5259 Der Wiener Kreis 2 2133
Voraussetzungen
  vorgaenger nachfolger
0 5001 5041
1 5001 5043
2 5001 5049
3 5041 5216
4 5043 5052
5 5041 5052
6 5052 5259
Hören
  matrnr vorlnr
0 26120 5001
1 27550 5001
2 27550 4052
3 28106 5041
4 28106 5052
5 28106 5216
6 28106 5259
7 29120 5001
8 29120 5041
9 29120 5049
10 29555 5022
11 25403 5022
12 29555 5001
Prüfen
  matrnr vorlnr persnr note
0 28106 5001 2126 1.000000
1 25403 5041 2125 2.000000
2 27550 4630 2137 2.000000
Assistenten
  persnr name fachgebiet boss
0 3002 Platon Ideenlehre 2125
1 3003 Aristoteles Syllogistik 2125
2 3004 Wittgenstein Sprachtheorie 2126
3 3005 Rhetikus Planetenbewegung 2127
4 3006 Newton Keplersche Gesetze 2127
5 3007 Spinoza Gott und Natur 2134
con = Check(
    duck_con,
    hashes=[
        ["82c5f00103c8fdc496e60e58a092ccd3", "1.1"],
        ["893e57904471455be8fe2e88be78b42f", "1.2"],
        ["9dc1d3ebc2f41494f72695cd4d85998e", "1.3"],
        ["45910b56c5f8023603d5d0b6edd8df4f", "1.4"],
        ["5ca534e9f807a0629b4fe17f7fa5352c", "1.5"],
        ["1cbbe6645d90c69fd180e67ce6269323", "1.6"],
        ["d4060051a9ce856d39d4b20130cc4839", "1.7.1"],
        ["16d7a32f47fd82ed90a8ea1c76a79dec", "1.7.2"],
        ["9c365417c4abd1919135d17062240b08", "1.7.3"],
        ["26affab1d343c6ddff7a5da3f4e190b4", "1.8"],
        ["f788bb6ffdff1f437abaeaf2b443adbf", "1.9"],
        ["550ce8cafb2b80935ccd517c43e68125", "1.10"],
    ],
)

Aufgabe 1#

Formulieren Sie zu den folgenden Aussagen passende SQL-DQL Anfragen.

Aufgabe 1.1#

Alle Studierenden.

con.sql(
    """

"""
)

Hide code cell content

#### Musterlösung

con.sql(
    """
SELECT * 
FROM studenten;
"""
)
🎉 Richtiges Ergebnis für Aufgabe 1.1 🎉
┌────────┬──────────────┬──────────┐
│ matrnr │     name     │ semester │
│ int32  │   varchar    │  int32   │
├────────┼──────────────┼──────────┤
│  24002 │ Xenokrates   │       18 │
│  25403 │ Jonas        │       12 │
│  26120 │ Fichte       │       10 │
│  26830 │ Aristoxenos  │        8 │
│  27550 │ Schopenhauer │        6 │
│  28106 │ Carnap       │        3 │
│  29120 │ Theophrastos │        2 │
│  29555 │ Feuerbach    │        2 │
└────────┴──────────────┴──────────┘

Aufgabe 1.2#

Die Namen der Studierenden, die im zweiten Semester sind.

con.sql(
    """

"""
)

Hide code cell content

#### Musterlösung

con.sql(
    """
SELECT name
FROM studenten 
WHERE semester = 2;
"""
)
🎉 Richtiges Ergebnis für Aufgabe 1.2 🎉
┌──────────────┐
│     name     │
│   varchar    │
├──────────────┤
│ Theophrastos │
│ Feuerbach    │
└──────────────┘

Hide code cell content

#### Musterlösung

con.sql(
    """
SELECT name
FROM studenten 
WHERE semester = 2;
"""
)
🎉 Richtiges Ergebnis für Aufgabe 1.2 🎉
┌──────────────┐
│     name     │
│   varchar    │
├──────────────┤
│ Theophrastos │
│ Feuerbach    │
└──────────────┘

Aufgabe 1.3#

Die Namen aller Studierenden und die Vorlesungsnummern, die Sie hören. Hört eine studierende Person keine Vorlesung, so soll diese Person nicht in der Ergebnisrelation auftauchen.

con.sql(
    """

"""
)

Hide code cell content

#### Musterlösung

con.sql(
    """
SELECT name, vorlnr
FROM studenten s, hoeren h
WHERE s.matrnr = h.matrnr;
"""
)
🎉 Richtiges Ergebnis für Aufgabe 1.3 🎉
┌──────────────┬────────┐
│     name     │ vorlnr │
│   varchar    │ int32  │
├──────────────┼────────┤
│ Fichte       │   5001 │
│ Schopenhauer │   5001 │
│ Schopenhauer │   4052 │
│ Carnap       │   5041 │
│ Carnap       │   5052 │
│ Carnap       │   5216 │
│ Carnap       │   5259 │
│ Theophrastos │   5001 │
│ Theophrastos │   5041 │
│ Theophrastos │   5049 │
│ Feuerbach    │   5022 │
│ Jonas        │   5022 │
│ Feuerbach    │   5001 │
├──────────────┴────────┤
│ 13 rows     2 columns │
└───────────────────────┘

Aufgabe 1.4#

Die Namen aller Studierenden, die keine Vorlesung besuchen.

con.sql(
    """
    
"""
)

Hide code cell content

#### Musterlösung

con.sql(
    """
SELECT name 
FROM studenten 
WHERE matrnr NOT IN (SELECT matrnr FROM hoeren)
"""
)
🎉 Richtiges Ergebnis für Aufgabe 1.4 🎉
┌─────────────┐
│    name     │
│   varchar   │
├─────────────┤
│ Xenokrates  │
│ Aristoxenos │
└─────────────┘

Aufgabe 1.5#

Die Namen aller Studierenden und Professor*innen.

con.sql(
    """

"""
)

Hide code cell content

#### Musterlösung

con.sql(
    """
SELECT p.name 
FROM professoren p
    
UNION
    
SELECT s.name 
FROM studenten s;
"""
)
🎉 Richtiges Ergebnis für Aufgabe 1.5 🎉
┌──────────────┐
│     name     │
│   varchar    │
├──────────────┤
│ Jonas        │
│ Curie        │
│ Kant         │
│ Schopenhauer │
│ Kopernikus   │
│ Aristoxenos  │
│ Xenokrates   │
│ Sokrates     │
│ Carnap       │
│ Popper       │
│ Russel       │
│ Fichte       │
│ Augustinus   │
│ Theophrastos │
│ Feuerbach    │
├──────────────┤
│   15 rows    │
└──────────────┘

Aufgabe 1.6#

Die Matrikelnummern aller Studierenden und die Anzahl der von ihnen besuchten Vorlesungen. Geben Sie nur Studierende aus, welche mindestens 3 Vorlesungen besuchen.

con.sql(
    """

"""
)

Hide code cell content

#### Musterlösung

con.sql(
    """
SELECT s.matrnr, COUNT(h.vorlnr) Anzahl 
FROM Studenten s JOIN Hoeren h ON s.matrnr = h.matrnr
GROUP BY s.matrnr
HAVING Anzahl >=3;
"""
)
🎉 Richtiges Ergebnis für Aufgabe 1.6 🎉
┌────────┬────────┐
│ matrnr │ Anzahl │
│ int32  │ int64  │
├────────┼────────┤
│  28106 │      4 │
│  29120 │      3 │
└────────┴────────┘

Aufgabe 1.7#

Die Matrikelnummern aller Studierenden und die Anzahl der von ihnen besuchten Vorlesungen. Auch Studierende, die keine Vorlesungen besuchen, sollen ausgegeben werden. Geben Sie die Ergebnisinstanzen absteigend sortiert nach der Anzahl der besuchten Vorlesungen an.

Aufgabe 1.7.1#

Bestimmen Sie im ersten Schritt nur die Matrikelnummern aller Studierenden und die Anzahl der von ihnen besuchten Vorlesungen, wenn diese Studierenden mindestens eine Vorlesung besuchen. Geben Sie die Ergebnisinstanzen absteigend sortiert nach der Anzahl der besuchten Vorlesungen an.

Zur Überprüfung mit Hash: Bei gleicher Anzahl sortiere die Matrikelnummer aufsteigend.

con.sql(
    """

"""
)

Hide code cell content

#### Musterlösung

con.sql(
    """
SELECT s.matrnr, COUNT(h.vorlnr) Anzahl 
FROM Studenten s JOIN Hoeren h ON s.matrnr = h.matrnr
GROUP BY s.matrnr
ORDER BY Anzahl desc, s.matrnr ASC;
"""
)
🎉 Richtiges Ergebnis für Aufgabe 1.7.1 🎉
┌────────┬────────┐
│ matrnr │ Anzahl │
│ int32  │ int64  │
├────────┼────────┤
│  28106 │      4 │
│  29120 │      3 │
│  27550 │      2 │
│  29555 │      2 │
│  25403 │      1 │
│  26120 │      1 │
└────────┴────────┘

Aufgabe 1.7.2#

Bestimmen Sie nun die Matrikelnummern aller Studierenden, welche keine Vorlesungen besuchen, und gebe sie die Matrikelnummer aus zusammen mit dem Wert 0 in einer Spalte Anzahl.

con.sql(
    """

"""
)

Hide code cell content

#### Musterlösung

con.sql(
    """
SELECT s.matrnr, 0 Anzahl
FROM Studenten s
WHERE NOT EXISTS(SELECT * FROM Hoeren h WHERE h.matrnr = s.matrnr);
"""
)

# 16d7a32f47fd82ed90a8ea1c76a79dec
🎉 Richtiges Ergebnis für Aufgabe 1.7.2 🎉

┌────────┬────────┐
│ matrnr │ Anzahl │
│ int32  │ int32  │
├────────┼────────┤
│  24002 │      0 │
│  26830 │      0 │
└────────┴────────┘

Aufgabe 1.7.3#

Bestimmen Sie nun das Ergebnis der Hauptanfrage:

Die Matrikelnummern aller Studierenden und die Anzahl der von ihnen besuchten Vorlesungen. Auch Studierende, die keine Vorlesungen besuchen, sollen ausgegeben werden. Geben Sie die Ergebnis-Instanzen absteigend sortiert nach der Anzahl der besuchten Vorlesungen an.

Zur Überprüfung mit Hash: Bei gleicher Anzahl sortiere die Matrikelnummer aufsteigend.

con.sql(
    """

"""
)

Hide code cell content

#### Musterlösung

con.sql(
    """
SELECT s.matrnr, COUNT(h.vorlnr) Anzahl 
FROM Studenten s JOIN Hoeren h ON s.matrnr = h.matrnr
GROUP BY s.matrnr

UNION

SELECT s.matrnr, 0 Anzahl 
FROM Studenten s
WHERE NOT EXISTS(SELECT * FROM Hoeren h WHERE h.matrnr = s.matrnr)

ORDER BY Anzahl desc, s.matrnr ASC;
"""
)
🎉 Richtiges Ergebnis für Aufgabe 1.7.3 🎉
┌────────┬────────┐
│ matrnr │ Anzahl │
│ int32  │ int64  │
├────────┼────────┤
│  28106 │      4 │
│  29120 │      3 │
│  27550 │      2 │
│  29555 │      2 │
│  25403 │      1 │
│  26120 │      1 │
│  24002 │      0 │
│  26830 │      0 │
└────────┴────────┘

Aufgabe 1.8*#

Geben Sie die Namen der Vorlesungen an, welche keine Nachfolgevorlesungen haben.

con.sql(
    """
                     
"""
)

Hide code cell content

#### Musterlösung

con.sql(
    """
SELECT titel 
FROM vorlesungen 
WHERE vorlnr NOT IN (SELECT vorgaenger
FROM voraussetzen);
"""
)
🎉 Richtiges Ergebnis für Aufgabe 1.8 🎉
┌───────────────────┐
│       titel       │
│      varchar      │
├───────────────────┤
│ Logik             │
│ Die 3 Kritiken    │
│ Glaube und Wissen │
│ Maeeutik          │
│ Bioethik          │
│ Der Wiener Kreis  │
└───────────────────┘

Aufgabe 1.9*#

Die Namen von Professoren und Professorinnen und die Durchschnittsnote, welche Studierende bei diesen erzielen. Sortieren Sie das Ergebnis absteigend nach der Durchschnittsnote.

Zur Überprüfung mit Hash: Bei gleicher Durchschnittsnote sortiere nach dem den Namen.

con.sql(
    """

"""
)

Hide code cell content

#### Musterlösung

con.sql(
    """
SELECT p.name, Durchschnittsnote
FROM professoren p JOIN (SELECT p.persnr, avg(note) AS Durchschnittsnote
                         FROM professoren p JOIN pruefen ON p.persnr = pruefen.persnr 
                         GROUP BY p.persnr) AS tmp ON p.persnr = tmp.persnr 
ORDER BY Durchschnittsnote, p.name;
"""
)
🎉 Richtiges Ergebnis für Aufgabe 1.9 🎉
┌──────────┬───────────────────┐
│   name   │ Durchschnittsnote │
│ varchar  │      double       │
├──────────┼───────────────────┤
│ Russel   │               1.0 │
│ Kant     │               2.0 │
│ Sokrates │               2.0 │
└──────────┴───────────────────┘

Aufgabe 1.10*#

Die Namen der Studierenden, welche ein Modul belegen und auch parallel ein darauf aufbauendes (Nachfolger) Modul belegen.

con.sql(
    """

"""
)

Hide code cell content

#### Musterlösung

con.sql(
    """
SELECT DISTINCT s.name
FROM studenten s JOIN hoeren h ON s.matrnr = h.matrnr
                 JOIN voraussetzen v ON h.vorlnr = v.Vorgaenger
                 JOIN hoeren h2 ON v.Nachfolger = h2.vorlnr
WHERE s.matrnr = h2.matrnr;
"""
)
🎉 Richtiges Ergebnis für Aufgabe 1.10 🎉
┌──────────────┐
│     name     │
│   varchar    │
├──────────────┤
│ Carnap       │
│ Theophrastos │
└──────────────┘
con.close()

Aufgabe 2#

Im Folgenden arbeiten wir mit dem Ihnen bekannten Regattaschema.

Formulieren Sie zu den folgenden Aussagen passende SQL-DQL Anfragen.

Zuerst öffnen wir die Datenbank.

duck_con = duckdb.connect("../resources/06_sql_dql/wettfahrt_ue.duckdb", read_only=False)
con = Check(
    duck_con,
    hashes=[
        ["93a65e62fc34df872038fc57f8f9fa2a", "2.1"],
        ["933e5df76f96d65c4bfbe7145a1e301a", "2.2"],
        ["eaeedaa7cc7e4318807d5159906f8e1d", "2.3"],
        ["c9b028e8e56cf2363badc3fe280906eb", "2.5"],
        ["511213b8a1e9862f238eccd8a1b9b1b6", "2.6"],
        ["8693f90eb469b6244b73eef97079aed7", "2.7.1"],
        ["18eac337c4439e181265f60340f14731", "2.7.2"],
        ["007c39966151322a90b8221820398e52", "2.7.3"],
        ["5a48f0574c3d62d26a08f3888732f792", "2.7.4"],
        ["2a669e47b6ecfa30b54a2aa2f1c50759", "2.7.5"],
        "bb843a25cc58f37b2e6bfe1d75cd7373",
        "ff1136f19b74186291424cf7c817df52",
        ["7b36f4c1068af7b11e3a3c009808443f", "3.1"],
    ],
)

Aufgabe 2.1#

Die Namen der Regatten und Boote, die sie gewonnen haben.

con.sql(
    """
    
"""
)

Hide code cell content

#### Musterlösung

con.sql(
    """
SELECT t.name, w.name
FROM teilnehmer t JOIN platzierung p ON t.segelNr = p.SegelNr
                  JOIN wettfahrt w ON p.wettfahrt = w.fahrtnr
WHERE Platz = 1;
"""
)
🎉 Richtiges Ergebnis für Aufgabe 2.1 🎉

┌──────────┬──────────────────────────┐
│   Name   │           Name           │
│ varchar  │         varchar          │
├──────────┼──────────────────────────┤
│ Carla F. │ Franz Huber Gedenk Preis │
│ Marie    │ Moorpokal                │
│ Celeste  │ Herbstmeister            │
│ Windrose │ Blaues Band              │
└──────────┴──────────────────────────┘

Aufgabe 2.2#

Die Bootsfarbe, die am häufigsten vertreten ist und die SegelNr der Boote, die in dieser Farbe gestrichen oder lackiert sind.

con.sql(
    """
"""
)

Hide code cell content

#### Musterlösung

con.sql(
    """
WITH COUNT AS (SELECT farbe, COUNT(farbe) as Anzahl
             FROM Teilnehmer 
             GROUP BY farbe),

MAX AS (SELECT MAX(Anzahl)
        FROM COUNT),

Farbe AS (SELECT farbe
          FROM teilnehmer 
          GROUP BY farbe
          HAVING COUNT(farbe) == (SELECT * FROM MAX))

          
SELECT f.farbe, t.segelnr
FROM farbe f JOIN teilnehmer t ON f.farbe = t.farbe;

"""
)
🎉 Richtiges Ergebnis für Aufgabe 2.2 🎉

┌─────────┬──────────┐
│  Farbe  │ SegelNr  │
│ varchar │ varchar  │
├─────────┼──────────┤
│ Weiß    │ GER 1393 │
│ Weiß    │ GER 3999 │
│ Weiß    │ GER 4833 │
└─────────┴──────────┘

Hide code cell content

#### Musterlösung


con.sql(
    """
WITH COUNT AS (SELECT farbe, COUNT(farbe) as Anzahl
               FROM Teilnehmer 
               GROUP BY farbe
               ORDER BY Anzahl desc
               LIMIT 1)
          
SELECT c.farbe, t.segelnr
FROM COUNT c JOIN teilnehmer t ON c.farbe = t.farbe;
"""
)
🎉 Richtiges Ergebnis für Aufgabe 2.2 🎉
┌─────────┬──────────┐
│  Farbe  │ SegelNr  │
│ varchar │ varchar  │
├─────────┼──────────┤
│ Weiß    │ GER 1393 │
│ Weiß    │ GER 3999 │
│ Weiß    │ GER 4833 │
└─────────┴──────────┘

Aufgabe 2.3*#

Die Namen der Pokale, an denen keine Holzboote teilgenommen haben. Dabei soll jeder Name nur genau einmal vorkommen.

con.sql(
    """

"""
)

Hide code cell content

#### Musterlösung

con.sql(
    """
WITH mitHolz AS (SELECT w.fahrtnr  
FROM Bootsklasse b JOIN Teilnehmer t ON b.klasse = t.Bootsklasse 
                   JOIN platzierung p ON t.SegelNr = p.SegelNr 
                   JOIN Wettfahrt w ON p.wettfahrt = w.fahrtnr
WHERE b.bauart = 'Holz')

SELECT name
FROM Wettfahrt
WHERE fahrtnr NOT IN (SELECT * FROM mitHolz)
"""
)
🎉 Richtiges Ergebnis für Aufgabe 2.3 🎉
┌─────────────┐
│    Name     │
│   varchar   │
├─────────────┤
│ Blaues Band │
└─────────────┘

Aufgabe 2.4*#

Die Namen der Pokale, an denen ausschließlich Holzboote teilgenommen haben.

con.sql(
    """

"""
)

Hide code cell content

#### Musterlösung

con.sql(
    """
WITH KeineHolzboote AS (SELECT DISTINCT w.name
                        FROM Bootsklasse b JOIN Teilnehmer t ON b.klasse = t.Bootsklasse 
                                           JOIN platzierung p ON t.SegelNr = p.SegelNr 
                                           JOIN Wettfahrt w ON p.wettfahrt = w.fahrtnr
                        WHERE Bauart != 'Holz' )

SELECT w.Name 
FROM Wettfahrt w
WHERE w.name NOT IN (SELECT * FROM KeineHolzBoote);
"""
)
🎉 Richtiges Ergebnis für eine Aufgabe 🎉
┌───────────────┐
│     Name      │
│    varchar    │
├───────────────┤
│ Moorpokal     │
│ Herbstmeister │
└───────────────┘

Aufgabe 2.5*#

Finde alle Teilnehmer (SegelNr und Name), die an mindestens zwei verschiedenen Wettfahrten teilgenommen haben.

con.sql(
    """

"""
)

Hide code cell content

#### Musterlösung

con.sql(
    """
SELECT t.SegelNr, t.eigner
FROM Teilnehmer t JOIN Platzierung p ON t.SegelNr = p.SegelNr
GROUP BY t.SegelNr, t.eigner
HAVING COUNT(DISTINCT p.wettfahrt) >= 2;
"""
)
🎉 Richtiges Ergebnis für Aufgabe 2.5 🎉
┌──────────┬───────────┐
│ SegelNr  │  Eigner   │
│ varchar  │  varchar  │
├──────────┼───────────┤
│ GER 4318 │ D. Dummer │
│ GER 4995 │ S. Schott │
└──────────┴───────────┘

Aufgabe 2.6*#

Finde alle Teilnehmer (SegelNr und Eigner) welche keine Platzierung erlangt haben.

con.sql(
    """

"""
)

Hide code cell content

#### Musterlösung

con.sql(
    """
SELECT t.SegelNr, t.eigner
FROM Teilnehmer t 
WHERE SegelNr NOT IN (SELECT segelNr FROM platzierung);
"""
)
🎉 Richtiges Ergebnis für Aufgabe 2.6 🎉
┌──────────┬──────────┐
│ SegelNr  │  Eigner  │
│ varchar  │ varchar  │
├──────────┼──────────┤
│ GER 3999 │ E. Ernst │
└──────────┴──────────┘

Aufgabe 2.7: Vorbereitung Hausaufgabe#

Die folgende Anfrage ähnelt von der Komplexität der Anfrage dem, was Sie unter anderem in der Hausaufgabe erwarten können. Lösen Sie die Aufgabe, indem Sie zuvor schrittweise die einzelnen Teilaufgaben bzw. Teilanfragen lösen. Nutzen Sie, wo möglich, die zuvor eingeführten Common Table Expressions.

Hauptanfrage: Bestimmen Sie die Namen der Wettfahrten, bei denen alle Teilnehmer eine im Durchschnitt höhere Platzierung haben, als der Durchschnitt aller Boote bei allen Wettfahrten.

Aufgabe 2.7.1#

Bestimmen Sie den Durchschnitt aller Platzierungen. Wenn ein Boot ein Rennen nicht abgeschlossen hat (dnf/NULL), dann soll dies in den Durchschnitt mit dem Wert 5 einfließen. Der Wert von dnf bzw. NULL gilt auch für die folgenden Teilaufgaben.

con.sql(
    """

"""
)

Hide code cell content

#### Musterlösung

con.sql(
    """
SELECT AVG(CASE WHEN platz IS NULL THEN 5 ELSE platz END) AS Durchschnittsplatzierung
FROM Platzierung;
"""
)
🎉 Richtiges Ergebnis für Aufgabe 2.7.1 🎉

┌──────────────────────────┐
│ Durchschnittsplatzierung │
│          double          │
├──────────────────────────┤
│                     2.25 │
└──────────────────────────┘

Aufgabe 2.7.2#

Bestimmen Sie die durchschnittliche Platzierung pro Boot (SegelNr).

con.sql(
    """

"""
)

Hide code cell content

#### Musterlösung

con.sql(
    """                   
SELECT p.SegelNr, avg(CASE WHEN platz IS NULL THEN 5 ELSE platz END)
FROM Teilnehmer t JOIN Platzierung p ON t.SegelNr = p.SegelNr
GROUP BY p.SegelNr;
"""
)
🎉 Richtiges Ergebnis für Aufgabe 2.7.2 🎉
┌──────────┬───────────────────────────────────────────────────────────┐
│ SEGELNR  │ avg(CASE  WHEN ((platz IS NULL)) THEN (5) ELSE platz END) │
│ varchar  │                          double                           │
├──────────┼───────────────────────────────────────────────────────────┤
│ GER 4318 │                                                       1.5 │
│ GER 5703 │                                                       2.0 │
│ GER 5505 │                                                       3.0 │
│ GER 1393 │                                                       1.0 │
│ GER 3876 │                                                       4.0 │
│ GER 4995 │                                                       1.5 │
│ GER 5107 │                                                       1.0 │
│ GER 4309 │                                                       3.0 │
│ GER 5503 │                                                       2.0 │
│ GER 4833 │                                                       5.0 │
├──────────┴───────────────────────────────────────────────────────────┤
│ 10 rows                                                    2 columns │
└──────────────────────────────────────────────────────────────────────┘

Aufgabe 2.7.3#

Bestimmen Sie die Boote (SegelNr), welche im Durchschnitt besser Platzierungen erzielen, als der Durchschnitt aller Platzierungen. Nutzen Sie (Teil-)Anfragen von zuvor.

Hide code cell content

#### Musterlösung

con.sql(
    """   

"""
)

Hide code cell content

#### Musterlösung

con.sql(
    """   
WITH Durchschnitt AS (SELECT AVG(CASE WHEN platz IS NULL THEN 5 ELSE platz END) AS Durchschnittsplatzierung
                      FROM Platzierung), 
                      
DurchschnittProBoot AS (SELECT p.SegelNr, avg(CASE WHEN platz IS NULL THEN 5 ELSE platz END) AS Durchschnittsplatzierung
                        FROM Teilnehmer t JOIN Platzierung p ON t.SegelNr = p.SegelNr
                        GROUP BY p.SegelNr) 
                     
SELECT *
FROM DurchschnittProBoot d
WHERE d.Durchschnittsplatzierung < (SELECT d2.Durchschnittsplatzierung FROM Durchschnitt d2);
"""
)
🎉 Richtiges Ergebnis für Aufgabe 2.7.3 🎉
┌──────────┬──────────────────────────┐
│ SEGELNR  │ Durchschnittsplatzierung │
│ varchar  │          double          │
├──────────┼──────────────────────────┤
│ GER 5703 │                      2.0 │
│ GER 5503 │                      2.0 │
│ GER 4995 │                      1.5 │
│ GER 5107 │                      1.0 │
│ GER 4318 │                      1.5 │
│ GER 1393 │                      1.0 │
└──────────┴──────────────────────────┘

Aufgabe 2.7.4#

Bestimmen Sie die Anzahl an Teilnehmern (SegelNr) pro Regatta.

con.sql(
    """

"""
)

Hide code cell content

#### Musterlösung

con.sql(
    """
SELECT w.fahrtNr, COUNT(SegelNr)
FROM Wettfahrt w JOIN Platzierung p ON w.fahrtNr = p.wettfahrt 
GROUP BY w.fahrtNr;
"""
)
🎉 Richtiges Ergebnis für Aufgabe 2.7.4 🎉
┌─────────┬────────────────┐
│ FahrtNr │ count(SegelNr) │
│  int16  │     int64      │
├─────────┼────────────────┤
│       1 │              2 │
│       2 │              2 │
│       3 │              5 │
│       4 │              3 │
└─────────┴────────────────┘

Aufgabe 2.7.5#

Bestimmen Sie die Anzahl der überdurchschnittlich guten Teilnehmer (SegelNr) pro Regatta. Nutzen Sie Ihr Ergebnis aus Teilaufgabe 2.7.3.

con.sql(
    """

"""
)

Hide code cell content

#### Musterlösung

con.sql(
    """
WITH Durchschnitt AS (SELECT AVG(CASE WHEN platz IS NULL THEN 5 ELSE platz END) AS Durchschnittsplatzierung
                      FROM Platzierung), 
                      
DurchschnittProBoot AS (SELECT p.SegelNr, avg(CASE WHEN platz IS NULL THEN 5 ELSE platz END) AS Durchschnittsplatzierung
                        FROM Teilnehmer t JOIN Platzierung p ON t.SegelNr = p.SegelNr
                        GROUP BY p.SegelNr), 
                     
ueberdurchschnittlicheBoote AS (SELECT *
                                FROM DurchschnittProBoot d
                                WHERE d.Durchschnittsplatzierung < (SELECT d2.Durchschnittsplatzierung 
                                                                    FROM Durchschnitt d2))
SELECT w.fahrtNr, COUNT(u.SegelNr) 
FROM ueberdurchschnittlicheBoote u JOIN Platzierung p ON u.SegelNr = p.SegelNr
                                   JOIN Wettfahrt w ON p.wettfahrt = w.fahrtNr
GROUP BY w.fahrtNr;
"""
)
🎉 Richtiges Ergebnis für Aufgabe 2.7.5 🎉
┌─────────┬──────────────────┐
│ FahrtNr │ count(u.SegelNr) │
│  int16  │      int64       │
├─────────┼──────────────────┤
│       1 │                2 │
│       2 │                2 │
│       3 │                2 │
│       4 │                2 │
└─────────┴──────────────────┘

Aufgabe 2.7.6#

Lösen Sie nun die Hauptaufgabe.

Bestimmen Sie die Namen der Wettfahrten, bei denen alle Teilnehmer eine im Durchschnitt höhere Platzierung haben, als der Durchschnitt aller Boote bei allen Wettfahrten.

con.sql(
    """

"""
)

Hide code cell content

#### Musterlösung

con.sql(
    """
WITH Durchschnitt AS (SELECT AVG(CASE WHEN platz IS NULL THEN 5 ELSE platz END) AS Durchschnittsplatzierung
                      FROM Platzierung), 
                      
DurchschnittProBoot AS (SELECT p.SegelNr, avg(CASE WHEN platz IS NULL THEN 5 ELSE platz END) AS Durchschnittsplatzierung
                        FROM Teilnehmer t JOIN Platzierung p ON t.SegelNr = p.SegelNr
                        GROUP BY p.SegelNr), 
                     
ueberdurchschnittlicheBoote AS (SELECT *
                                FROM DurchschnittProBoot d
                                WHERE d.Durchschnittsplatzierung < (SELECT d2.Durchschnittsplatzierung 
                                                                    FROM Durchschnitt d2)),
                                                                    
teilnehmerProWettfahrt AS (SELECT w.fahrtNr, COUNT(SegelNr) AS Anzahl 
                           FROM Wettfahrt w JOIN Platzierung p ON w.fahrtNr = p.wettfahrt 
                           GROUP BY w.fahrtNr),                       
                                                                    
ueberdurchschnittlicheProWettfahrt AS (SELECT w.fahrtNr, COUNT(u.SegelNr) AS Anzahl 
                                       FROM ueberdurchschnittlicheBoote u JOIN Platzierung p ON u.SegelNr = p.SegelNr
                                                                          JOIN Wettfahrt w ON p.wettfahrt = w.fahrtNr
                                       GROUP BY w.fahrtNr)
                                       
SELECT w.name
FROM teilnehmerProWettfahrt t JOIN ueberdurchschnittlicheProWettfahrt u ON t.FahrtNr = u.FahrtNr
                              JOIN Wettfahrt w ON t.fahrtNr = w.fahrtNr
WHERE t.Anzahl = u.Anzahl;
"""
)
🎉 Richtiges Ergebnis für eine Aufgabe 🎉
┌───────────────┐
│     Name      │
│    varchar    │
├───────────────┤
│ Moorpokal     │
│ Herbstmeister │
└───────────────┘

Aufgabe 2.8*#

Die Namen der Wettfahrten, an denen die wenigsten Boote teilgenommen haben.

con.sql(
    """

"""
)

Hide code cell content

#### Musterlösung

"""
Die Lösung nimmt an, dass das Feld Name in Wettfahrt nicht eindeutig sein muss, da dies kein
Schlüsselattribut ist und es z.B. viele Silversterläufe gibt und genau so gleich benannte Wettfahrtnamen
geben könnte."""

con.sql(
    """
WITH Last AS (SELECT COUNT(t.SegelNr) AS AnzahlBoote
              FROM teilnehmer t JOIN platzierung p ON t.segelNr = p.segelNr 
                                JOIN wettfahrt w ON w.fahrtNr = p.wettfahrt 
              GROUP BY FahrtNr
              ORDER BY COUNT(t.SegelNr) asc
              LIMIT 1),

fahrtNr AS (SELECT w.fahrtNr
            FROM teilnehmer t JOIN platzierung p ON t.segelNr = p.segelNr 
                              JOIN wettfahrt w ON w.fahrtNr = p.wettfahrt 
            GROUP BY w.fahrtNr
            HAVING COUNT(t.SegelNr) == (SELECT * FROM Last))

SELECT w.name 
FROM wettfahrt w JOIN fahrtNr f ON f.fahrtNr == w.fahrtNr;
"""
)
🎉 Richtiges Ergebnis für eine Aufgabe 🎉
┌───────────────┐
│     Name      │
│    varchar    │
├───────────────┤
│ Moorpokal     │
│ Herbstmeister │
└───────────────┘

Aufgabe 2.9*#

In der Relationalen Algebra gibt es den Divisions Operator. Die Division ermöglicht es, Entitäten zu finden, die mit allen Entitäten einer anderen Relation interagieren. Da es die Division in SQL nicht direkt gibt, ist es nun Ihre Aufgabe, die Division durch existierende SQL-Operationen darzustellen.

(Link zur Division: https://www.red-gate.com/simple-talk/databases/sql-server/t-sql-programming-sql-server/divided-we-stand-the-sql-of-relational-division/) (Alternativ kann auch ein Ansatz, die Division darzustellen, verwendet werden, den man gut mit CTEs kombinieren kann: https://www.geeksforgeeks.org/sql-division/).

Die Wettfahrten, an denen Boote aller Bauarten teilgenommen haben.

con.sql(
    """

"""
)

Hide code cell content

#### Musterlösung

con.sql(
    """
SELECT DISTINCT w.Name
FROM Bootsklasse b3 JOIN Teilnehmer t2 ON b3.klasse = t2.bootsklasse 
                    JOIN Platzierung p2 ON t2.SegelNr = p2.SegelNr
                    JOIN wettfahrt w ON p2.wettfahrt = w.fahrtNr
WHERE not EXISTS (
    SELECT *
    FROM Bootsklasse b2
    WHERE not EXISTS (
        SELECT *
        FROM Bootsklasse b1 JOIN Teilnehmer t ON b1.klasse = t.bootsklasse 
                            JOIN Platzierung p ON t.SegelNr = p.SegelNr
        WHERE p2.WETTFAHRT = p.WETTFAHRT
        AND b2.bauart = b1.bauart
    )
);
"""
)
🎉 Richtiges Ergebnis für eine Aufgabe 🎉
┌──────────────────────────┐
│           Name           │
│         varchar          │
├──────────────────────────┤
│ Franz Huber Gedenk Preis │
└──────────────────────────┘

Hide code cell content

#### Musterlösung

con.sql(
    """
WITH kreuzProdukt AS (SELECT Bauart, FahrtNr
                      FROM Bootsklasse, wettfahrt),
                      
bauartenBeiWettfahrten AS (SELECT Bauart, FahrtNr
                           FROM Bootsklasse b JOIN Teilnehmer t ON b.klasse = t.bootsklasse
                                              JOIN Platzierung p ON t.SegelNr = p.SegelNr 
                                              JOIN wettfahrt w ON p.wettfahrt = w.fahrtNr),
                                              
differenz AS (SELECT * FROM kreuzProdukt 

              EXCEPT

              SELECT * FROM bauartenBeiWettfahrten)

SELECT DISTINCT NAME 
FROM wettfahrt w 
WHERE FahrtNr NOT IN (SELECT FahrtNr FROM differenz);
"""
)
🎉 Richtiges Ergebnis für eine Aufgabe 🎉
┌──────────────────────────┐
│           Name           │
│         varchar          │
├──────────────────────────┤
│ Franz Huber Gedenk Preis │
└──────────────────────────┘

Aufgabe 3#

Formulieren Sie die folgenden Ausdrücke der relationalen Algebra zu äquivalenten SQL-DQL Anfragen.

Aufgabe 3.1#

\(\pi_{SegelNr, Wettfahrt, Platz, Name, Datum, Zeit}(\sigma_{Name="Herbstmeister"} (Wettfahrt\bowtie_{FahrtNr=Wettfahrt}(\sigma_{Platz=1}Platzierung)))\)

con.sql(
    """
"""
)

Hide code cell content

#### Musterlösung

"""
Hinweis:
Der Ausdruck der Relationalen Alegebra ist identisch zu Aufgabe 4.2 aus Blatt 5.
"""

con.sql(
    """
SELECT SegelNr, Wettfahrt, Platz, Name, Datum, Zeit 
FROM Platzierung p JOIN Wettfahrt w ON w.fahrtNr = p.wettfahrt
WHERE Platz = '1' AND name = 'Herbstmeister';
"""
)
🎉 Richtiges Ergebnis für Aufgabe 3.1 🎉
┌──────────┬───────────┬───────┬───────────────┬────────────┬──────────┐
│ SEGELNR  │ WETTFAHRT │ platz │     Name      │   Datum    │   Zeit   │
│ varchar  │   int16   │ int64 │    varchar    │    date    │   time   │
├──────────┼───────────┼───────┼───────────────┼────────────┼──────────┤
│ GER 4995 │         2 │     1 │ Herbstmeister │ 2003-09-16 │ 14:00:00 │
└──────────┴───────────┴───────┴───────────────┴────────────┴──────────┘

Aufgabe 3.2*#

\(\pi_{Name}(Wettfahrt \bowtie_{FahrtNr=Wettfahrt}(Platzierung \bowtie(\pi_{SegelNr}(\sigma_{Baujahr<1980}(Teilnehmer)))))\)

con.sql(
    """

"""
)

Hide code cell content

#### Musterlösung

"""
Hinweis:
Der Ausdruck der Relationalen Alegebra ist (fast) identisch zu Aufgabe 4.3 aus Blatt 5.
Allerdings verwenden wir hier einen Natural Join anstelle eines Theta-Joins.
"""

con.sql(
    """
SELECT w.name 
FROM Teilnehmer t JOIN Platzierung p ON t.SegelNr = p.SegelNr 
                  JOIN Wettfahrt w ON p.wettfahrt = w.fahrtNr
WHERE baujahr < '1980';
"""
)
🎉 Richtiges Ergebnis für eine Aufgabe 🎉
┌──────────────────────────┐
│           Name           │
│         varchar          │
├──────────────────────────┤
│ Franz Huber Gedenk Preis │
└──────────────────────────┘
con.close()

Aufgabe 4*: SQL Injection#

Wieso sind SQL Injections relevant? Fragen wir xkcd:

Zuerst öffnen wir die die Uni Datenbank wieder.

con = duckdb.connect(database="../resources/06_sql_dql/uni_ue.duckdb", read_only=False)

Aufgabe 4.1*#

Gegeben sei die folgende Abfrage, um die Matrikelnummer eines:einer Studierenden mit mindestens 8 Semestern abzufragen.

Schreiben Sie einen Input, der mehr als einen Studierenden zurückgibt. Diskutieren Sie danach wie dies verhindert werden kann.

name = input("Gib einen Namen an: ")
query = (
    "SELECT s.name, s.matrnr FROM studenten s WHERE s.name LIKE '"
    + name
    + "' AND s.semester >= 8;"
)
print(query)

con.sql(query)
---------------------------------------------------------------------------
StdinNotImplementedError                  Traceback (most recent call last)
Cell In[70], line 1
----> 1 name = input("Gib einen Namen an: ")
      2 query = (
      3     "SELECT s.name, s.matrnr FROM studenten s WHERE s.name LIKE '"
      4     + name
      5     + "' AND s.semester >= 8;"
      6 )
      7 print(query)

File /usr/local/lib/python3.13/site-packages/ipykernel/kernelbase.py:1259, in Kernel.raw_input(self, prompt)
   1257 if not self._allow_stdin:
   1258     msg = "raw_input was called, but this frontend does not support input requests."
-> 1259     raise StdinNotImplementedError(msg)
   1260 return self._input_request(
   1261     str(prompt),
   1262     self._parent_ident["shell"],
   1263     self.get_parent("shell"),
   1264     password=False,
   1265 )

StdinNotImplementedError: raw_input was called, but this frontend does not support input requests.

Aufgabe 4.2*#

Schreiben Sie nun einen Input, der alle Studierenden zurückgibt.

name = input("Gib einen Namen an: ")

query = (
    "SELECT s.name, s.matrnr FROM studenten s WHERE s.name LIKE '"
    + name
    + "' AND s.semester >= 8;"
)
print(query)

con.sql(query)

Hide code cell content

#### Musterlösung

#### Lösungen

# Input: `%' AND TRUE=TRUE;--`

#### Mitigation

# Parametrisiertes statement
con.close()

Probleme bei der Nutzung von LLMs#

Wir würden Sie gerne dazu motivieren, die Vorleistung ohne die Nutzung von LLM’s zu lösen, um den größt möglichen Lerneffekt zu erzielen.

Bedenken Sie, dass Sie in der Klausur das Wissen ohne LLM’s nachweisen müssen !#

Es gibt neben dem Lerneffekt jedoch auch zusätzliche Schwierigkeiten, die mit der Nutzung von LLM’s einhergeht:

Ambiguität in der Spezifikation#

Beispiel: Generiere eine SQL-Query, die alle deutschen Filmregisseure ausgibt Problem: Je nach Auffassung kann die korrekte Query eine andere sein: z.B.

  • deutsche Staatsbürger die Regisseure sind

  • Regisseure, die deutschsprachige Filme drehen

  • Regisseure, die in Deutschland Filme produzieren

  • etc.

Beispiel: Generiere eine SQL-Query, die die beste Marathon-Läuferin ermittelt Problem: gut/beste ist nicht eindeutig definiert. Gemeint sein könnte

  • Marathon-Läuferin mit geringster Bestzeit

  • Marathon-Läuferin mit geringster Durchschnittszeit

  • mit höchster Anzahl an Wettbewerben

  • etc.

Bedenke:#

Selbst wenn Ihnen, die Spezifikation bereits klar geworden ist, kann das LLM weiterhin mit der genauen Spezifikation verwirrt sein.

Schema Linking#

Beispiel: Generiere eine SQL-Query, die alle deutschen Filme ausgibt Problem: Wissen über Abbildung der Daten ist nötig, um korrekte Query zu schreiben. In Spalte “Nation” könnte Deutschland abgebildet sein als Germany, Deutschland, GER, DE, etc.