{
 "nbformat_minor": 5,
 "nbformat": 4,
 "cells": [
  {
   "cell_type": "markdown",
   "id": "d3cd016c",
   "metadata": {},
   "source": [
    "# Lernwerkstatt 04 — Tabellen verbinden\n",
    "\n",
    "**Wirtschaftsinformatik · Brick-Flow AG · Wintersemester 2026/27**\n",
    "\n",
    "> **Leitfrage der Woche:** *Eine Kundin heiratet und zieht um — was muss geändert werden, und an wie vielen Stellen?*\n",
    "\n",
    "Heute lernt ihr\n",
    "\n",
    "1. eine **Funktion mit Schleife** zu schreiben (Wiederholung aus Woche 3),\n",
    "2. zwei Tabellen über einen gemeinsamen **Schlüssel** zu verbinden (`merge`),\n",
    "3. den Unterschied zwischen `inner` und `left` zu sehen.\n",
    "\n",
    "Alles ist kurz gehalten. Unter manchen Aufgaben könnt ihr eine **Kontrollzahl** aufklappen. **Fehlermeldungen sind Hinweise.**"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2698e8e3",
   "metadata": {},
   "source": [
    "## Aufwärmen — eine Funktion mit Schleife (ca. 10 Minuten)\n",
    "\n",
    "Eine Wiederholung aus Woche 3. Schreibt eine **Funktion** `zaehle_nach_grenze`, die zählt, wie viele Werte einer Liste **mindestens so groß wie eine Grenze** sind und wie viele **kleiner**. Sie gibt beide Zahlen als Tupel `(anzahl_gross, anzahl_klein)` zurück.\n",
    "\n",
    "Die erste Zeile zum Kopieren (zwei Eingaben: die Liste und die Grenze):\n",
    "\n",
    "```python\n",
    "def zaehle_nach_grenze(bestellwerte, grenze):\n",
    "```\n",
    "\n",
    "Ihr braucht zwei Zähler, eine `for`-Schleife, darin ein `if` mit `else`, und ein `return` ganz am Ende. Unter eurer Funktion steht schon ein kleiner Test.\n",
    "\n",
    "<details>\n",
    "<summary><b>Falls ihr hängt</b></summary>\n",
    "\n",
    "Beide Zähler stehen **vor** der Schleife und beginnen bei 0. In der Schleife prüft ihr bei jedem `wert` mit `if`, ob `wert >= grenze` ist: Im `if`-Zweig erhöht ihr `anzahl_gross`, im `else`-Zweig `anzahl_klein`. Das `return` steht **nach** der Schleife, so weit links wie das `for`.\n",
    "\n",
    "</details>\n",
    "\n",
    "<details>\n",
    "<summary><b>Kontrollzahl</b></summary>\n",
    "\n",
    "Mit Grenze 100: **(3, 3)**. Mit Grenze 150: **(2, 4)**.\n",
    "\n",
    "</details>"
   ]
  },
  {
   "cell_type": "code",
   "id": "e34b432d",
   "metadata": {},
   "source": [
    "bestellwerte = [45.5, 120.0, 99.9, 210.0, 80.0, 150.0]\n",
    "\n",
    "# Aufwärmaufgabe: hier die Funktion zaehle_nach_grenze schreiben\n",
    "\n",
    "\n",
    "\n",
    "# Test:\n",
    "print(zaehle_nach_grenze(bestellwerte, 100))\n",
    "print(zaehle_nach_grenze(bestellwerte, 150))\n"
   ],
   "execution_count": null,
   "outputs": []
  },
  {
   "cell_type": "markdown",
   "id": "f2abffdb",
   "metadata": {},
   "source": [
    "## Die Daten\n",
    "\n",
    "Wir brauchen zwei Tabellen: `kunden` (eine Zeile je Kundin) und `bestellungen` (eine Zeile je Bestellung). Die Spalte `kundennr` steht in **beiden**: Sie ist der **Schlüssel**, über den man die Tabellen verbindet. (`marketing` sind die Marketing-Kampagnen, die brauchen wir gleich für eine Aufgabe.)\n",
    "\n",
    "Legt die drei Dateien `brickflow_kunden_dubletten.csv`, `brickflow_bestellungen.csv` und `brickflow_marketing.csv` in **denselben Ordner wie dieses Notebook** (auf dem **HS-Bochum-Jupyter-Cluster** `jupyter.hs-bochum.de`: aus dem Kursordner kopieren oder hochladen)."
   ]
  },