add synced notes on IT know how
This commit is contained in:
@@ -0,0 +1,497 @@
|
||||
#datenbank
|
||||
|
||||
➡️ [[#Zusammenfassung]]
|
||||
|
||||
---
|
||||
## 1. Grundidee: Was ist eine Identity Column?
|
||||
|
||||
Stell dir eine Tabelle „Kunden“ vor. Jeder Kunde soll eine eindeutige Nummer bekommen:
|
||||
|
||||
- Kunde 1
|
||||
- Kunde 2
|
||||
- Kunde 3
|
||||
- …
|
||||
|
||||
Du willst diese Nummern **nicht selbst vergeben**, sondern die Datenbank soll das **automatisch** machen, wenn du einen neuen Datensatz einfügst.
|
||||
|
||||
Genau das ist eine **Identity Column**:
|
||||
|
||||
> Eine Identity Column ist eine Spalte, deren Wert von der Datenbank automatisch erzeugt wird, meist als laufende Nummer (1, 2, 3, …). Sie wird oft als Primärschlüssel benutzt.
|
||||
|
||||
Typische Eigenschaften:
|
||||
|
||||
- numerisch (z. B. `INT`, `BIGINT`)
|
||||
- beim `INSERT` wird kein Wert angegeben – die DB füllt ihn selbst
|
||||
- der Wert ist pro Tabelle eindeutig
|
||||
- er steigt (meist) monoton an (1,2,3,…), kann aber Lücken haben
|
||||
|
||||
---
|
||||
|
||||
## 2. Ein einfaches Beispiel
|
||||
|
||||
Beispiel in **SQL Server**:
|
||||
|
||||
```sql
|
||||
CREATE TABLE Kunde (
|
||||
KundeID INT IDENTITY(1,1) PRIMARY KEY, -- Start bei 1, Inkrement 1
|
||||
Name NVARCHAR(100) NOT NULL,
|
||||
Email NVARCHAR(200) NOT NULL
|
||||
);
|
||||
|
||||
-- Einfügen ohne KundeID anzugeben:
|
||||
INSERT INTO Kunde (Name, Email)
|
||||
VALUES ('Anna Beispiel', 'anna@example.com'),
|
||||
('Max Muster', 'max@muster.de');
|
||||
|
||||
-- Auslesen:
|
||||
SELECT * FROM Kunde;
|
||||
```
|
||||
|
||||
Ergebnis (vereinfacht):
|
||||
|
||||
```text
|
||||
KundeID | Name | Email
|
||||
--------+----------------+-------------------
|
||||
1 | Anna Beispiel | anna@example.com
|
||||
2 | Max Muster | max@muster.de
|
||||
```
|
||||
|
||||
Du hast `KundeID` nie selbst gesetzt – die Datenbank hat sie erzeugt.
|
||||
|
||||
---
|
||||
|
||||
## 3. Abgrenzung zu verwandten Begriffen
|
||||
|
||||
### 3.1 Identity Column vs. Primärschlüssel
|
||||
|
||||
- **Primärschlüssel (Primary Key)**:
|
||||
Konzept: eine oder mehrere Spalten, die **eindeutig** einen Datensatz identifizieren.
|
||||
- **Identity Column**:
|
||||
Mechanismus: eine Spalte, deren Werte automatisch generiert werden.
|
||||
|
||||
Eine Identity Column **kann** der Primärschlüssel sein, muss aber nicht. Man kann auch:
|
||||
|
||||
- Identity Column + separater fachlicher Schlüssel (z. B. Kundennummer im ERP)
|
||||
- Primärschlüssel auf einer anderen Spalte (z. B. E-Mail) ohne Identity Column
|
||||
|
||||
In der Praxis: Sehr häufig ist die Identity Column = Primärschlüssel.
|
||||
|
||||
---
|
||||
|
||||
### 3.2 Identity Column vs. Auto-Increment
|
||||
|
||||
Viele Systeme benutzen unterschiedliche Begriffe:
|
||||
|
||||
- SQL Server: `IDENTITY`
|
||||
- PostgreSQL: `GENERATED AS IDENTITY` (früher `SERIAL`)
|
||||
- MySQL: `AUTO_INCREMENT`
|
||||
- Oracle: Identity-Spalten oder separate `SEQUENCE`
|
||||
|
||||
**Inhaltlich** ist „Identity Column“ oft gleichbedeutend mit „Auto-Increment-Spalte“: eine automatisch hochzählende Spalte.
|
||||
|
||||
PostgreSQL-Beispiel:
|
||||
|
||||
```sql
|
||||
CREATE TABLE kunde (
|
||||
kunde_id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
email TEXT NOT NULL
|
||||
);
|
||||
```
|
||||
|
||||
MySQL-Beispiel:
|
||||
|
||||
```sql
|
||||
CREATE TABLE kunde (
|
||||
kunde_id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||
name VARCHAR(100) NOT NULL,
|
||||
email VARCHAR(200) NOT NULL
|
||||
);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3.3 Identity Column vs. Sequence
|
||||
|
||||
**Sequence (Sequenz)** ist ein **eigenständiges Datenbankobjekt**, das Zahlen generiert, etwa so:
|
||||
|
||||
```sql
|
||||
CREATE SEQUENCE kunde_seq START WITH 1 INCREMENT BY 1;
|
||||
|
||||
SELECT nextval('kunde_seq'); -- gibt 1 zurück
|
||||
SELECT nextval('kunde_seq'); -- gibt 2 zurück
|
||||
```
|
||||
|
||||
Eine Identity Column ist oft intern an eine Sequence gekoppelt.
|
||||
Unterschied:
|
||||
|
||||
- Sequence: unabhängig von Tabellen, du kannst sie überall verwenden
|
||||
- Identity Column: an genau eine Spalte gebunden, automatischer Einsatz bei `INSERT`
|
||||
|
||||
In einigen DB-Systemen (z. B. Oracle, PostgreSQL) sind Identity Columns technisch eine bequeme Hülle um eine Sequence.
|
||||
|
||||
---
|
||||
|
||||
### 3.4 Identity Column vs. natürlicher/fachlicher Schlüssel
|
||||
|
||||
- **Natürlicher/fachlicher Schlüssel**: basiert auf echten Geschäfts-Daten
|
||||
Beispiele:
|
||||
- E-Mail-Adresse als eindeutiger Schlüssel für Benutzer
|
||||
- ISBN für Bücher
|
||||
- Personalnummer aus dem HR-System
|
||||
|
||||
- **Surrogate Key** (hier passt die Identity Column): technischer, künstlicher Schlüssel, ohne fachliche Bedeutung.
|
||||
|
||||
Identity Columns sind **Surrogate Keys**:
|
||||
- `KundeID = 42` sagt fachlich nichts über den Kunden aus
|
||||
- sie existiert nur, um Zeilen eindeutig zu identifizieren
|
||||
|
||||
In vielen Projekten arbeitet man mit:
|
||||
|
||||
- Identity Column als Primärschlüssel
|
||||
- dazu Unique-Constraints auf fachlichen Spalten (z. B. `Email UNIQUE`)
|
||||
|
||||
---
|
||||
|
||||
### 3.5 Identity Column vs. UUID / GUID
|
||||
|
||||
Statt einer Identity Column (INT) kann man auch **UUIDs** (z. B. `UUID`, `uniqueidentifier`) als Primärschlüssel verwenden.
|
||||
|
||||
Unterschiede:
|
||||
|
||||
- **Identity (INT/BIGINT)**:
|
||||
- kleiner, effizienter Index
|
||||
- gut lesbar (`KundeID = 123`)
|
||||
- nicht global eindeutig über mehrere Systeme, nur innerhalb der Tabelle
|
||||
- lässt leicht Rückschlüsse auf Anzahl/Abfolge (z. B. „wir sind bei Kunde 10.000“)
|
||||
|
||||
- **UUID**:
|
||||
- sehr große, „zufällig“ wirkende Zeichenfolge
|
||||
- global eindeutig (sehr hoch wahrscheinlich)
|
||||
- schwerer zu merken/lesen
|
||||
- Indexe sind meist größer, Einfügen kann teurer sein
|
||||
|
||||
Z. B. in PostgreSQL:
|
||||
|
||||
```sql
|
||||
CREATE TABLE kunde (
|
||||
kunde_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
name TEXT NOT NULL
|
||||
);
|
||||
```
|
||||
|
||||
Identity Column und UUID sind also alternative Strategien zur ID-Erzeugung.
|
||||
|
||||
---
|
||||
|
||||
## 4. Welche Probleme löst eine Identity Column?
|
||||
|
||||
### 4.1 Eindeutige Identifikation von Datensätzen
|
||||
|
||||
Jede Zeile bekommt automatisch eine eindeutige ID:
|
||||
|
||||
- kein Risiko, dass zwei Datensätze „versehentlich“ dieselbe ID haben
|
||||
- Voraussetzung für saubere Joins, Fremdschlüssel, Referenzen
|
||||
|
||||
Beispiel: Fremdschlüssel von Auftrag auf Kunde:
|
||||
|
||||
```sql
|
||||
CREATE TABLE Kunde (
|
||||
KundeID INT IDENTITY(1,1) PRIMARY KEY,
|
||||
Name NVARCHAR(100) NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE Auftrag (
|
||||
AuftragID INT IDENTITY(1,1) PRIMARY KEY,
|
||||
KundeID INT NOT NULL,
|
||||
Betrag DECIMAL(10,2) NOT NULL,
|
||||
FOREIGN KEY (KundeID) REFERENCES Kunde(KundeID)
|
||||
);
|
||||
```
|
||||
|
||||
Beim Anlegen eines Auftrags verweist `Auftrag.KundeID` auf `Kunde.KundeID`.
|
||||
|
||||
---
|
||||
|
||||
### 4.2 Vereinfachung beim Einfügen von Daten
|
||||
|
||||
Ohne Identity Column müsstest du bei jedem `INSERT` einen neuen eindeutigen Wert berechnen und eintragen – in Mehrbenutzer-Umgebungen ist das fehleranfällig.
|
||||
|
||||
Mit Identity Column:
|
||||
|
||||
```sql
|
||||
INSERT INTO Kunde (Name) VALUES ('Lisa Test'); -- ID macht die DB
|
||||
```
|
||||
|
||||
Du musst dir keine Gedanken machen über:
|
||||
|
||||
- die nächste freie Nummer
|
||||
- Race Conditions (zwei Nutzer generieren zufällig dieselbe ID)
|
||||
- Sperren von Tabellen, etc.
|
||||
|
||||
---
|
||||
|
||||
### 4.3 Unterstützung von Mehrbenutzerbetrieb
|
||||
|
||||
In einer Datenbank arbeiten meist viele Nutzer oder Prozesse parallel. Identity Columns sind so implementiert, dass:
|
||||
|
||||
- gleichzeitig eingefügte Zeilen **ohne Kollision** IDs bekommen
|
||||
- du dich nicht um Synchronisation kümmern musst
|
||||
|
||||
Die Datenbank regelt intern:
|
||||
|
||||
- Sperren der Sequenz
|
||||
- Transaktionssicherheit
|
||||
|
||||
---
|
||||
|
||||
### 4.4 Gute Performance und einfache Indizierung
|
||||
|
||||
Numerische Identity-Spalten:
|
||||
|
||||
- sind kompakt (z. B. 4 oder 8 Byte)
|
||||
- lassen sich effizient indexieren
|
||||
- wachsen meist monoton -> B-Tree-Indizes funktionieren sehr performant
|
||||
|
||||
Im Vergleich:
|
||||
- komplexe Primärschlüssel aus mehreren Textspalten sind größer, langsamer
|
||||
- Identity-Spalte als „technischer Schlüssel“ macht viele Operationen schneller
|
||||
|
||||
---
|
||||
|
||||
## 5. Herausforderungen und typische Fallstricke
|
||||
|
||||
### 5.1 Lücken in der Nummernfolge
|
||||
|
||||
Anfänger erwarten oft: „Wenn ich einen Datensatz lösche oder eine Transaktion zurückrolle, füllt die DB die Nummer wieder auf.“
|
||||
|
||||
Das **passiert nicht** (und sollte auch nicht passieren).
|
||||
|
||||
Gründe für Lücken:
|
||||
|
||||
- Transaktion wird zurückgerollt nach dem Erzeugen einer ID
|
||||
- Datensatz mit ID 10 wird gelöscht
|
||||
- Parallel eingefügte Datensätze
|
||||
|
||||
Beispiel:
|
||||
- du fügst einen Datensatz ein -> bekommt ID 10
|
||||
- im gleichen Moment ein zweiter -> ID 11
|
||||
- deine Transaktion schlägt fehl -> Datensatz 10 existiert nicht
|
||||
- Datensatz 11 bleibt erhalten
|
||||
|
||||
Ergebnis: Es gibt jetzt mitunter keinen Datensatz mit ID 10. Das ist normal.
|
||||
|
||||
**Wichtige Praxisregel**:
|
||||
Eine Identity Column ist eine **technische ID**, keine „lückenlose Rechnungsnummer“, keine „fortlaufende Kundennummer“ im rechtlichen Sinn.
|
||||
|
||||
Wenn du wirklich lückenlose Nummern brauchst (z. B. für Rechnungen), wird das typischerweise anders gelöst (spezialisierte Logik, Sperren, eigene Tabellen).
|
||||
|
||||
---
|
||||
|
||||
### 5.2 „ID = Reihenfolge“ ist gefährlich
|
||||
|
||||
Menschen neigen dazu zu denken:
|
||||
|
||||
> „ID 100 wurde nach ID 99 erstellt.“
|
||||
|
||||
In der Praxis ist das oft, aber **nicht garantiert**:
|
||||
|
||||
- Backups/Restores
|
||||
- Replikation
|
||||
- Imports aus anderen Systemen
|
||||
- unterschiedliche Identity-Strategien
|
||||
|
||||
Besser: Wenn du die zeitliche Reihenfolge brauchst, verwende eine **Zeitstempel-Spalte**:
|
||||
|
||||
```sql
|
||||
CREATE TABLE Kunde (
|
||||
KundeID INT IDENTITY(1,1) PRIMARY KEY,
|
||||
Name NVARCHAR(100),
|
||||
CreatedAt DATETIME2 NOT NULL DEFAULT SYSDATETIME()
|
||||
);
|
||||
```
|
||||
|
||||
Dann sortierst du nach `CreatedAt`, nicht nach `KundeID`.
|
||||
|
||||
---
|
||||
|
||||
### 5.3 Limits des Datentyps (Überlauf)
|
||||
|
||||
Wenn du `INT` (32-Bit) verwendest, ist irgendwann Schluss (ca. 2,1 Milliarden positive Werte). Bei sehr großen Tabellen oder sehr intensiver Nutzung kann das relevant werden.
|
||||
|
||||
Praktischer Tipp:
|
||||
|
||||
- lieber direkt `BIGINT` für Identity-Spalten verwenden – die Grenze ist so hoch, dass du in normalen Anwendungen nicht anstößt.
|
||||
|
||||
Beispiel:
|
||||
|
||||
```sql
|
||||
CREATE TABLE Bestellung (
|
||||
BestellungID BIGINT IDENTITY(1,1) PRIMARY KEY,
|
||||
...
|
||||
);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 5.4 Datenmigrationen, Importe, Reseeding
|
||||
|
||||
Problemfälle:
|
||||
|
||||
- du willst Daten von einem System ins andere migrieren
|
||||
- dort gibt es schon Datensätze mit Identity-Werten
|
||||
- beim Import dürfen keine IDs kollidieren
|
||||
|
||||
Strategien:
|
||||
|
||||
1. **Identity-Werte mit übernehmen**
|
||||
In vielen DB-Systemen kann man temporär eigene Werte setzen:
|
||||
|
||||
SQL Server:
|
||||
```sql
|
||||
SET IDENTITY_INSERT Kunde ON;
|
||||
|
||||
INSERT INTO Kunde (KundeID, Name, Email)
|
||||
VALUES (1001, 'Imported User', 'import@example.com');
|
||||
|
||||
SET IDENTITY_INSERT Kunde OFF;
|
||||
```
|
||||
|
||||
2. **Nach dem Import die Identity „neu starten“ (reseeden)**
|
||||
SQL Server:
|
||||
```sql
|
||||
DBCC CHECKIDENT ('Kunde', RESEED, 2000); -- nächste ID = 2001
|
||||
```
|
||||
|
||||
Diese Themen werden wichtig, wenn du Daten zwischen Systemen hin- und herschiebst.
|
||||
|
||||
---
|
||||
|
||||
### 5.5 Verteilte Systeme / Sharding
|
||||
|
||||
In modernen Architekturen gibt es manchmal:
|
||||
|
||||
- mehrere Datenbankserver (Shards)
|
||||
- die später zusammengeführt werden sollen
|
||||
|
||||
Wenn jede Tabelle eine Identity-Spalte bei 1 beginnen lässt, kann es bei Merge-Vorgängen Kollisionen geben (z. B. `KundeID = 100` existiert auf zwei Servern mit unterschiedlichen Kunden).
|
||||
|
||||
Lösungen:
|
||||
|
||||
- pro Server unterschiedliche Startwerte und Inkremente:
|
||||
- Server A: `IDENTITY(1, 3)` → 1,4,7,…
|
||||
- Server B: `IDENTITY(2, 3)` → 2,5,8,…
|
||||
- Server C: `IDENTITY(3, 3)` → 3,6,9,…
|
||||
- oder Nutzung von UUIDs statt Identity-Spalten
|
||||
|
||||
---
|
||||
|
||||
### 5.6 Informationsleck (Datenschutz, Sicherheit)
|
||||
|
||||
Außen sichtbare IDs (z. B. in URLs) können Informationen verraten:
|
||||
|
||||
- wenn dein Kunde eine URL wie `/bestellung/1000` sieht, kann er ahnen, dass es ~1000 Bestellungen gibt
|
||||
- er kann versuchen, `/bestellung/999` aufzurufen (ID-Raten)
|
||||
|
||||
Das ist kein Problem der Identity-Spalte an sich, sondern der Entscheidung, **diese ID außerhalb der Anwendung sichtbar** zu machen.
|
||||
|
||||
Lösungen:
|
||||
|
||||
- Zugriffskontrollen („zeige nur eigene Bestellungen“)
|
||||
- andere, „nicht-erratbare“ IDs für externe Darstellung (z. B. UUIDs, Hashes)
|
||||
|
||||
---
|
||||
|
||||
### 5.7 Portabilität zwischen Datenbanksystemen
|
||||
|
||||
Jede Datenbank hat leicht andere Syntax:
|
||||
|
||||
- SQL Server: `INT IDENTITY(1,1)`
|
||||
- PostgreSQL: `GENERATED ALWAYS AS IDENTITY`
|
||||
- MySQL: `AUTO_INCREMENT`
|
||||
- Oracle: `GENERATED BY DEFAULT AS IDENTITY` oder Sequences
|
||||
|
||||
Wenn du Wert auf **Portabilität** legst (dass dein Schema in mehreren DB-Systemen läuft), musst du darauf achten:
|
||||
|
||||
- eher Standard-SQL (`GENERATED [ALWAYS|BY DEFAULT] AS IDENTITY`)
|
||||
- oder ID-Generierung in der Anwendung selbst
|
||||
|
||||
---
|
||||
|
||||
## 6. Praxisnahe Gesamtbeispiele
|
||||
|
||||
### 6.1 Einfache Kunden- und Bestellverwaltung (SQL Server-Variante)
|
||||
|
||||
```sql
|
||||
CREATE TABLE Kunde (
|
||||
KundeID INT IDENTITY(1,1) PRIMARY KEY,
|
||||
Name NVARCHAR(100) NOT NULL,
|
||||
Email NVARCHAR(200) NOT NULL UNIQUE,
|
||||
CreatedAt DATETIME2 NOT NULL DEFAULT SYSDATETIME()
|
||||
);
|
||||
|
||||
CREATE TABLE Bestellung (
|
||||
BestellungID INT IDENTITY(1,1) PRIMARY KEY,
|
||||
KundeID INT NOT NULL,
|
||||
Datum DATETIME2 NOT NULL DEFAULT SYSDATETIME(),
|
||||
Betrag DECIMAL(10, 2) NOT NULL,
|
||||
FOREIGN KEY (KundeID) REFERENCES Kunde(KundeID)
|
||||
);
|
||||
|
||||
-- Neuen Kunden anlegen:
|
||||
INSERT INTO Kunde (Name, Email)
|
||||
VALUES ('Anna Beispiel', 'anna@example.com');
|
||||
|
||||
-- Die ID des eben eingefügten Kunden holen:
|
||||
SELECT SCOPE_IDENTITY() AS NeueKundeID;
|
||||
```
|
||||
|
||||
`SCOPE_IDENTITY()` gibt dir in SQL Server die zuletzt erzeugte Identity in der aktuellen Session und dem aktuellen Scope. Damit kannst du *direkt danach* eine Bestellung für genau diesen Kunden anlegen.
|
||||
|
||||
---
|
||||
|
||||
### 6.2 Beispiel in PostgreSQL (moderner Standard)
|
||||
|
||||
```sql
|
||||
CREATE TABLE kunde (
|
||||
kunde_id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
email TEXT NOT NULL UNIQUE,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE TABLE bestellung (
|
||||
bestellung_id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
||||
kunde_id BIGINT NOT NULL REFERENCES kunde(kunde_id),
|
||||
datum TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
betrag NUMERIC(10,2) NOT NULL
|
||||
);
|
||||
|
||||
-- Einfügen:
|
||||
INSERT INTO kunde (name, email)
|
||||
VALUES ('Max Muster', 'max@muster.de')
|
||||
RETURNING kunde_id; -- PostgreSQL-spezifisch, um die ID direkt zu bekommen
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Zusammenfassung
|
||||
|
||||
- Eine **Identity Column** ist eine Spalte, deren Wert die Datenbank automatisch erzeugt (typisch: laufende Nummer).
|
||||
- Sie wird häufig als **Primärschlüssel** verwendet, ist aber **nicht dasselbe** wie der Begriff „Primärschlüssel“.
|
||||
- Sie löst v. a. diese Probleme:
|
||||
- eindeutige Identifikation von Zeilen
|
||||
- einfache Inserts, ohne selbst IDs zu erzeugen
|
||||
- sichere Nutzung in Mehrbenutzer-Umgebungen
|
||||
- gute Performance bei Indexen und Joins
|
||||
- Sie ist verwandt mit:
|
||||
- Auto-Increment-Spalten (`AUTO_INCREMENT`, `SERIAL`)
|
||||
- Sequences (`SEQUENCE`)
|
||||
- Surrogate Keys im Gegensatz zu natürlichen Schlüsseln
|
||||
- Alternativen wie UUID/GUID
|
||||
- Typische Herausforderungen:
|
||||
- Lücken in der Nummernfolge (normal und erwünscht)
|
||||
- ID ist nicht zuverlässig die zeitliche Reihenfolge
|
||||
- Datenmigration, Reseeding, Sharding
|
||||
- potenzielles Informationsleck, wenn IDs öffentlich sichtbar sind
|
||||
- Unterschiede in der Implementierung zwischen Datenbanksystemen
|
||||
+475
@@ -0,0 +1,475 @@
|
||||
## 1. Grundidee: Was ist ein ORM?
|
||||
|
||||
ORM steht für **Object-Relational Mapping**.
|
||||
Im Kontext von Python und Datenbankmodellen bedeutet das:
|
||||
|
||||
> Ein ORM ist eine Bibliothek, die es dir erlaubt, mit einer relationalen Datenbank (z. B. PostgreSQL, MySQL, SQLite) zu arbeiten, indem du **Python-Klassen und -Objekte** verwendest statt SQL-Strings.
|
||||
|
||||
Anstatt also SQL wie:
|
||||
|
||||
```sql
|
||||
SELECT * FROM konto WHERE id = 1;
|
||||
```
|
||||
|
||||
zu schreiben, machst du in Python z. B.:
|
||||
|
||||
```python
|
||||
konto = session.get(Konto, 1)
|
||||
```
|
||||
|
||||
Das ORM übersetzt deine Python-Anweisung im Hintergrund in SQL, führt sie aus und gibt dir Python-Objekte zurück.
|
||||
|
||||
---
|
||||
|
||||
## 2. Wichtige Begriffe und Bausteine (intuitiv erklärt)
|
||||
|
||||
Nehmen wir ein vereinfachtes Bank-Beispiel:
|
||||
|
||||
- **Kunde** (Customer)
|
||||
- **Konto** (Account)
|
||||
- **Transaktion** (Transaction)
|
||||
|
||||
In einer relationalen Datenbank wären das Tabellen:
|
||||
|
||||
- `kunde`
|
||||
- `konto`
|
||||
- `transaktion`
|
||||
|
||||
Mit einem ORM legst du dafür **Modelle** als Python-Klassen an:
|
||||
|
||||
```python
|
||||
class Kunde:
|
||||
...
|
||||
class Konto:
|
||||
...
|
||||
class Transaktion:
|
||||
...
|
||||
```
|
||||
|
||||
Die wichtigsten Bausteine:
|
||||
|
||||
- **Modell / Entity**:
|
||||
Eine Klasse, die eine Tabelle repräsentiert (z. B. `Konto` → Tabelle `konto`).
|
||||
- **Felder / Spalten**:
|
||||
Attribute der Klasse (z. B. `saldo`, `kunde_id`).
|
||||
- **Beziehungen**:
|
||||
- „Ein Kunde hat viele Konten“ (One-to-Many)
|
||||
- „Ein Konto gehört zu genau einem Kunden“ (Many-to-One)
|
||||
- **Session / EntityManager**:
|
||||
Ein Objekt, über das du mit der Datenbank sprichst (z. B. Datensätze abfragen, speichern, löschen).
|
||||
|
||||
---
|
||||
|
||||
## 3. Abgrenzung zu verwandten Begriffen
|
||||
|
||||
### 3.1 ORM vs. direkte SQL-Nutzung
|
||||
|
||||
**Direkte SQL-Nutzung:**
|
||||
|
||||
- Du schreibst selbst SQL-Statements.
|
||||
- Du verwendest z. B. `psycopg2` (PostgreSQL) oder `sqlite3` (Standard in Python).
|
||||
- Du bekommst Resultate als Tupel / Dictionaries zurück und baust selbst deine Objekte.
|
||||
|
||||
**Mit ORM:**
|
||||
|
||||
- Du arbeitest mit Python-Klassen und -Objekten.
|
||||
- Das ORM generiert und führt SQL für dich aus.
|
||||
- Du bekommst direkt Instanzen deiner Klassen.
|
||||
|
||||
```python
|
||||
# Direkte SQL-Nutzung
|
||||
cursor.execute("SELECT id, name FROM kunde WHERE id = %s", (1,))
|
||||
row = cursor.fetchone()
|
||||
kunde = {"id": row[0], "name": row[1]}
|
||||
|
||||
# ORM
|
||||
kunde = session.get(Kunde, 1)
|
||||
print(kunde.name)
|
||||
```
|
||||
|
||||
ORM nimmt dir also den „manuellen“ Teil des Mappings ab.
|
||||
|
||||
---
|
||||
|
||||
### 3.2 ORM vs. Datenbankmodellierung (ER-Modell)
|
||||
|
||||
- **Datenbankmodellierung** (ER-Diagramme, Normalisierung usw.) ist der Schritt, in dem du **konzipierst**, wie deine Daten strukturiert sind.
|
||||
- **ORM** ist ein **Werkzeug**, um mit dieser Struktur in Python zu arbeiten.
|
||||
|
||||
Du kannst ein gutes Datenbankmodell haben – **mit oder ohne** ORM.
|
||||
ORM ersetzt nicht das Nachdenken über ein sinnvolles Datenmodell.
|
||||
|
||||
---
|
||||
|
||||
### 3.3 ORM vs. Migrations-Tools
|
||||
|
||||
- **Migrations-Tools** (z. B. Alembic für SQLAlchemy, Django-Migrations) verwalten die **Versionierung und Änderungen** am Schema (z. B. neue Spalte, geänderte Spalte).
|
||||
- Ein **ORM** arbeitet primär zur **Laufzeit** mit Daten; viele ORMs bringen allerdings Tools mit, um aus den Modellen Migrationen zu erzeugen.
|
||||
|
||||
---
|
||||
|
||||
### 3.4 ORM vs. Query-Builder
|
||||
|
||||
- **Query-Builder**: Bibliothek, die das Schreiben von SQL erleichtert, aber nicht unbedingt Objekte modelliert (z. B. SQLAlchemy Core).
|
||||
- **ORM**: baut auf einem Query-Builder auf und bringt zusätzlich:
|
||||
- Klassen→Tabellen-Mapping
|
||||
- Objekte→Zeilen-Mapping
|
||||
- Beziehungen als Attribute
|
||||
|
||||
---
|
||||
|
||||
### 3.5 ORM vs. [[Pydantic]] / Dataclasses
|
||||
|
||||
- **[[Pydantic]] / `dataclasses`**: Modellieren **In-Memory-Daten** (z. B. Input aus einer API), inkl. Validation und Typen.
|
||||
- **ORM**: Modelliert **persistente Daten** in einer relationalen Datenbank.
|
||||
|
||||
Man kombiniert das oft:
|
||||
|
||||
- [[Pydantic]]-Modelle für API-Ein-/Ausgaben
|
||||
- ORM-Modelle für Speicherung in der Datenbank
|
||||
|
||||
---
|
||||
|
||||
## 4. Welche Probleme löst ein ORM?
|
||||
|
||||
### 4.1 „Impedance Mismatch“: Objekte vs. Tabellen
|
||||
|
||||
Python arbeitet mit **Objekten**:
|
||||
|
||||
```python
|
||||
kunde.name
|
||||
konto.saldo
|
||||
konto.kunde.name
|
||||
```
|
||||
|
||||
Datenbanken arbeiten mit **Tabellen**, **Zeilen** und **Fremdschlüsseln**.
|
||||
ORMs „übersetzen“ zwischen diesen Welten.
|
||||
|
||||
Beispiel:
|
||||
|
||||
- `konto.kunde` ist in Python einfach ein Attribut.
|
||||
- Intern bedeutet das: Joins über `konto.kunde_id = kunde.id`.
|
||||
|
||||
---
|
||||
|
||||
### 4.2 Weniger Boilerplate, mehr Fokus auf Fachlogik
|
||||
|
||||
Ohne ORM schreibst du viel repetiven Code:
|
||||
|
||||
- SQL-Strings
|
||||
- Parameter-Bindung
|
||||
- Zeilen in Python-Objekte umwandeln
|
||||
|
||||
Mit ORM schreibst du einmal deine Modelle und konzentrierst dich dann auf:
|
||||
|
||||
- „Neue Transaktion buchen“
|
||||
- „Saldo prüfen“
|
||||
- „Kontoauszug generieren“
|
||||
|
||||
Anstatt: „Wie formuliere ich nochmal den SQL-Join...“.
|
||||
|
||||
---
|
||||
|
||||
### 4.3 Typisierung, Autocomplete, Konsistenz
|
||||
|
||||
Durch Python-Klassen hast du:
|
||||
|
||||
- **Typhinweise** (z. B. `saldo: float`)
|
||||
- Unterstützung durch IDE (Autocomplete, Refactoring)
|
||||
- Klar definierte Beziehungen (z. B. `konto.kunde`)
|
||||
|
||||
---
|
||||
|
||||
### 4.4 Datenbank-Agnostik
|
||||
|
||||
Viele ORMs unterstützen mehrere Datenbanken:
|
||||
|
||||
- Du kannst z. B. in Tests SQLite verwenden,
|
||||
- in Produktion PostgreSQL,
|
||||
- ohne deinen ganzen Code umzuschreiben (meist nur Konfiguration).
|
||||
|
||||
---
|
||||
|
||||
### 4.5 Testbarkeit
|
||||
|
||||
Du kannst:
|
||||
|
||||
- einfacher Unit-Tests schreiben, indem du In-Memory-SQLite nutzt,
|
||||
- oder sogar nur mit „Fake-Repositories“ arbeitest, die sich wie das ORM verhalten.
|
||||
|
||||
---
|
||||
|
||||
## 5. Welche Herausforderungen bringen ORMs mit sich?
|
||||
|
||||
### 5.1 Performance-Fallen („N+1-Problem“, zu viele Queries)
|
||||
|
||||
Beispiel: Du lädst 100 Konten und für jedes Konto den zugehörigen Kunden:
|
||||
|
||||
Naiv:
|
||||
|
||||
```python
|
||||
konten = session.query(Konto).all()
|
||||
for konto in konten:
|
||||
print(konto.kunde.name)
|
||||
```
|
||||
|
||||
Kann bedeuten:
|
||||
|
||||
- 1 Query für alle Konten
|
||||
- + 100 Queries für jeden einzelnen Kunden
|
||||
|
||||
→ Insgesamt 101 Queries (= N+1-Problem).
|
||||
|
||||
Mit ORM musst du lernen, wie man:
|
||||
|
||||
- **Eager Loading** / `join` / `selectinload` etc. nutzt,
|
||||
- um nur **1–2 Queries** zu erzeugen.
|
||||
|
||||
---
|
||||
|
||||
### 5.2 Man darf SQL nicht völlig „vergessen“
|
||||
|
||||
ORM nimmt viel Arbeit ab, aber:
|
||||
|
||||
- du solltest **verstehen**, was für SQL generiert wird,
|
||||
- und Grundbegriffe wie `JOIN`, `WHERE`, `GROUP BY`, Indexe kennen.
|
||||
|
||||
Ohne grundlegende SQL-Kenntnisse tappst du schnell in Performance-Probleme.
|
||||
|
||||
---
|
||||
|
||||
### 5.3 Komplexe Abfragen
|
||||
|
||||
Für einfache Abfragen ist ORM sehr angenehm.
|
||||
Bei sehr komplexen Auswertungen (z. B. Bank-Reporting mit vielen Aggregationen, Window-Funktionen) kann:
|
||||
|
||||
- die ORM-Syntax unübersichtlich werden,
|
||||
- rohe SQL-Statements manchmal klarer und effizienter sein.
|
||||
|
||||
Viele ORMs erlauben gemischt:
|
||||
|
||||
- 90 % ORM,
|
||||
- 10 % direktes SQL für Spezialfälle.
|
||||
|
||||
---
|
||||
|
||||
### 5.4 Migrationen und Schema-Änderungen
|
||||
|
||||
- Du musst im Blick behalten: **Modelle in Python** und **Schema in Datenbank** dürfen nicht auseinanderlaufen.
|
||||
- Migrations-Tools sind notwendig, aber auch ein eigener Lernbereich.
|
||||
|
||||
---
|
||||
|
||||
### 5.5 „Lock-In“ und Komplexität
|
||||
|
||||
- Große ORMs (z. B. SQLAlchemy ORM, Django ORM) sind mächtig, aber haben Lernkurve.
|
||||
- Wenn du einmal dein ganzes Projekt auf ein bestimmtes ORM gebaut hast, ist ein Wechsel auf ein anderes ORM oder „plain SQL“ aufwendig.
|
||||
|
||||
---
|
||||
|
||||
## 6. Praxisnahe Beispiele mit Python (SQLAlchemy ORM)
|
||||
|
||||
### 6.1 Setup: einfache Bank-Domain mit SQLAlchemy
|
||||
|
||||
Installation:
|
||||
|
||||
```bash
|
||||
pip install sqlalchemy
|
||||
```
|
||||
|
||||
Ein minimaler Aufbau mit `Kunde` und `Konto`:
|
||||
|
||||
```python
|
||||
from sqlalchemy import (
|
||||
create_engine, Column, Integer, String, Float, ForeignKey
|
||||
)
|
||||
from sqlalchemy.orm import (
|
||||
declarative_base, relationship, Session
|
||||
)
|
||||
|
||||
# Basis-Klasse für alle ORM-Modelle
|
||||
Base = declarative_base()
|
||||
|
||||
class Kunde(Base):
|
||||
__tablename__ = "kunde"
|
||||
|
||||
id = Column(Integer, primary_key=True)
|
||||
name = Column(String, nullable=False)
|
||||
|
||||
# Beziehung: Ein Kunde hat viele Konten
|
||||
konten = relationship("Konto", back_populates="kunde")
|
||||
|
||||
def __repr__(self):
|
||||
return f"<Kunde id={self.id} name={self.name!r}>"
|
||||
|
||||
class Konto(Base):
|
||||
__tablename__ = "konto"
|
||||
|
||||
id = Column(Integer, primary_key=True)
|
||||
kontonummer = Column(String, unique=True, nullable=False)
|
||||
saldo = Column(Float, default=0.0)
|
||||
|
||||
kunde_id = Column(Integer, ForeignKey("kunde.id"), nullable=False)
|
||||
# Beziehung: Konto gehört zu genau einem Kunden
|
||||
kunde = relationship("Kunde", back_populates="konten")
|
||||
|
||||
def __repr__(self):
|
||||
return f"<Konto id={self.id} konto={self.kontonummer!r} saldo={self.saldo}>"
|
||||
|
||||
# Engine und Session einrichten (hier: SQLite-Datei)
|
||||
engine = create_engine("sqlite:///bank.db", echo=True) # echo=True zeigt SQL an
|
||||
Base.metadata.create_all(engine) # Tabellen aus den Modellen erzeugen
|
||||
|
||||
# Session erstellen
|
||||
session = Session(engine)
|
||||
|
||||
# Beispiel-Daten anlegen
|
||||
kunde = Kunde(name="Max Mustermann")
|
||||
konto1 = Konto(kontonummer="DE123", saldo=1000.0, kunde=kunde)
|
||||
konto2 = Konto(kontonummer="DE456", saldo=2500.0, kunde=kunde)
|
||||
|
||||
session.add(kunde) # reicht, da konten via Beziehung mit hinzugefügt werden
|
||||
session.commit()
|
||||
|
||||
# Abfragen
|
||||
alle_kunden = session.query(Kunde).all()
|
||||
print(alle_kunden)
|
||||
|
||||
# Zugriff auf Beziehungen
|
||||
for k in alle_kunden:
|
||||
print(f"Kunde: {k.name}")
|
||||
for konto in k.konten:
|
||||
print(f" Konto {konto.kontonummer}, Saldo: {konto.saldo}")
|
||||
|
||||
# Einzelnes Konto laden und zugehörigen Kunden ausgeben
|
||||
konto = session.query(Konto).filter_by(kontonummer="DE123").one()
|
||||
print(konto.kunde.name)
|
||||
|
||||
session.close()
|
||||
```
|
||||
|
||||
Wichtige Punkte im Beispiel:
|
||||
|
||||
- `Kunde` und `Konto` sind Python-Klassen, die Tabellen repräsentieren.
|
||||
- Die Beziehung `Kunde.konten` und `Konto.kunde` erlaubt dir objektorientierten Zugriff.
|
||||
- Das ORM generiert automatisch SQL (sichtbar durch `echo=True`).
|
||||
|
||||
---
|
||||
|
||||
### 6.2 Gleiche Logik mit „plain SQL“ (zum Vergleich)
|
||||
|
||||
Zum Vergleich ein stark vereinfachendes Beispiel mit `sqlite3`:
|
||||
|
||||
```python
|
||||
import sqlite3
|
||||
|
||||
conn = sqlite3.connect("bank_plain.db")
|
||||
cursor = conn.cursor()
|
||||
|
||||
# Tabellen anlegen
|
||||
cursor.execute("""
|
||||
CREATE TABLE IF NOT EXISTS kunde (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL
|
||||
)
|
||||
""")
|
||||
|
||||
cursor.execute("""
|
||||
CREATE TABLE IF NOT EXISTS konto (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
kontonummer TEXT NOT NULL UNIQUE,
|
||||
saldo REAL DEFAULT 0.0,
|
||||
kunde_id INTEGER NOT NULL,
|
||||
FOREIGN KEY(kunde_id) REFERENCES kunde(id)
|
||||
)
|
||||
""")
|
||||
|
||||
# Kunde anlegen
|
||||
cursor.execute("INSERT INTO kunde (name) VALUES (?)", ("Max Mustermann",))
|
||||
kunde_id = cursor.lastrowid
|
||||
|
||||
# Konten anlegen
|
||||
cursor.execute(
|
||||
"INSERT INTO konto (kontonummer, saldo, kunde_id) VALUES (?, ?, ?)",
|
||||
("DE123", 1000.0, kunde_id)
|
||||
)
|
||||
cursor.execute(
|
||||
"INSERT INTO konto (kontonummer, saldo, kunde_id) VALUES (?, ?, ?)",
|
||||
("DE456", 2500.0, kunde_id)
|
||||
)
|
||||
conn.commit()
|
||||
|
||||
# Kunden mit Konten abfragen
|
||||
cursor.execute("""
|
||||
SELECT k.name, ko.kontonummer, ko.saldo
|
||||
FROM kunde k
|
||||
JOIN konto ko ON ko.kunde_id = k.id
|
||||
""")
|
||||
rows = cursor.fetchall()
|
||||
|
||||
for row in rows:
|
||||
name, kontonummer, saldo = row
|
||||
print(name, kontonummer, saldo)
|
||||
|
||||
conn.close()
|
||||
```
|
||||
|
||||
Hier musst du:
|
||||
|
||||
- SQL selbst schreiben,
|
||||
- `rows` manuell in Python-Strukturen übersetzen.
|
||||
|
||||
Mit ORM übernimmt der ORM-Layer:
|
||||
|
||||
- das Mapping,
|
||||
- das Auflösen der Beziehungen,
|
||||
- einiges an Boilerplate.
|
||||
|
||||
---
|
||||
|
||||
## 7. Speziell im Bank-/Finanz-Kontext
|
||||
|
||||
Gerade im Bankumfeld ist ORM attraktiv, weil:
|
||||
|
||||
- es viele **fachliche Entitäten** gibt (Konto, Kunde, Vertrag, Produkt, Transaktion, Wertpapier, Order, …),
|
||||
- du komplexe **Geschäftslogik** in Python implementieren willst,
|
||||
- und dabei konsistent auf persistente Daten zugreifen musst.
|
||||
|
||||
Typische Muster:
|
||||
|
||||
- **Transaktions-Logs** (jede Buchung als eigener Datensatz),
|
||||
- **Audit-Trails** (wer hat wann was geändert),
|
||||
- **Referenzdaten** (Währungen, Länder, Produktstammdaten).
|
||||
|
||||
ORMs helfen:
|
||||
|
||||
- diese Entitäten sauber als Klassen zu modellieren,
|
||||
- Beziehungen explizit zu machen,
|
||||
- und fachliche Operationen wie „Buchung durchführen“ objektorientiert abzubilden.
|
||||
|
||||
---
|
||||
|
||||
## 8. Wann lohnt sich ein ORM – und wann eher nicht?
|
||||
|
||||
**Sinnvoll:**
|
||||
|
||||
- Du baust eine mittlere bis große Anwendung mit vielen Entitäten.
|
||||
- Du willst langfristig Wartbarkeit, Tests und Refactoring erleichtern.
|
||||
- Du hast Standard-CRUD-Operationen (create/read/update/delete) und „normale“ fachliche Logik.
|
||||
|
||||
**Eher nicht sinnvoll:**
|
||||
|
||||
- Kleine Skripte, die nur ein paar einfache SQL-Abfragen ausführen.
|
||||
- Hochoptimierte Reporting- oder Analytics-Abfragen, die ohnehin spezielle SQL-Funktionen nutzen.
|
||||
- Wenn dein Team sehr SQL-affin ist und kein Interesse an der zusätzlichen Abstraktionsschicht hat.
|
||||
|
||||
---
|
||||
|
||||
## 9. Zusammenfassung
|
||||
|
||||
- **ORM** ist eine Technik (und meist eine Bibliothek), um **relationalen Datenbanken** über **Objekte** und **Klassen** zu begegnen.
|
||||
- Sie löst das Mapping zwischen Tabellen/Zeilen und Klassen/Objekten und reduziert manuellen SQL-Boilerplate.
|
||||
- Sie bietet Vorteile in Wartbarkeit, Typisierung, Testbarkeit – gerade bei komplexeren Domänen wie Banking.
|
||||
- Gleichzeitig bringt ein ORM neue Herausforderungen mit sich: Performance-Fallen, Lernaufwand, Verständnis der generierten SQL-Abfragen bleibt wichtig.
|
||||
- In Python sind gängige ORMs: **[[SQLAlchemy]] ORM**, **Django ORM**, (in moderneren Stacks oft in Kombination mit [[Pydantic]], [[FastAPI]] etc.).
|
||||
|
||||
Executable
+346
@@ -0,0 +1,346 @@
|
||||
Hier eine kompakte, praxisorientierte Übersicht zu PostgreSQL mit Fokus auf „Was ist anders als in Oracle?“ und „Was sollte ich vor dem Umstieg lernen?“.
|
||||
|
||||
---
|
||||
|
||||
## 1. Kurzüberblick & Gemeinsamkeiten
|
||||
|
||||
Gemeinsamkeiten (nur kurz, da dir das meiste vertraut ist):
|
||||
|
||||
- Relationales DBMS, SQL-basiert, ANSI-konform
|
||||
- [[ACID-Transaktionen]], [[MVCC]], Isolation Levels
|
||||
- Sequences, Views, [[Materialized Views]], Trigger, Stored Procedures/Functions
|
||||
- Joins, Subselects, Window Functions, [[CTEs]] (`WITH`), etc.
|
||||
- Rolle-/Rechtemodell, Schemas
|
||||
|
||||
---
|
||||
|
||||
## 2. Zentrale Unterschiede auf einen Blick
|
||||
|
||||
Die wichtigsten Lernfelder im Übergang von Oracle zu PostgreSQL:
|
||||
|
||||
1. **Namensräume & `search_path` statt Synonyme**
|
||||
2. **Datentypen & Funktionalität (z.B. `NUMERIC`, `SERIAL`, `JSONB`, Arrays)**
|
||||
3. **Sequences & Identity-Spalten**
|
||||
4. **PL/pgSQL vs. PL/SQL (kein Paketkonzept)**
|
||||
5. **MVCC-Implementierung & VACUUM/Autovacuum**
|
||||
6. **DDL in Transaktionen, Auto-Commit-Verhalten**
|
||||
7. **Index-Typen & Besonderheiten (GIN/GiST, Partial-, Expression-Indexe)**
|
||||
8. **Partitionierung (deutlich anders als ältere Oracle-Partitioning-Konzepte)**
|
||||
9. **Admin & Tools (psql, Konfigurationsparameter, Monitoring)**
|
||||
|
||||
---
|
||||
|
||||
## 3. Schemata, Namensauflösung & Synonyme
|
||||
|
||||
### Schemata & `search_path`
|
||||
|
||||
In PostgreSQL sind Schemata ähnlich wie in Oracle. Es gibt zusätzlich einen **`search_path`**, der festlegt, in welcher Reihenfolge Schemata nach Objekten durchsucht werden.
|
||||
|
||||
```sql
|
||||
SHOW search_path;
|
||||
SET search_path TO app, public;
|
||||
```
|
||||
|
||||
Statt Synonymen (Oracle) wird oft mit `search_path` gearbeitet:
|
||||
|
||||
- Kein `CREATE SYNONYM` in PostgreSQL.
|
||||
- Alternative: Views in einem „zentrale“ Schema, `search_path` setzen, oder `CREATE VIEW` als Abstraktionsschicht.
|
||||
|
||||
**Lernpunkt:** Überleg dir, wie du Synonyme ablöst – meistens durch eine Kombination aus `search_path`, Views und ggf. konsistenter Schema-Namenskonvention.
|
||||
|
||||
---
|
||||
|
||||
## 4. Datentypen & SQL-Dialekt
|
||||
|
||||
### Wichtige Unterschiede bei Datentypen
|
||||
|
||||
- `NUMBER` → in der Regel `NUMERIC(p,s)` oder `INTEGER`, `BIGINT`.
|
||||
- `VARCHAR2` → `VARCHAR(n)` oder `TEXT`. PostgreSQL hat keinen Unterschied zwischen `VARCHAR` und `VARCHAR2`.
|
||||
- `DATE` in PostgreSQL enthält Datum **und Uhrzeit** (wie Oracle `DATE`). Für „reine“ Datumswerte: `date`, für Zeitpunkte mit Zeitzone: `timestamptz`.
|
||||
- `CLOB`/`BLOB` → meist `TEXT` bzw. `BYTEA`.
|
||||
- Zusätzliche Typen:
|
||||
- `JSON`/`JSONB`
|
||||
- Arrays (`integer[]`, `text[]` etc.)
|
||||
- Geodaten (via Extension PostGIS)
|
||||
- `UUID`, `Inet`, `CIDR` etc.
|
||||
|
||||
```sql
|
||||
CREATE TABLE kunde (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
name VARCHAR(200),
|
||||
email TEXT,
|
||||
erstellt_am timestamptz DEFAULT now(),
|
||||
daten JSONB
|
||||
);
|
||||
```
|
||||
|
||||
### Funktionen und Syntax-Details
|
||||
|
||||
- String-Konkatenation: `||` (wie Oracle).
|
||||
- `NVL` → `COALESCE` (Standard); es gibt auch `NULLIF`.
|
||||
- `DECODE` → `CASE WHEN ... THEN ... ELSE ... END`.
|
||||
- `ROWNUM` → `LIMIT` / `OFFSET` oder Window Functions (`row_number()`).
|
||||
- Kein „`FROM dual`“ nötig, du kannst einfach:
|
||||
```sql
|
||||
SELECT 1;
|
||||
```
|
||||
|
||||
**Lernpunkt:** Zuordnen der wichtigsten Oracle-Funktionen zu PostgreSQL-Äquivalenten (`DECODE` → `CASE`, `NVL` → `COALESCE`, `ROWNUM` → `LIMIT`/Window).
|
||||
|
||||
---
|
||||
|
||||
## 5. Sequences & Identity-Spalten
|
||||
|
||||
In Oracle: `CREATE SEQUENCE ...` + Trigger oder `IDENTITY` in neueren Versionen.
|
||||
|
||||
In PostgreSQL gibt es mehrere Varianten:
|
||||
|
||||
- Klassische Sequence:
|
||||
```sql
|
||||
CREATE SEQUENCE myseq;
|
||||
SELECT nextval('myseq');
|
||||
```
|
||||
|
||||
- „Legacy“ Auto-Increment:
|
||||
```sql
|
||||
CREATE TABLE t (
|
||||
id SERIAL PRIMARY KEY,
|
||||
...
|
||||
);
|
||||
```
|
||||
|
||||
- Standardkonforme [[Identity Columns]] (empfohlen):
|
||||
```sql
|
||||
CREATE TABLE t (
|
||||
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
||||
...
|
||||
);
|
||||
```
|
||||
|
||||
**Lernpunkt:** Umstieg auf `GENERATED AS IDENTITY` planen, `SERIAL` verstehen (ist syntaktischer Zucker für Sequence + Default).
|
||||
|
||||
---
|
||||
|
||||
## 6. PL/pgSQL vs. PL/SQL (Packages, Prozeduren, Funktionen)
|
||||
|
||||
### Kein Paketkonzept
|
||||
|
||||
PostgreSQL hat keine Packages wie Oracle (`package spec/body`). Stattdessen:
|
||||
|
||||
- Funktionen stehen „flach“ in einem Schema.
|
||||
- Ähnliches Strukturieren über Namenskonventionen, Schemata, Extensions.
|
||||
|
||||
### PL/pgSQL
|
||||
|
||||
Syntax ähnlich PL/SQL, aber mit einigen Unterschieden:
|
||||
|
||||
```sql
|
||||
CREATE OR REPLACE FUNCTION addiere(a int, b int)
|
||||
RETURNS int
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
DECLARE
|
||||
res int;
|
||||
BEGIN
|
||||
res := a + b;
|
||||
RETURN res;
|
||||
END;
|
||||
$$;
|
||||
```
|
||||
|
||||
Ab PostgreSQL 11 gibt es auch **Stored Procedures** (ohne Rückgabewert, aufrufbar mit `CALL`) – im Unterschied zu Funktionen, die in SQL-Ausdrücke eingebettet werden können.
|
||||
|
||||
**Lernpunkte:**
|
||||
|
||||
- Unterschiede in Ausnahmebehandlung und Cursor-Syntax im Detail.
|
||||
- Kein Paket-Overloading wie in Oracle – Overloading geht, aber ohne Packages.
|
||||
- Migration von Package-Variablen: oft durch Tabellen, Konfigurationstabellen oder `SET LOCAL` + GUC-Parameter ersetzen.
|
||||
|
||||
---
|
||||
|
||||
## 7. Transaktionen, MVCC & Locks
|
||||
|
||||
### MVCC-Implementierung
|
||||
|
||||
Beide nutzen MVCC, aber die Implementierung ist anders:
|
||||
|
||||
- PostgreSQL speichert mehrere Versionen von Zeilen (Tuples) im Heap.
|
||||
- Gelöschte/veraltete Versionen werden nicht sofort entfernt, sondern durch **VACUUM** aufgeräumt.
|
||||
- Standard: Autovacuum kümmert sich darum. Manchmal Tuning nötig (`autovacuum_*`-Parameter).
|
||||
|
||||
### Isolation Levels
|
||||
|
||||
Standard-Level in PostgreSQL ist `READ COMMITTED`, `REPEATABLE READ` ist nicht exakt wie Oracle `SERIALIZABLE`. Es gibt zusätzlich ein echtes `SERIALIZABLE` via SSI (Serializable Snapshot Isolation).
|
||||
|
||||
```sql
|
||||
SHOW default_transaction_isolation;
|
||||
SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL REPEATABLE READ;
|
||||
```
|
||||
|
||||
### DDL in Transaktionen
|
||||
|
||||
- PostgreSQL erlaubt DDL **innerhalb** von Transaktionen und kann diese zurückrollen:
|
||||
```sql
|
||||
BEGIN;
|
||||
CREATE TABLE test (id int);
|
||||
ROLLBACK; -- Tabelle existiert danach nicht
|
||||
```
|
||||
|
||||
- Standard-Clients (z.B. `psql`) arbeiten mit Auto-Commit = ON, aber du kannst das Verhalten steuern.
|
||||
|
||||
**Lernpunkte:**
|
||||
|
||||
- VACUUM/Autovacuum verstehen: wann notwendig, wie überwachen, wie konfigurieren.
|
||||
- Unterschiedliche Semantik von Isolation Levels im Detail prüfen (z.B. bei Portierung von Code, der sich auf Oracle-Sperrverhalten verlässt).
|
||||
|
||||
---
|
||||
|
||||
## 8. Indizes & Partitionierung
|
||||
|
||||
### Index-Typen
|
||||
|
||||
PostgreSQL bietet mehr verschiedene Index-Methoden:
|
||||
|
||||
- `btree` (Standard)
|
||||
- `hash` (seltener)
|
||||
- `GIN` (für Volltext, JSONB-Keys, Arrays)
|
||||
- `GiST` (Geodaten, Range-Typen)
|
||||
- `BRIN` (große append-only Tabellen, z.B. Logs)
|
||||
|
||||
Zusätzlich:
|
||||
|
||||
- **Expression Indexes**:
|
||||
```sql
|
||||
CREATE INDEX idx_lower_name ON kunde (lower(name));
|
||||
```
|
||||
|
||||
- **Partial Indexes**:
|
||||
```sql
|
||||
CREATE INDEX idx_active_kunden ON kunde (id) WHERE aktiv = true;
|
||||
```
|
||||
|
||||
### Partitionierung
|
||||
|
||||
Neuere PostgreSQL-Versionen haben native Partitionierung (Range, List, Hash). Unterschied zu Oracle:
|
||||
|
||||
- Implementierung anders, kein identisches Interface.
|
||||
- Viele Operationen laufen „Partition-transparent“, aber es gibt noch Ecken (z.B. bestimmte DDL-Operationen).
|
||||
|
||||
```sql
|
||||
CREATE TABLE messwerte (
|
||||
id BIGINT GENERATED ALWAYS AS IDENTITY,
|
||||
ts timestamptz NOT NULL,
|
||||
wert numeric
|
||||
) PARTITION BY RANGE (ts);
|
||||
|
||||
CREATE TABLE messwerte_2024 PARTITION OF messwerte
|
||||
FOR VALUES FROM ('2024-01-01') TO ('2025-01-01');
|
||||
```
|
||||
|
||||
**Lernpunkte:**
|
||||
|
||||
- Eignung und Grenzen von GIN/GiST verstehen, wenn du JSONB oder Volltext planst.
|
||||
- Partitionierungsstrategie neu durchdenken, statt 1:1 die Oracle-Logik zu kopieren.
|
||||
|
||||
---
|
||||
|
||||
## 9. Materialized Views
|
||||
|
||||
PostgreSQL hat Materialized Views, aber:
|
||||
|
||||
- Kein `ON COMMIT REFRESH`.
|
||||
- Refresh ist explizit:
|
||||
```sql
|
||||
REFRESH MATERIALIZED VIEW my_mv;
|
||||
```
|
||||
- Optional mit `CONCURRENTLY` (mit Einschränkungen), um Downtime zu minimieren.
|
||||
|
||||
**Lernpunkt:** Wenn du in Oracle stark auf automatische Refresh-Mechanismen setzt, brauchst du in PostgreSQL einen eigenen Refresh-Workflow (z.B. Cron, Scheduler, Applikationslogik).
|
||||
|
||||
---
|
||||
|
||||
## 10. Rechte & Rollenmodell
|
||||
|
||||
Ähnlich, aber mit eigenen Begriffen/Details:
|
||||
|
||||
- Nur **Rollen** (kein getrenntes Konzept von „User“ und „Role“ wie in Oracle; ein User ist eine Login-fähige Rolle).
|
||||
- Rechte auf Objekte (`GRANT SELECT ON table TO role;` etc.)
|
||||
- Kein systemweites `PUBLIC SYNONYM`, aber es gibt das Schema `public` und das `PUBLIC`-Rolle/Privileges-Konzept.
|
||||
|
||||
```sql
|
||||
CREATE ROLE app_user LOGIN PASSWORD '...';
|
||||
GRANT CONNECT ON DATABASE mydb TO app_user;
|
||||
GRANT USAGE ON SCHEMA app TO app_user;
|
||||
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA app TO app_user;
|
||||
```
|
||||
|
||||
**Lernpunkt:** Mapping deiner bisherigen Oracle-Rollen/Profiles auf PostgreSQL-Rollen und -Rechte.
|
||||
|
||||
---
|
||||
|
||||
## 11. Administration, Tools & Ökosystem
|
||||
|
||||
### Wichtige Tools
|
||||
|
||||
- `psql` (Kommandozeile, sehr mächtig)
|
||||
- Admin-Tools: `pgAdmin`, `DBeaver`, `DataGrip` etc.
|
||||
- Verbindungs-Pooler (wichtig, da PostgreSQL pro Verbindung einen Prozess startet):
|
||||
- `pgbouncer`, `pgpool-II`
|
||||
|
||||
### Wichtige Admin-Konzepte
|
||||
|
||||
- **Konfiguration** über `postgresql.conf` und `ALTER SYSTEM`:
|
||||
z.B. `shared_buffers`, `work_mem`, `maintenance_work_mem`, `max_connections`.
|
||||
- **VACUUM/ANALYZE**:
|
||||
- `VACUUM` räumt auf.
|
||||
- `ANALYZE` aktualisiert Statistiken.
|
||||
- **EXPLAIN/EXPLAIN ANALYZE**: zwingend für Performance-Tuning.
|
||||
|
||||
```sql
|
||||
EXPLAIN ANALYZE
|
||||
SELECT ...
|
||||
FROM ...
|
||||
WHERE ...;
|
||||
```
|
||||
|
||||
- **Backup/Recovery**:
|
||||
- Logische Backups: `pg_dump`, `pg_dumpall`.
|
||||
- Physische Backups & PITR: Base-Backups + WAL-Archiving (oder Tools wie `pgBackRest`).
|
||||
|
||||
**Lernpunkte:**
|
||||
|
||||
- Verbindungspooling ist viel wichtiger als in Oracle.
|
||||
- Autovacuum-Monitoring (z.B. via `pg_stat_*`-Views) und Basiskonfiguration.
|
||||
|
||||
---
|
||||
|
||||
## 12. Typische Stolpersteine beim Umstieg
|
||||
|
||||
1. **Fehlende Synonyme** → Lösung über `search_path`, Views, klare Schema-Strategie.
|
||||
2. **Paketkonzept fehlt** → Namespacing neu denken.
|
||||
3. **Materialized View Refresh** → eigene Scheduler-Logik.
|
||||
4. **ROWNUM/`dual`** → an `LIMIT`/`OFFSET`, Window Functions, `SELECT 1;` gewöhnen.
|
||||
5. **Datenbanklinks (`DBLINK`)** → Extension `dblink` oder `postgres_fdw` (Foreign Data Wrapper) verwenden.
|
||||
6. **Autoincrement** → `IDENTITY`/`SERIAL` + `currval()/nextval()`-Verwendung lernen.
|
||||
7. **Optimizer-Verhalten** → Statistiken, Parameter und Query-Pläne sind anders; alte „Index-Hints-Gewohnheiten“ greifen nicht 1:1.
|
||||
|
||||
---
|
||||
|
||||
## 13. Sinnvolle Lernreihenfolge
|
||||
|
||||
1. Basiskonzepte:
|
||||
- Schemata + `search_path`
|
||||
- Datentyp-Mapping (NUMBER, DATE, CLOB/BLOB → NUMERIC, TIMESTAMP, TEXT/BYTEA)
|
||||
- Sequences & Identity
|
||||
2. SQL-Dialekt:
|
||||
- Funktionale Unterschiede (`DECODE`, `NVL`, `ROWNUM`, `dual`, Subqueries)
|
||||
3. Server-seitige Logik:
|
||||
- PL/pgSQL, Unterschiede zu PL/SQL, kein Package-Konzept
|
||||
4. MVCC & Performance:
|
||||
- VACUUM/Autovacuum, EXPLAIN ANALYZE, Index-Typen
|
||||
5. Admin & Betrieb:
|
||||
- Rollen, Backups, Konfiguration, Verbindungspooling
|
||||
6. Spezielle Features:
|
||||
- JSONB, Arrays, GIN-Indizes, Partitionierung, FDWs
|
||||
|
||||
Wenn du magst, kannst du mir ein paar typische Oracle-Features nennen, die du intensiv nutzt (z.B. bestimmte Partitionierungsarten, Advanced Queuing, bestimmte PL/SQL-Patterns). Dann kann ich dir eine gezielte „Mapping-Tabelle“ für genau diese Themen in PostgreSQL machen.
|
||||
Reference in New Issue
Block a user