update
This commit is contained in:
parent
17f3491f05
commit
ecc7ab358e
BIN
Writerside/images/image_914.png
Normal file
BIN
Writerside/images/image_914.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 24 KiB |
BIN
Writerside/images/image_915.png
Normal file
BIN
Writerside/images/image_915.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 13 KiB |
BIN
Writerside/images/image_916.png
Normal file
BIN
Writerside/images/image_916.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 22 KiB |
BIN
Writerside/images/image_917.png
Normal file
BIN
Writerside/images/image_917.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 27 KiB |
BIN
Writerside/images/image_918.png
Normal file
BIN
Writerside/images/image_918.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 13 KiB |
BIN
Writerside/images/image_919.png
Normal file
BIN
Writerside/images/image_919.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 13 KiB |
@ -80,6 +80,7 @@
|
|||||||
<toc-element topic="01_semantischeDatenmodellierungUndRelationenmodell.md"/>
|
<toc-element topic="01_semantischeDatenmodellierungUndRelationenmodell.md"/>
|
||||||
<toc-element topic="02_semantischeDatenmodellierung.md"/>
|
<toc-element topic="02_semantischeDatenmodellierung.md"/>
|
||||||
<toc-element topic="03_sql.md"/>
|
<toc-element topic="03_sql.md"/>
|
||||||
|
<toc-element topic="04_anwendungsentwicklung.md"/>
|
||||||
|
|
||||||
</toc-element>
|
</toc-element>
|
||||||
<toc-element toc-title="HCI">
|
<toc-element toc-title="HCI">
|
||||||
|
489
Writerside/topics/04/Datenbanken/04_anwendungsentwicklung.md
Normal file
489
Writerside/topics/04/Datenbanken/04_anwendungsentwicklung.md
Normal file
@ -0,0 +1,489 @@
|
|||||||
|
# Anwendungsentwicklung
|
||||||
|
## Grundprinzipien
|
||||||
|
- Fall 1: Ergebnis der Abfrage ist (max) ein einzelner Tupel
|
||||||
|
- bspw. _Ausgabe von Preis und Namen des Produkts mit der Nr. 203_
|
||||||
|
- 
|
||||||
|
- Fall 2: Ergebnis der Abfrage sind mehrere Tupel
|
||||||
|
- bspw. _Ausgabe aller Produkte mit einem Preis größer 100 Euro_
|
||||||
|
- Problem: Unterschiedliche Datenstrukturen
|
||||||
|
- (imperative) Programmiersprachen: Tupel
|
||||||
|
- SQL: Relation (Menge von Tupeln)
|
||||||
|
- → Impedance mismatch
|
||||||
|
- 
|
||||||
|
|
||||||
|
### Cursor-Konzept
|
||||||
|
- Abstrakte Sicht auf eine Relation, realisiert als Liste
|
||||||
|
- Anfrageergebnisse werden sequenziell abgearbeitet
|
||||||
|
- 
|
||||||
|
|
||||||
|
## Prozedurale SQL-Erweiterungen
|
||||||
|
- Nachteile von SQL in höheren Programmiersprachen
|
||||||
|
- Anwendungsprogramm wechselt häufig zwischen Host-Sprache und SQL-Anweisung
|
||||||
|
- Optimizer (Kern von DBMS) kann nur Bereich einzelner SQL-Anweisungen überschauen
|
||||||
|
- Daten müssen zwischen DB und Anwendung transportiert werden
|
||||||
|
- Lösungen
|
||||||
|
- Erweiterung von SQL um konzepte prozeduraler Sprachen
|
||||||
|
|
||||||
|
### Aufbau und Kontrollstrukturen
|
||||||
|
- Kleinstes Element ist ein Block
|
||||||
|
- ```SQL
|
||||||
|
[ DECLARE
|
||||||
|
declarations ]
|
||||||
|
BEGIN
|
||||||
|
statements
|
||||||
|
END;
|
||||||
|
```
|
||||||
|
- Kontrollstrukturen
|
||||||
|
- ```SQL
|
||||||
|
IF bedingung THEN
|
||||||
|
statements
|
||||||
|
[ ELSE
|
||||||
|
statements ]
|
||||||
|
END IF;
|
||||||
|
```
|
||||||
|
- ```SQL
|
||||||
|
WHILE bedingung LOOP
|
||||||
|
statements
|
||||||
|
END LOOP;
|
||||||
|
```
|
||||||
|
- ```SQL
|
||||||
|
FOR name IN [reverse] expression ... expression [BY expression] LOOP
|
||||||
|
statements
|
||||||
|
END LOOP;
|
||||||
|
```
|
||||||
|
- können direkt in SQL-Client ausgeführt werden
|
||||||
|
- Ausführung startet nach `;`
|
||||||
|
- `$$ ... $$` sind Stringbegrenzer (ähnlich zu `{...}` in C++)
|
||||||
|
- ```bash
|
||||||
|
user=> DO $$
|
||||||
|
user=> BEGIN
|
||||||
|
user=> RAISE NOTICE 'Mein erster Test';
|
||||||
|
user=> END $$;
|
||||||
|
NOTICE: Mein erster Test
|
||||||
|
DO
|
||||||
|
user=>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Stored Procedures
|
||||||
|
- entspricht Funktion, die in der Sprache des jeweiligen DBMS geschrieben wird
|
||||||
|
- werden in Tabellen des Systemkatalogs der DB abgelegt
|
||||||
|
- Spezielle Anwendung:
|
||||||
|
- Kombination mit Triggern
|
||||||
|
- Beispiel:
|
||||||
|
- ```PLSQL
|
||||||
|
CREATE OR REPLACE FUNCTION square(integer) returns integer as $SQUARE$
|
||||||
|
DECLARE
|
||||||
|
x integer := $1;
|
||||||
|
BEGIN
|
||||||
|
RAISE NOTICE 'Meine erste Funktion';
|
||||||
|
x := x*x;
|
||||||
|
RAISE NOTICE 'Ergebnis: %', x;
|
||||||
|
RETURN x;
|
||||||
|
END;
|
||||||
|
$SQUARE$ LANGUAGE plpgsql;
|
||||||
|
```
|
||||||
|
- erzeugt Funktion mit dem Namen `square`, die einen int als Parameter akzeptiert und zurückgibt
|
||||||
|
- `CREATE OR REPLACE` erlaubt Änderung eines Stored Procedure
|
||||||
|
|
||||||
|
|
||||||
|
### Auslesen einzerlner Tupel
|
||||||
|
- **Mithilfe eines Cursors**
|
||||||
|
- ```PLSQL
|
||||||
|
CREATE OR REPLACE FUNCTION countairports() returns integer as $countairports$
|
||||||
|
DECLARE
|
||||||
|
anzahl integer;
|
||||||
|
c1 cursor for select count(*) from flughafen;
|
||||||
|
BEGIN
|
||||||
|
open c1;
|
||||||
|
fetch c1 into anzahl;
|
||||||
|
close c1;
|
||||||
|
RETURN anzahl;
|
||||||
|
END;
|
||||||
|
$countairports$ LANGUAGE plpgsql;
|
||||||
|
```
|
||||||
|
- Ausgabe:
|
||||||
|
- ```bash
|
||||||
|
user => select countairports();
|
||||||
|
countairports
|
||||||
|
-------------
|
||||||
|
12
|
||||||
|
```
|
||||||
|
- **Mit Hilfe eines `SELECT INTO`**
|
||||||
|
- ```PLSQL
|
||||||
|
CREATE OR REPLACE FUNCTION countairports2() returns integer as $countairports2$
|
||||||
|
DECLARE
|
||||||
|
anzahl integer;
|
||||||
|
BEGIN
|
||||||
|
select count(*) INTO anzahl from flughafen;
|
||||||
|
RETURN anzahl;
|
||||||
|
END;
|
||||||
|
$countairports2$ LANGUAGE plpgsql;
|
||||||
|
```
|
||||||
|
- Ausgabe:
|
||||||
|
- ```bash
|
||||||
|
user => select countairports2();
|
||||||
|
countairports2
|
||||||
|
-------------
|
||||||
|
12
|
||||||
|
```
|
||||||
|
### Cursor in PL/pgSQL
|
||||||
|
```PLSQL
|
||||||
|
CREATE OR REPLACE FUNCTION listflights() returns void AS $listflights$
|
||||||
|
DECLARE
|
||||||
|
c1 CURSOR for SELECT * FROM flughafen;
|
||||||
|
rowl RECORD;
|
||||||
|
BEGIN
|
||||||
|
for rowl IN c1 LOOP
|
||||||
|
raise notice 'Flugnummer: %', rowl.flugnr;
|
||||||
|
end loop;
|
||||||
|
return;
|
||||||
|
end;
|
||||||
|
$listflights$ language plpgsql
|
||||||
|
```
|
||||||
|
- Mit jedem Schleifendurchlauf
|
||||||
|
- Cursor wird eine Position weitergeschaltet
|
||||||
|
- record passt sich auf Zeile des Cursors an
|
||||||
|
- mittels `.` kann auf Attribute zugegriffen werden
|
||||||
|
- _Cursor wird durch Schleife automatisch geöffnet, geschlossen_
|
||||||
|
|
||||||
|
### Ändern von Tabelleninhalten
|
||||||
|
```PLSQL
|
||||||
|
create or replace function wartun(varchar(8)) returns void as $wartung$
|
||||||
|
declare
|
||||||
|
knz varchar(8) := $1;
|
||||||
|
heute date := current_date;
|
||||||
|
begin
|
||||||
|
if not exists(select * from protokoll where kennzeichen = knz and datum = heute)
|
||||||
|
then
|
||||||
|
insert into protokoll values (knz, heute, true);
|
||||||
|
else
|
||||||
|
update protokoll set freigabe = true where kennzeichen = knz and datum = heute;
|
||||||
|
end if;
|
||||||
|
end;
|
||||||
|
$wartung$ language plpgsql
|
||||||
|
```
|
||||||
|
- `NOT EXISTS` (bzw `EXISTS`) tested, ob überhaupt Zeilen existieren
|
||||||
|
- `INSERT` und `UPDATE` können _einfach so_ genutzt werden
|
||||||
|
|
||||||
|
- Ändern über Cursor
|
||||||
|
- ```PLSQL
|
||||||
|
DO $tarifrunde$
|
||||||
|
DECLARE
|
||||||
|
c1 cursor for select * from angestellter;
|
||||||
|
row1 record;
|
||||||
|
proz1 float8 = 1.02;
|
||||||
|
proz2 float8 = 1.05;
|
||||||
|
BEGIN
|
||||||
|
open c1;
|
||||||
|
loop
|
||||||
|
fetch c1 into row1;
|
||||||
|
exit when not found;
|
||||||
|
if row1.gehalt > 10000
|
||||||
|
then
|
||||||
|
update angestellter set gehalt = gehalt * proz1 where current of c1;
|
||||||
|
else
|
||||||
|
update angestellter set gehalt * proz2 where current of c1;
|
||||||
|
end if;
|
||||||
|
end loop;
|
||||||
|
END;
|
||||||
|
$tarifrunde$ language plpgsql
|
||||||
|
```
|
||||||
|
- `EXIT WHEN NOT FOUND` verlässt Schleife, wenn kein Datensatz (mehr) gefunden wird
|
||||||
|
- `WHERE CURRENT OF c1` beschränkt Update auf den Tupel an Position des Cursors
|
||||||
|
|
||||||
|
## Semantische Integritätsbedingungen
|
||||||
|
### Trigger
|
||||||
|
- Folge von benutzerdefinierten Anweisungen, die automatisch beim Vorliegen bestimmter Bedingungen ausgeführt werden
|
||||||
|
- bspw. bei Einfügen, Update, Löschen
|
||||||
|
- Aufbau:
|
||||||
|
- ```PLSQL
|
||||||
|
CREATE TRIGGER name { BEFORE | AFTER |INSTEAD OF } { event [OR...]}
|
||||||
|
ON table_name
|
||||||
|
[FROM referenced_table_name ]
|
||||||
|
[NOT DEFERRABLE | DEFERRABLE ][INITIALLY IMMEDIATE | INITIALLY DEFERRED]
|
||||||
|
[FOR [EACH] {ROW | STATEMENT}]
|
||||||
|
[WHEN (condition)]
|
||||||
|
EXECUTE FUNCTION function_name (arguments)
|
||||||
|
|
||||||
|
----------------
|
||||||
|
|
||||||
|
where event can be one of:
|
||||||
|
INSERT
|
||||||
|
UPDATE [ OF column_name [, ...]]
|
||||||
|
DELETE
|
||||||
|
TRUNCATE
|
||||||
|
```
|
||||||
|
- Bsp _Keine Preissenkung gestatten bei Änderung eines Preises_:
|
||||||
|
- ```PLSQL
|
||||||
|
CREATE OR REPLACE FUNCTION nosale() returns trigger as $$
|
||||||
|
BEGIN
|
||||||
|
IF NEW.ProdPreis < OLD.ProdPrice then
|
||||||
|
NEW.ProdPreis = OLD.ProdPrice;
|
||||||
|
RAISE NOTICE 'Preissenkung nicht gestattet!';
|
||||||
|
END IF;
|
||||||
|
RETURN NEW;
|
||||||
|
END;
|
||||||
|
$$ language plpgsql;
|
||||||
|
|
||||||
|
CREATE TRIGGER nosaletrigger BEFORE UPDATE ON Produkt
|
||||||
|
FOR EACH ROW EXECUTE FUNCTION nosale();
|
||||||
|
```
|
||||||
|
|
||||||
|
## Zugriff auf relationale DB über APIs
|
||||||
|
### SQLALCHEMY
|
||||||
|
- ermöglicht DB-Zugriffe aus Python
|
||||||
|
- Installation
|
||||||
|
- `pip install sqlalchemy`
|
||||||
|
- `pip install psycopg2`
|
||||||
|
- Begriffe:
|
||||||
|
- **Driver**
|
||||||
|
- Treiber, der zur Verbindung zur DB benötigt wird
|
||||||
|
- **Engine**
|
||||||
|
- Verbindungsdaten zu einem DB-Server
|
||||||
|
- Von ihr aus werden Connections erzeugt
|
||||||
|
- **Connection**
|
||||||
|
- konkrete Verbindung zur DB
|
||||||
|
- Kann SQL Befehle ausführen
|
||||||
|
- **Result**
|
||||||
|
- Ergebnisse werden in einem Result gespeichert
|
||||||
|
- Beispiel:
|
||||||
|
- ```Python
|
||||||
|
from sqlalchemy import *
|
||||||
|
|
||||||
|
engine = create_engine("postgresql+psycopg2://user:pass@141.100.70.93/dbname", echo=False)
|
||||||
|
|
||||||
|
with engine.connect() as con: # ensures closing the connection at the end of the block
|
||||||
|
s = text("Select * from buch") # Creates Query object
|
||||||
|
result = con.execute(s)
|
||||||
|
for row in result:
|
||||||
|
print(row)
|
||||||
|
|
||||||
|
s = text("Select isbn, title from buch")
|
||||||
|
result = con.execute(s)
|
||||||
|
print(result.fetchall()) # fetches all rows as list and prints it
|
||||||
|
|
||||||
|
s = text("Select * from buch where verlegt_name = 'Verglag 4'")
|
||||||
|
result = con.execute(s)
|
||||||
|
for row in result:
|
||||||
|
print(row.isbn) # prints only isbn
|
||||||
|
```
|
||||||
|
|
||||||
|
#### SQL Injections
|
||||||
|
```Python
|
||||||
|
stmt = text("select * from account where username='" + username + "' and password='" + password + "'")
|
||||||
|
|
||||||
|
result = con.execute(stmt)
|
||||||
|
|
||||||
|
# If the result contains the specified user/password combination, then that user exists
|
||||||
|
if result.fetchone() is not None:
|
||||||
|
print("User logged in")
|
||||||
|
else:
|
||||||
|
print("Username and/or Password wrong!")
|
||||||
|
```
|
||||||
|
|
||||||
|
- Bei Eingabe von `abc' or '1' ='1` als Passwort
|
||||||
|
- Ausführen von `select * from account where username='abc' and password='abc' OR ‘1’=’1’;`
|
||||||
|
- Ergbnis ist eine Zeile, auch wenn das Passwort falsch war
|
||||||
|
|
||||||
|
##### Lösung: Parameter Binding
|
||||||
|
```Python
|
||||||
|
stmt = text("select * from account where username=:user and password=:pass")
|
||||||
|
|
||||||
|
result = con.execute(stmt, {"user": username, "pass": password})
|
||||||
|
|
||||||
|
# If the result contains the specified user/password combination, then that user exists
|
||||||
|
if result.fetchone() is not None:
|
||||||
|
print("User logged in")
|
||||||
|
else:
|
||||||
|
print("Username and/or Password wrong!")
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Speichern von MetaDaten
|
||||||
|
```Python
|
||||||
|
from sqlalchemy import MetaData # or from sqlalchemy import *
|
||||||
|
meta = MetaData() # create metadata object
|
||||||
|
|
||||||
|
# Informationen über Tabellen werden anschließend aus DB extrahiert
|
||||||
|
buch = Table("buch", meta, autoload_with=engine)
|
||||||
|
verlag = Table("verlag", meta, autoload_with=engine)
|
||||||
|
autor = Table("autor", meta, autoload_with=engine)
|
||||||
|
verfasst = Table("ist_autor", meta, autoload_with=engine)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Impedance Mismatch
|
||||||
|
- Wenn Daten aus DB extrahiert werden und in Klassen übernommen werden sollen, müssen diese konvertiert werden
|
||||||
|
- Nennt man Impedance Mismatch
|
||||||
|
- _Unverträglichkeit der beiden Welten_
|
||||||
|
- 
|
||||||
|
|
||||||
|
## Objekt-relationales Mapping (OR-Mapping)
|
||||||
|
- Mapping zwischen objektorientiertem (Klassendiagramm) und Relationenmodell
|
||||||
|
- Änderungen werden automatisch an DB-System weitergegeben
|
||||||
|
- Strategien
|
||||||
|
- | Top-Down | Bottom-Up |
|
||||||
|
|----------------------------------------------------------------------------|-------------------------------------------------------------------------------|
|
||||||
|
| Erstellen eines Klassendiagramms und mappen auf ein relationales DB-Schema | Erstellen eines relationalen DB-Schemas und Re-Engineering auf Entity-Klassen |
|
||||||
|
|  |  |
|
||||||
|
- Begriffe
|
||||||
|
- **Session**
|
||||||
|
- Erweiterung der bisher bekannten Connection
|
||||||
|
- Verwaltet Verbindung zur DB und Transaktionen
|
||||||
|
- Wichtiger Bestandteil: _identity map_
|
||||||
|
- referenziert Objekte im Persistenzkontext
|
||||||
|
- werden anhand der PKs unterschieden
|
||||||
|
- **Persistenzkontext**
|
||||||
|
- Objekte können einer Session hinzugefügt werden
|
||||||
|
- Alle "managed" Objekte werden auf Änderungen überwacht
|
||||||
|
- Beim Schließen oder Committen der Session auf DB übertragen
|
||||||
|
- **Mapping**
|
||||||
|
- definiert wie und ob ein Objekt der Anwendung in der DB abgebildet wird
|
||||||
|
|
||||||
|
### Table Annotation
|
||||||
|
- Jede Klasse muss entsprechend annotiert werden
|
||||||
|
- werden auch Entity Klassen genannt
|
||||||
|
- Metadaten werden in einer Klasse gespeichert, von der die Klassen der Anwendung erben
|
||||||
|
- gemeinsame Basisklasse zur Speicherung bleibt leer
|
||||||
|
- wird zum Tabellen erzeugen verwendet
|
||||||
|
- Benötigt
|
||||||
|
- Name der Tabelle
|
||||||
|
- PKs
|
||||||
|
- ggf. weitere Attribute
|
||||||
|
- Beispiel
|
||||||
|
- ```Python
|
||||||
|
class Base(DeclarativeBase):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class Verlag(Base):
|
||||||
|
__tablename__ = "alc_verlag"
|
||||||
|
|
||||||
|
name: Mapped[str] = mapped_column(String(50), primary_key=True)
|
||||||
|
adresse: Mapped[str] = mapped_column(String(50), nullable=True)
|
||||||
|
```
|
||||||
|
- **Deklaration der Attribute**
|
||||||
|
- ```Python
|
||||||
|
name: Mapped[str] = mapped_column(String(50), primary_key=True)
|
||||||
|
adresse: Mapped[str] = mapped_column(String(50), nullable=True)
|
||||||
|
isbn: Mapped[str] = mapped_column(String(13), primary_key=True)
|
||||||
|
auflage: Mapped[int] = mapped_column(primary_key=True)
|
||||||
|
erscheinungsjahr: Mapped[Date] = mapped_column(Date(), nullable=True)
|
||||||
|
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=False)
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Minimalbeispiel:
|
||||||
|
**Adressbuch mit einer Tabelle**
|
||||||
|
```Python
|
||||||
|
class Address(Base):
|
||||||
|
__tablename__ = "addressbook"
|
||||||
|
|
||||||
|
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
|
||||||
|
first_name: Mapped[str] = mapped_column(String(50))
|
||||||
|
last_name: Mapped[str] = mapped_column(String(50))
|
||||||
|
birthday: Mapped[Date] = mapped_column(Date())
|
||||||
|
|
||||||
|
def __init__(self, fname, lname, bday) -> None:
|
||||||
|
self.first_name = fname
|
||||||
|
self.last_name = lname
|
||||||
|
self.birthday = bday
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return f"{self.last_name}, {self.first_name} ({self.birthday})"
|
||||||
|
```
|
||||||
|
|
||||||
|
- Tabellen aller Entity Klassen werden durch OR Mapper erzeugt
|
||||||
|
- müssen/können nicht mehr manuell erzeugt werden
|
||||||
|
- Folgender Befehl erzeugt alle Tabellen, die von Base erben
|
||||||
|
- `Base.metadata.create_all(engine)`
|
||||||
|
|
||||||
|
- Arbeiten mit den Tabellen:
|
||||||
|
- ```Python
|
||||||
|
with Session(engine) as session:
|
||||||
|
session.add(Address("Charles", "Dickens", date(1812, 2, 7)))
|
||||||
|
session.add(Address("Edgar Allen", "Poe", date(1809, 1, 19)))
|
||||||
|
session.add(Address("Douglas", "Adams", date(1952, 3, 11)))
|
||||||
|
session.add(Address("Terry", "Pratchett", date(1948, 4, 28)))
|
||||||
|
session.add(Address("Albert", "Einstein", date(1879, 3, 14)))
|
||||||
|
session.add(Address("Marie", "Curie", date(1867, 11, 7)))
|
||||||
|
session.add(Address("Werner", "Heisenberg", date(1901, 12, 5)))
|
||||||
|
session.add(Address("Pierre", "Curie", date(1859, 3, 15)))
|
||||||
|
|
||||||
|
session.commit()
|
||||||
|
```
|
||||||
|
|
||||||
|
### Relationships
|
||||||
|
#### 1:N Relationships
|
||||||
|
```Python
|
||||||
|
class Parent(Base):
|
||||||
|
__tablename__ = "parent_table"
|
||||||
|
|
||||||
|
id: Mapped[int] = mapped_column(primary_key=True)
|
||||||
|
|
||||||
|
# Deklariert eine Liste von Child-Referenzen als Attribut, nicht jedoch als mapped column
|
||||||
|
# Dies hat *keine* Repräsentation in der Datenbank!
|
||||||
|
children: Mapped[list["Child"]] = relationship(back_populates="parent")
|
||||||
|
|
||||||
|
class Child(Base):
|
||||||
|
__tablename__ = "child_table"
|
||||||
|
|
||||||
|
id: Mapped[int] = mapped_column(primary_key=True)
|
||||||
|
|
||||||
|
# Deklariert einen Fremdschlüssel als mapped column. Dieser wird anwendungsseitig *nicht* verwendet!
|
||||||
|
# nullable=True kann gesetzt werden, falls 0..N gewünscht ist
|
||||||
|
parent_id: Mapped[int] = mapped_column(ForeignKey("parent_table.id"), nullable=True)
|
||||||
|
|
||||||
|
# Deklariert eine Parent-Referenz als Attribut, nicht jedoch als mapped column
|
||||||
|
parent: Mapped["Parent"] = relationship(back_populates="children")
|
||||||
|
```
|
||||||
|
- Attribute `parent` und `children` sind nur auf Anwendungsseite vorhanden
|
||||||
|
- `back_populates` sorgt dafür, dass Attribut der gegenüberliegenden Seite automatisch gefüllt wird
|
||||||
|
- Fremdschlüssel werden durch OR-Mapper automatisch gesetzt
|
||||||
|
|
||||||
|
#### 1:1 Relationships
|
||||||
|
- Gleiches wie bei 1:N
|
||||||
|
- `Mapped[list["Child"]]` wird zu `Mapped["Child"]`
|
||||||
|
|
||||||
|
|
||||||
|
#### M:N Relationships
|
||||||
|
- Zwischentabellen können nicht automatisch erzeugt werden
|
||||||
|
- Explizite Deklaration
|
||||||
|
- Parameter `secondary`
|
||||||
|
```Python
|
||||||
|
association_table = Table(
|
||||||
|
"association_table",
|
||||||
|
Base.metadata,
|
||||||
|
Column("left_id", ForeignKey("left_table.id"), primary_key=True),
|
||||||
|
Column("right_id", ForeignKey("right_table.id"), primary_key=True),
|
||||||
|
)
|
||||||
|
|
||||||
|
class Parent(Base):
|
||||||
|
__tablename__ = "left_table"
|
||||||
|
|
||||||
|
id: Mapped[int] = mapped_column(primary_key=True)
|
||||||
|
children: Mapped[list[Child]] = relationship(
|
||||||
|
secondary=association_table, back_populates="parents"
|
||||||
|
)
|
||||||
|
|
||||||
|
class Child(Base):
|
||||||
|
__tablename__ = "right_table"
|
||||||
|
|
||||||
|
id: Mapped[int] = mapped_column(primary_key=True)
|
||||||
|
parents: Mapped[list[Parent]] = relationship(
|
||||||
|
secondary=association_table, back_populates="children"
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Queries
|
||||||
|
- Da Ergebnisse jetzt Objekte sind, kein "raw sql" mehr
|
||||||
|
- Bspw:
|
||||||
|
- ```Python
|
||||||
|
verlag4 = session.get(Verlag, "Verlag 4")
|
||||||
|
print("Bücher von", verlag4.name)
|
||||||
|
books = session.scalars(select(Buch).where(Buch.erscheint_bei == verlag4))
|
||||||
|
for book in books:
|
||||||
|
print(book)
|
||||||
|
```
|
||||||
|
- Einfacher:
|
||||||
|
- ```Python
|
||||||
|
verlag4 = session.get(Verlag, "Verlag 4")
|
||||||
|
for book in verlag4.buecher:
|
||||||
|
print(book)
|
||||||
|
```
|
Loading…
x
Reference in New Issue
Block a user