
  {
   "cell_type": "code",
   "id": "53dea41e",
   "metadata": {},
   "source": [
    "import pandas as pd\n",
    "\n",
    "kunden       = pd.read_csv(\"brickflow_kunden_dubletten.csv\")\n",
    "bestellungen = pd.read_csv(\"brickflow_bestellungen.csv\")\n",
    "marketing    = pd.read_csv(\"brickflow_marketing.csv\")\n",
    "\n",
    "print(\"kunden      :\", len(kunden), \"Zeilen\")\n",
    "print(\"bestellungen:\", len(bestellungen), \"Zeilen\")\n",
    "print(\"marketing   :\", len(marketing), \"Zeilen\")\n",
    "kunden.head()"
   ],
   "execution_count": null,
   "outputs": []
  },
  {
   "cell_type": "code",
   "id": "64705148",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "# Die ersten Zeilen: Welche Spalten hat die Tabelle?\n",
    "bestellungen.head()"
   ]
  },
  {
   "cell_type": "code",
   "id": "a8f4bdcd",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "marketing.head()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "417389ef",
   "metadata": {},
   "source": [
    "## Zwei Tabellen verbinden — gemeinsam\n",
    "\n",
    "Wir holen zu **jeder Bestellung** die Daten ihrer Kundin dazu. Das heißt **Merge**. Hier steht das vollständige Grundgerüst, das wir die ganze Woche benutzen:\n",
    "\n",
    "`bestellungen.merge(kunden, on=\"kundennr\", how=\"inner\")`\n",
    "\n",
    "- Links steht die Tabelle, mit der wir beginnen (`bestellungen`), rechts die, aus der Spalten **dazukommen** (`kunden`).\n",
    "- `on=\"kundennr\"` nennt den **Schlüssel**, der in beiden Tabellen vorkommt.\n",
    "- `how=\"inner\"` legt fest, **welche Zeilen im Ergebnis bleiben**: bei `inner` nur die, die auf **beiden** Seiten einen Partner haben. Eine Bestellung, deren `kundennr` in `kunden` nicht vorkommt, fällt heraus, und eine Kundin ohne Bestellung ebenfalls. (Lässt man `how` weg, gilt `inner` automatisch. Wir schreiben es trotzdem hin, damit man es sieht.)\n",
    "\n",
    "Jede Bestellung gehört zu genau einer Kundin. Das Ergebnis sollte also **genauso viele Zeilen** haben wie `bestellungen`. Probieren wir es:"
   ]
  },
  {
   "cell_type": "code",
   "id": "e2d586bc",
   "metadata": {},
   "source": [
    "zusammen_kaputt = bestellungen.merge(kunden, on=\"kundennr\", how=\"inner\")\n",
    "\n",
    "print(\"Bestellungen    :\", len(bestellungen))\n",
    "print(\"nach dem merge  :\", len(zusammen_kaputt))"
   ],
   "execution_count": null,
   "outputs": []
  },
  {
   "cell_type": "markdown",
   "id": "0ce57d44",
   "metadata": {},
   "source": [
    "### Mini-Aufgabe 1 — Euer erster Merge (`inner`)\n",
    "\n",
    "Jetzt seid ihr dran. Verbindet `bestellungen` (links) mit `marketing` (rechts) über die Spalte `code`, die in beiden Tabellen so heißt (`on=\"code\"`), mit `how=\"inner\"`. Gebt die Länge des Ergebnisses mit `len` aus.\n",
    "\n",
    "<details>\n",
    "<summary><b>Kontrollzahl</b></summary>\n",
    "\n",
    "**267** Zeilen.\n",
    "\n",
    "</details>"
   ]
  },
  {
   "cell_type": "code",
   "id": "e32b8939",
   "metadata": {},
   "source": [
    "# Mini-Aufgabe 1:\n",
    "\n",
    "\n"
   ],
   "execution_count": null,
   "outputs": []
  },
  {
   "cell_type": "markdown",
   "id": "a9a7412c",
   "metadata": {},
   "source": [
    "Zurück zu unserem ersten Merge (`bestellungen` mit `kunden`): Es sind **1.625** statt 1.616 Zeilen, neun zu viel. Irgendetwas stimmt mit dem Schlüssel nicht. Schauen wir, ob eine `kundennr` in `kunden` **mehrfach** vorkommt:"
   ]
  },