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="02_semantischeDatenmodellierung.md"/>
|
||||
<toc-element topic="03_sql.md"/>
|
||||
<toc-element topic="04_anwendungsentwicklung.md"/>
|
||||
|
||||
</toc-element>
|
||||
<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