
  {
   "cell_type": "code",
   "id": "ec1b038d",
   "metadata": {},
   "source": [
    "doppelt = kunden[\"kundennr\"].duplicated(keep=False)\n",
    "kunden[doppelt]"
   ],
   "execution_count": null,
   "outputs": []
  },
  {
   "cell_type": "markdown",
   "id": "d05a4e6d",
   "metadata": {},
   "source": [
    "Drei Kundinnen stehen **zweimal** in der Tabelle: dieselbe Person, einmal mit `Bergstraße`, einmal mit `Bergstr.`. Ein Schlüssel muss in seiner Tabelle **eindeutig** sein. Sonst entsteht beim Merge für jeden Treffer eine eigene Zeile. Wir behalten je Kundin **eine** Zeile (`drop_duplicates`) und verbinden neu:"
   ]
  },
  {
   "cell_type": "code",
   "id": "a7024568",
   "metadata": {},
   "source": [
    "kunden_sauber = kunden.drop_duplicates(subset=\"kundennr\")\n",
    "\n",
    "zusammen = bestellungen.merge(kunden_sauber, on=\"kundennr\", how=\"inner\")\n",
    "print(\"Bestellungen   :\", len(bestellungen))\n",
    "print(\"nach dem merge :\", len(zusammen))"
   ],
   "execution_count": null,
   "outputs": []
  },
  {
   "cell_type": "markdown",
   "id": "dce9d21a",
   "metadata": {},
   "source": [
    "Jetzt stimmt es: **1.616** Zeilen, eine je Bestellung. Ab hier arbeiten wir mit `kunden_sauber` und `zusammen`."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "36012138",
   "metadata": {},
   "source": [
    "## Der `left`-Merge\n",
    "\n",
    "Manche Kundinnen haben vielleicht noch **nie bestellt**. Bei `inner` fallen sie heraus. Mit `how=\"left\"` bleiben dagegen **alle Zeilen der linken Tabelle** erhalten, auch wenn rechts kein Partner gefunden wird. Die Felder von rechts bleiben dann leer. Wir verbinden `kunden_sauber` (diesmal **links**) einmal mit `inner` und einmal mit `left` und vergleichen die Ergebnisse. Der einzige Unterschied im Code ist `how`:"
   ]
  },
  {
   "cell_type": "code",
   "id": "6acbfa68",
   "metadata": {},
   "source": [
    "inner = kunden_sauber.merge(bestellungen, on=\"kundennr\", how=\"inner\")\n",
    "links = kunden_sauber.merge(bestellungen, on=\"kundennr\", how=\"left\")\n",
    "\n",
    "print(\"inner:\", len(inner), \"Zeilen\")\n",
    "print(\"left :\", len(links), \"Zeilen\")\n",
    "\n",
    "# Welche Zeilen hat left mehr? Die, bei denen keine Bestellung dazugekommen ist (bestellnr leer):\n",
    "links[links[\"bestellnr\"].isna()][[\"kundennr\", \"name\", \"bestellnr\"]]"
   ],
   "execution_count": null,
   "outputs": []
  },
  {
   "cell_type": "markdown",
   "id": "11cf0d8e",
   "metadata": {},
   "source": [
    "`left` hat **2 Zeilen mehr**: zwei Kundinnen, die noch nie bestellt haben. Ihre Bestellfelder sind leer.\n",
    "\n",
    "- `inner` behält nur Zeilen, die auf **beiden** Seiten einen Partner haben. Kundinnen ohne Bestellung fallen heraus.\n",
    "- `left` behält **alle** Zeilen der linken Tabelle, auch ohne Partner (die Felder von rechts bleiben dann leer).\n",
    "\n",
    "Welche Art richtig ist, hängt von der Frage ab: Zählt man Bestellungen, genügt `inner`. Will man alle Kundinnen sehen, braucht man `left`."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7fd311cb",
   "metadata": {},
   "source": [
    "### Mini-Aufgabe 2 (`left`)\n",
    "\n",
    "Wiederholt eure Mini-Aufgabe 1 (`bestellungen` mit `marketing` über `code`), diesmal mit `how=\"left\"`. Wie viele Zeilen hat das Ergebnis jetzt? Was sagt der Unterschied zu vorher?\n",
    "\n",
    "<details>\n",
    "<summary><b>Kontrollzahl</b></summary>\n",
    "\n",
    "**1616** Zeilen statt 267.\n",
    "\n",
    "</details>"
   ]
  },
  {
   "cell_type": "code",
   "id": "639971b8",
   "metadata": {},
   "source": [
    "# Mini-Aufgabe 2:\n",
    "\n",
    "\n"
   ],
   "execution_count": null,
   "outputs": []
  },
  {
   "cell_type": "markdown",
   "id": "479840a4",
   "metadata": {},
   "source": [
    "## Aufgaben\n",
    "\n",
    "In eurem Tempo. Tutor:innen helfen. Pflicht ist Aufgabe 1, die Sprinteraufgabe ★ ist für alle, die schneller fertig sind."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "42ed3391",
   "metadata": {},
   "source": [
    "**Aufgabe 1.** Welche **fünf Bestellungen** haben den höchsten Bestellwert? Verbindet `bestellungen` (links) mit `kunden_sauber` über `kundennr` und zeigt Bestellnummer, Bestellwert sowie Name und Ort der Kundin.\n",
    "\n",
    "Zum **Ordnen** gibt es `sort_values`: `tabelle.sort_values(\"spalte\", ascending=False)` stellt den **größten** Wert nach oben, `.head(5)` zeigt die ersten fünf Zeilen. Mit `[[\"a\", \"b\"]]` wählt ihr Spalten aus.\n",
    "\n",
    "<details>\n",
    "<summary><b>Kontrollzahl</b></summary>\n",
    "\n",
    "Ganz oben steht die Bestellung `8900` mit **5.000 €**, danach `8274` mit 3.628,57 €.\n",
    "\n",
    "</details>"
   ]
  },
  {
   "cell_type": "code",
   "id": "9124632d",
   "metadata": {},
   "source": [
    "# Aufgabe 1:\n",
    "\n",
    "\n"
   ],
   "execution_count": null,
   "outputs": []
  },
  {
   "cell_type": "markdown",
   "id": "462fc2b0",
   "metadata": {},
   "source": [
    "### Sprinteraufgabe\n",
    "\n",
    "Für alle, die schneller fertig sind.\n",
    "\n",
    "**Aufgabe 2 (★).** Wie viele Kundinnen haben noch **nie bestellt**? Ihr habt es oben schon gesehen. Zählt es mit Code: Verbindet `kunden_sauber` (links) mit `bestellungen` mit `how=\"left\"` und zählt die Zeilen, in denen `bestellnr` leer ist (`isna()`, das kennt ihr aus Woche 3).\n",
    "\n",
    "<details>\n",
    "<summary><b>Kontrollzahl</b></summary>\n",
    "\n",
    "**2** Kundinnen (Anna Weiß und Robert Böhm).\n",
    "\n",
    "</details>"
   ]
  },
  {
   "cell_type": "code",
   "id": "19431a92",
   "metadata": {},
   "source": [
    "# Aufgabe 2:\n",
    "\n",
    "\n",
    ""
   ],
   "execution_count": null,
   "outputs": []
  },
  {
   "cell_type": "markdown",
   "id": "ec37a84e",
   "metadata": {},
   "source": [
    "## Geschafft\n",
    "\n",
    "Ihr habt heute\n",
    "\n",
    "- eine Funktion mit Schleife und `if`/`else` geschrieben,\n",
    "- zwei Tabellen über einen **Schlüssel** verbunden (`merge`) und gesehen, was ein doppelter Schlüssel anrichtet,\n",
    "- `inner` und `left` verglichen und gesehen, welche Zeilen dabei verloren gehen.\n",
    "\n",
    "**Neu in dieser Woche:**\n",
    "\n",
    "| Befehl | Was er tut |\n",
    "|---|---|\n",
    "| `a.merge(b, on=\"schluessel\")` | verbindet zwei Tabellen über eine gemeinsame Spalte |\n",
    "| `how=\"inner\"`, `how=\"left\"` | legt fest, welche Zeilen ohne Partner bleiben |\n",
    "| `drop_duplicates(subset=\"spalte\")` | behält je Wert der Spalte nur eine Zeile |\n",
    "| `sort_values(\"spalte\", ascending=False)` | ordnet eine Tabelle, der größte Wert zuerst |\n",
    "\n",
    "**Nächste Woche:** Wie liest man eine Datenbank? Wir lesen das Modell der großen `brickflow.db`, laden Daten mit einer kurzen SQL-Abfrage und verbinden sie mit `merge`."
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "name": "python3",
   "language": "python"
  },
  "language_info": {
   "name": "python"
  }
 }
}