Commits vergleichen
Keine gemeinsamen Commits. „main“ und „v1.0.0“ haben vollständig unterschiedliche Historien.
5 geänderte Dateien mit 9 neuen und 519 gelöschten Zeilen
|
|
@ -18,38 +18,6 @@ if (get_method() === 'GET' && empty($segments[1])) {
|
|||
json_ok(['decks' => $stmt->fetchAll()]);
|
||||
}
|
||||
|
||||
// GET /api/decks/export-anki — download all cards as Anki TSV
|
||||
if (get_method() === 'GET' && ($segments[1] ?? '') === 'export-anki') {
|
||||
$stmt = $pdo->query("
|
||||
SELECT d.name AS deck_name, c.question, c.answer, c.level
|
||||
FROM cards c JOIN decks d ON d.id = c.deck_id
|
||||
ORDER BY d.sort_order, d.name, c.sort_order
|
||||
");
|
||||
$cards = $stmt->fetchAll();
|
||||
|
||||
header('Content-Type: text/tab-separated-values; charset=utf-8');
|
||||
header('Content-Disposition: attachment; filename="sol2_training_anki_' . date('Y-m-d') . '.txt"');
|
||||
header('Cache-Control: no-cache');
|
||||
|
||||
// Clean markdown formatting for Anki: backticks -> <code>, ** -> <b>
|
||||
function anki_format($text) {
|
||||
$t = $text;
|
||||
$t = preg_replace('/```(\w*)\n(.*?)```/s', '<pre><code>$2</code></pre>', $t);
|
||||
$t = str_replace('`', '', $t);
|
||||
$t = preg_replace('/\*\*(.+?)\*\*/', '<b>$1</b>', $t);
|
||||
$t = str_replace("\n", '<br>', $t);
|
||||
$t = str_replace("\t", ' ', $t);
|
||||
return $t;
|
||||
}
|
||||
|
||||
foreach ($cards as $c) {
|
||||
$q = anki_format($c['question']);
|
||||
$a = anki_format($c['answer']);
|
||||
echo $q . "\t" . $a . "\t" . 'Sol2-Training::' . $c['deck_name'] . "\t" . $c['level'] . "\n";
|
||||
}
|
||||
exit;
|
||||
}
|
||||
|
||||
// GET /api/decks/{slug}
|
||||
if (get_method() === 'GET' && !empty($segments[1])) {
|
||||
$stmt = $pdo->prepare("SELECT * FROM decks WHERE slug = :s");
|
||||
|
|
|
|||
|
|
@ -1,256 +0,0 @@
|
|||
# Flashcard Deck: Datenbankdesign & Solution2-Patterns
|
||||
|
||||
**Thema**: Relationales Datenbankdesign mit echten Solution2-Beispielen
|
||||
**Schwierigkeit**: Anfaenger -> Fortgeschritten
|
||||
**Karten**: 20
|
||||
|
||||
---
|
||||
|
||||
## Karte 1 | Basis
|
||||
**Q:** Was ist ein Primary Key und welche Konvention verwendet Solution2?
|
||||
**A:** Ein Primary Key ist ein eindeutiger Bezeichner fuer jeden Datensatz. Solution2-Konvention: Der Primary Key heisst meistens wie die Tabelle + Suffix, z.B.:
|
||||
- Tabelle `orders` -> PK `orders` (INTEGER)
|
||||
- Tabelle `parts` -> PK `parts` (INTEGER)
|
||||
- Tabelle `company` -> PK `company` (INTEGER)
|
||||
Die Werte werden ueber Nummernkreise (Counter-Tabellen) vergeben, NICHT ueber PostgreSQL-Sequences.
|
||||
|
||||
---
|
||||
|
||||
## Karte 2 | Basis
|
||||
**Q:** Was ist ein Foreign Key? Nenne ein Beispiel aus Solution2.
|
||||
**A:** Ein Foreign Key verknuepft zwei Tabellen. In Solution2 z.B.:
|
||||
- `orderitems.orders` -> verweist auf `orders.orders` (Welchem Auftrag gehoert die Position?)
|
||||
- `orderitems.parts` -> verweist auf `parts.parts` (Welcher Artikel?)
|
||||
- `customerinvoices.company` -> verweist auf `company.company` (Welcher Kunde?)
|
||||
Der FK stellt sicher, dass nur existierende Auftraege/Artikel/Kunden referenziert werden koennen.
|
||||
|
||||
---
|
||||
|
||||
## Karte 3 | Basis
|
||||
**Q:** Was bedeutet Normalisierung und warum ist sie wichtig?
|
||||
**A:** Normalisierung vermeidet Datenredundanz. Beispiel aus Solution2:
|
||||
**Falsch (unnormalisiert):** In `orders` den Kundennamen direkt speichern -> bei Namensaenderung muessten ALLE Auftraege aktualisiert werden.
|
||||
**Richtig (normalisiert):** `orders.company` enthaelt nur die Company-ID -> der aktuelle Name wird aus `company` gelesen.
|
||||
Solution2 ist voll normalisiert: Stammdaten (Adressen, Artikel) werden an EINER Stelle gepflegt und ueberall referenziert.
|
||||
|
||||
---
|
||||
|
||||
## Karte 4 | Basis
|
||||
**Q:** Was ist der Unterschied zwischen `INTEGER`, `BIGINT`, `SERIAL` und `TEXT` in PostgreSQL?
|
||||
**A:** - `INTEGER`: 32-bit Ganzzahl (-2 Mrd bis +2 Mrd) — Standard fuer Solution2 IDs
|
||||
- `BIGINT`: 64-bit Ganzzahl — fuer sehr grosse Zaehler
|
||||
- `SERIAL`: Kein echter Datentyp! Kurzform fuer `INTEGER + automatische Sequence`
|
||||
- `TEXT`: Unbegrenzt langer String — in PostgreSQL genauso schnell wie VARCHAR
|
||||
- `BOOLEAN`: true/false — z.B. `valid`-Spalte in Solution2
|
||||
- `TIMESTAMP`: Datum + Uhrzeit — z.B. `created`, `modtime` in Solution2
|
||||
- `NUMERIC(p,s)`: Exakte Dezimalzahl — fuer Geldbetraege (z.B. Rechnungssummen)
|
||||
|
||||
---
|
||||
|
||||
## Karte 5 | Basis
|
||||
**Q:** Warum verwendet Solution2 `NUMERIC` statt `FLOAT` fuer Geldbetraege?
|
||||
**A:** `FLOAT` hat Rundungsfehler: `0.1 + 0.2 = 0.30000000000000004`. Bei Geldbetraegen ist das inakzeptabel — eine Rechnung ueber 100,00 Euro darf nicht als 99,99999 gespeichert werden.
|
||||
`NUMERIC(15,2)` speichert exakt 15 Stellen mit 2 Nachkommastellen. Solution2 nutzt dies fuer alle Betrags-Felder (Preise, Summen, Steuern). Die `orders`-Tabelle hat z.B. ueber 20 NUMERIC-Spalten fuer verschiedene Betraege.
|
||||
|
||||
---
|
||||
|
||||
## Karte 6 | Basis
|
||||
**Q:** Was ist ein INDEX und wann braucht man einen?
|
||||
**A:** Ein Index beschleunigt Abfragen — wie ein Stichwortverzeichnis in einem Buch. PostgreSQL muss nicht die ganze Tabelle durchsuchen.
|
||||
**Solution2-Beispiel:** Die `orders`-Tabelle hat ~100 Spalten und kann tausende Zeilen haben. Ohne Index auf `company` muesste PostgreSQL JEDEN Auftrag pruefen um die eines Kunden zu finden.
|
||||
```sql
|
||||
CREATE INDEX idx_orders_company ON orders(company);
|
||||
-- Jetzt: SELECT * FROM orders WHERE company = 42 ist schnell
|
||||
```
|
||||
Indizes kosten Speicher und verlangsamen INSERTs — nur dort anlegen wo oft gesucht wird.
|
||||
|
||||
---
|
||||
|
||||
## Karte 7 | Mittel
|
||||
**Q:** Wie sieht die Tabellenstruktur eines typischen Solution2-Geschaeftsprozesses aus?
|
||||
**A:** Am Beispiel Auftragsabwicklung:
|
||||
```
|
||||
orders (Auftragskopf)
|
||||
|-- orderitems (Auftragspositionen)
|
||||
|
|
||||
+-> deliverynotes (Lieferscheine)
|
||||
|-- deliverynoteitems
|
||||
|
|
||||
+-> customerinvoices (Rechnungen, 90 Spalten!)
|
||||
|-- customerinvoiceitems
|
||||
```
|
||||
Jede Ebene referenziert die vorherige per FK. Ein Auftrag kann mehrere Lieferscheine haben, ein Lieferschein kann zu einer Rechnung fuehren. Das ist die klassische **Auftrags-Lieferschein-Rechnung-Kette**.
|
||||
|
||||
---
|
||||
|
||||
## Karte 8 | Mittel
|
||||
**Q:** Warum hat die `orders`-Tabelle in Solution2 ueber 100 Spalten?
|
||||
**A:** Solution2 speichert am Auftragskopf alle relevanten Informationen zum Zeitpunkt der Erstellung:
|
||||
- Kundenreferenz, Lieferadresse, Rechnungsadresse
|
||||
- Zahlungsbedingungen, Waehrung, Wechselkurs
|
||||
- Steuerinfos (MwSt-Saetze, Steuer-IDs)
|
||||
- Rabatte, Zuschlaege, Frachtkosten
|
||||
- Status-Flags, Genehmigungsstatus, Prioritaet
|
||||
- Audit-Felder (created, modtime, revisor, valid)
|
||||
Das ist ein bewusstes Design: Zum Zeitpunkt der Rechnung muessen die Original-Konditionen verfuegbar sein, auch wenn sich Kundendaten seitdem geaendert haben.
|
||||
|
||||
---
|
||||
|
||||
## Karte 9 | Mittel
|
||||
**Q:** Was ist eine Nummernkreis-Tabelle und warum nutzt Solution2 diese statt PostgreSQL-Sequences?
|
||||
**A:** Solution2 hat eine Tabelle `nextnumber` die Zaehler fuer verschiedene Nummernkreise verwaltet:
|
||||
- Auftragsnummern, Rechnungsnummern, Artikelnummern etc.
|
||||
- Jeder Nummernkreis hat einen eigenen Eintrag mit dem naechsten freien Wert.
|
||||
**Vorteile gegenueber Sequences:** Lueckenlose Nummernfolgen (gesetzlich erforderlich bei Rechnungen!), flexible Formate (z.B. "RE-2026-0001"), verschiedene Nummernkreise pro Geschaeftsjahr.
|
||||
**Nachteil:** Muss in einer Transaktion gesperrt werden um Duplikate zu vermeiden.
|
||||
|
||||
---
|
||||
|
||||
## Karte 10 | Mittel
|
||||
**Q:** Was ist ein Composite Key? Gibt es Beispiele in Solution2?
|
||||
**A:** Ein Composite Key besteht aus mehreren Spalten die zusammen eindeutig sind. In Solution2 z.B.:
|
||||
- `orderitems`: Kombination aus `orders` (Auftrags-ID) + `position` (Positionsnummer)
|
||||
- `additionalfields`: Kombination aus `tablename` + `fieldname` (Zusatzfeld-Definition)
|
||||
- `inventoryquantities`: Kombination aus `parts` + `warehouse` (Bestand pro Artikel pro Lager)
|
||||
In PostgreSQL: `PRIMARY KEY (orders, position)` oder `UNIQUE (parts, warehouse)`.
|
||||
|
||||
---
|
||||
|
||||
## Karte 11 | Mittel
|
||||
**Q:** Was ist `EXPLAIN ANALYZE` und wie nutzt man es fuer Solution2?
|
||||
**A:** `EXPLAIN ANALYZE` zeigt den tatsaechlichen Ausfuehrungsplan einer Query:
|
||||
```sql
|
||||
EXPLAIN ANALYZE
|
||||
SELECT o.*, c.name1
|
||||
FROM orders o
|
||||
JOIN company c ON c.company = o.company
|
||||
WHERE o.valid = true AND o.status = 'A';
|
||||
```
|
||||
**Worauf achten:**
|
||||
- `Seq Scan` auf grosse Tabellen = Index fehlt!
|
||||
- `Nested Loop` bei vielen Zeilen = ineffizient, besser Hash/Merge Join
|
||||
- `actual time` vs `rows` = Verhaeltnis zeigt Effizienz
|
||||
Typischer Fix: `CREATE INDEX idx_orders_status ON orders(status) WHERE valid = true;`
|
||||
|
||||
---
|
||||
|
||||
## Karte 12 | Mittel
|
||||
**Q:** Was ist ein Partial Index und wann ist er in Solution2 sinnvoll?
|
||||
**A:** Ein Partial Index indexiert nur einen Teil der Tabelle:
|
||||
```sql
|
||||
CREATE INDEX idx_orders_active ON orders(company, status)
|
||||
WHERE valid = true;
|
||||
```
|
||||
Da Solution2 Soft-Delete nutzt (`valid = false`), sind oft 80%+ der Datensaetze "geloescht" aber noch in der Tabelle. Ein Partial Index auf `WHERE valid = true` ist viel kleiner und schneller als ein voller Index. **Best Practice:** Fuer alle haeufigen Queries auf aktive Datensaetze Partial Indices verwenden.
|
||||
|
||||
---
|
||||
|
||||
## Karte 13 | Mittel
|
||||
**Q:** Was sind die wichtigsten PostgreSQL-Datentypen die Solution2 NICHT standardmaessig nutzt, die aber nuetzlich waeren?
|
||||
**A:** - `JSONB`: Strukturierte Daten ohne festes Schema — ideal fuer Konfigurationen, API-Responses. Kann indexiert werden!
|
||||
- `ARRAY`: Mehrere Werte in einer Spalte — z.B. Tags, Kategorie-Listen
|
||||
- `UUID`: Universell eindeutige IDs — besser als INTEGER fuer verteilte Systeme
|
||||
- `TSTZRANGE`: Zeitbereiche — z.B. Gueltigkeitszeitraeume fuer Preise
|
||||
- `INET/CIDR`: IP-Adressen — fuer Audit-Logs
|
||||
Solution2 ist historisch gewachsen (FrontBase-Herkunft) und nutzt hauptsaechlich INTEGER, TEXT, NUMERIC, BOOLEAN, TIMESTAMP.
|
||||
|
||||
---
|
||||
|
||||
## Karte 14 | Fortgeschritten
|
||||
**Q:** Was ist ein Deadlock und wie vermeidet man ihn in Solution2?
|
||||
**A:** Ein Deadlock entsteht wenn zwei Transaktionen gegenseitig auf Ressourcen warten:
|
||||
- Transaktion A sperrt Zeile 1, will Zeile 2
|
||||
- Transaktion B sperrt Zeile 2, will Zeile 1
|
||||
-> Beide warten ewig. PostgreSQL erkennt das und bricht EINE Transaktion ab (Error 40P01).
|
||||
**Vermeidung in Solution2:**
|
||||
- Immer in der gleichen Reihenfolge sperren (z.B. erst Auftragskopf, dann Positionen)
|
||||
- Transaktionen so kurz wie moeglich halten
|
||||
- SELECT FOR UPDATE nur wenn noetig
|
||||
- Bei Error 40P01: Transaktion wiederholen (Retry-Pattern)
|
||||
|
||||
---
|
||||
|
||||
## Karte 15 | Fortgeschritten
|
||||
**Q:** Was bedeutet `ON DELETE CASCADE` und wo ist es in Solution2 gefaehrlich?
|
||||
**A:** `ON DELETE CASCADE` loescht automatisch abhaengige Datensaetze wenn der Parent geloescht wird. Z.B. `orderitems.orders REFERENCES orders(orders) ON DELETE CASCADE` wuerde alle Positionen loeschen wenn ein Auftrag geloescht wird.
|
||||
**In Solution2 NICHT verwenden!** Weil:
|
||||
1. Solution2 nutzt Soft-Delete (`valid = false`) statt echtem DELETE
|
||||
2. Historische Daten muessen erhalten bleiben (Revisionssicherheit)
|
||||
3. CASCADE kann unbeabsichtigt riesige Datenverluste verursachen
|
||||
Solution2 nutzt stattdessen `ON DELETE RESTRICT` (Standard) — Loeschen wird verhindert wenn abhaengige Daten existieren.
|
||||
|
||||
---
|
||||
|
||||
## Karte 16 | Fortgeschritten
|
||||
**Q:** Wie funktioniert `pg_dump` fuer Solution2-Backups?
|
||||
**A:** ```bash
|
||||
# Vollstaendiges Backup (Custom-Format, komprimiert)
|
||||
pg_dump -U postgres -Fc -Z9 -d masterdemo -f backup.dump
|
||||
|
||||
# Nur Schema (ohne Daten)
|
||||
pg_dump -U postgres -s -d masterdemo -f schema.sql
|
||||
|
||||
# Nur eine Tabelle
|
||||
pg_dump -U postgres -t orders -d masterdemo -f orders.sql
|
||||
|
||||
# Wiederherstellen
|
||||
pg_restore -U postgres -d masterdemo -c backup.dump
|
||||
```
|
||||
**Fuer Docker:** `docker exec postgres-local pg_dump -U postgres -Fc -d masterdemo > backup.dump`
|
||||
Automatisierte Backups laufen auf dem Pi taeglich um 02:30 Uhr.
|
||||
|
||||
---
|
||||
|
||||
## Karte 17 | Fortgeschritten
|
||||
**Q:** Was ist der Unterschied zwischen `VACUUM` und `VACUUM FULL` in PostgreSQL?
|
||||
**A:** - `VACUUM`: Markiert geloeschte Zeilen als wiederverwendbar. Blockiert KEINE Tabellen, laeuft im Hintergrund (Autovacuum).
|
||||
- `VACUUM FULL`: Schreibt die gesamte Tabelle physisch neu, gibt Speicher ans OS zurueck. **Sperrt die Tabelle exklusiv!**
|
||||
**Fuer Solution2:** Autovacuum reicht normalerweise. `VACUUM FULL` nur nach massiven Loeschaktionen (z.B. nach Bereinigung alter Bewegungsdaten). Nie waehrend Geschaeftszeiten ausfuehren!
|
||||
|
||||
---
|
||||
|
||||
## Karte 18 | Fortgeschritten
|
||||
**Q:** Was sind CTEs (Common Table Expressions) und wie nutzt man sie?
|
||||
**A:** CTEs sind "benannte Subqueries" — machen komplexe Queries lesbar:
|
||||
```sql
|
||||
-- Alle Kunden mit offenem Auftragsvolumen > 10.000 EUR
|
||||
WITH offene_auftraege AS (
|
||||
SELECT company, SUM(totalamount) AS gesamt
|
||||
FROM orders
|
||||
WHERE valid = true AND status IN ('A', 'B')
|
||||
GROUP BY company
|
||||
)
|
||||
SELECT c.name1, oa.gesamt
|
||||
FROM offene_auftraege oa
|
||||
JOIN company c ON c.company = oa.company
|
||||
WHERE oa.gesamt > 10000
|
||||
ORDER BY oa.gesamt DESC;
|
||||
```
|
||||
CTEs koennen auch rekursiv sein — z.B. fuer Stuecklisten-Aufloesung (BOM explosion).
|
||||
|
||||
---
|
||||
|
||||
## Karte 19 | Fortgeschritten
|
||||
**Q:** Was sind Window Functions und wozu braucht man sie in Solution2?
|
||||
**A:** Window Functions berechnen Werte ueber eine Gruppe von Zeilen OHNE sie zu gruppieren:
|
||||
```sql
|
||||
-- Kumulierte Umsaetze pro Kunde, nach Datum sortiert
|
||||
SELECT company, orderdate, totalamount,
|
||||
SUM(totalamount) OVER (PARTITION BY company ORDER BY orderdate) AS kumuliert,
|
||||
ROW_NUMBER() OVER (PARTITION BY company ORDER BY orderdate DESC) AS rang
|
||||
FROM orders
|
||||
WHERE valid = true;
|
||||
```
|
||||
**Typische Anwendungen:** Ranglisten, laufende Summen, Vergleich mit Vorperiode, Top-N pro Gruppe. Viel effizienter als Subqueries!
|
||||
|
||||
---
|
||||
|
||||
## Karte 20 | Fortgeschritten
|
||||
**Q:** Was muss man bei der Migration von FrontBase nach PostgreSQL beachten?
|
||||
**A:** Solution2 wurde urspruenglich fuer FrontBase entwickelt. Wichtige Unterschiede:
|
||||
- **Case Sensitivity:** PostgreSQL ist case-sensitive bei Spaltennamen in Anfuehrungszeichen (`"Name"` != `name`)
|
||||
- **Boolean:** FrontBase nutzt INTEGER (0/1), PostgreSQL hat echtes BOOLEAN
|
||||
- **BLOBs:** Verschiedene Speichermechanismen (FrontBase Inline vs PostgreSQL TOAST)
|
||||
- **Transaktionen:** FrontBase hat andere Isolation-Level-Defaults
|
||||
- **Sequences:** FrontBase hatte eigenes Counter-System, PostgreSQL hat native Sequences
|
||||
Die Migration nutzt ein spezielles Toolset (`sol2-migration`) das Datentyp-Konvertierung und BLOB-Transfer automatisiert.
|
||||
|
|
@ -1,217 +0,0 @@
|
|||
# Flashcard Deck: PostgreSQL Grundlagen & Konfiguration
|
||||
|
||||
**Thema**: PostgreSQL-spezifisches Wissen fuer Solution2-Entwickler
|
||||
**Schwierigkeit**: Anfaenger -> Fortgeschritten
|
||||
**Karten**: 20
|
||||
|
||||
---
|
||||
|
||||
## Karte 1 | Basis
|
||||
**Q:** Welcher DAM (Data Access Module) wird fuer PostgreSQL in Omnis verwendet?
|
||||
**A:** `PGSQLDAM` (auch als `PostgresDAM` in der INI-Datei). Omnis laedt die Bibliothek `dampgsql.so` (Linux) bzw. `dampgsql.dll` (Windows), die wiederum `libpq` (die offizielle PostgreSQL-Client-Bibliothek) nutzt.
|
||||
|
||||
---
|
||||
|
||||
## Karte 2 | Basis
|
||||
**Q:** Was ist `libpq` und warum ist es fuer Solution2 wichtig?
|
||||
**A:** `libpq` ist die offizielle C-Client-Bibliothek von PostgreSQL. Omnis' `dampgsql.so` nutzt `dlopen()` um libpq zur Laufzeit zu laden. Ohne libpq crasht Omnis beim Datenbankzugriff mit "double free or corruption" (SIGABRT) oder "general protection fault" (SIGSEGV). In Docker muss ein Symlink erstellt werden: `ln -s /usr/lib/x86_64-linux-gnu/libpq.so.5 /opt/omnis/libpq.so`
|
||||
|
||||
---
|
||||
|
||||
## Karte 3 | Basis
|
||||
**Q:** Wie sieht die PostgreSQL-Verbindungskonfiguration in der Solution2-INI-Datei aus?
|
||||
**A:** ```
|
||||
[SQL]
|
||||
Database=PostgreSQL
|
||||
DatabaseName=masterdemo
|
||||
HostIP=postgres
|
||||
HostPort=5432
|
||||
UserName=soluser
|
||||
DAM=PostgresDAM
|
||||
Schema=soluser
|
||||
```
|
||||
**ACHTUNG:** Es muss `HostPort` heissen, NICHT `Port`! Omnis liest `$getSectionParameter('SQL','HostPort')`.
|
||||
|
||||
---
|
||||
|
||||
## Karte 4 | Basis
|
||||
**Q:** Welche Standard-Rollen gibt es in einer Solution2 PostgreSQL-Datenbank?
|
||||
**A:** - `sol2_system`: Schema-Owner, erstellt Tabellen und Objekte
|
||||
- `soluser`: Anwendungsbenutzer mit CRUD-Rechten, wird von Omnis verwendet
|
||||
- Optional: `kunde_readonly`: Lesezugriff fuer Reporting/Monitoring.
|
||||
Rechte werden ueber `ALTER DEFAULT PRIVILEGES` automatisch vergeben: `ALTER DEFAULT PRIVILEGES FOR ROLE sol2_system GRANT ALL ON TABLES TO soluser;`
|
||||
|
||||
---
|
||||
|
||||
## Karte 5 | Basis
|
||||
**Q:** Was ist der Unterschied zwischen `$logon()` und `$execdirect()` in Omnis?
|
||||
**A:** - `$logon()` erstellt eine **persistente Datenbank-Session** mit voller Transaktionskontrolle ($begin/$commit/$rollback)
|
||||
- `$execdirect()` fuehrt ein **einzelnes SQL-Statement** direkt aus, mit Auto-Commit.
|
||||
Fuer alles was zusammenhaengt (z.B. Auftrag + Positionen anlegen) MUSS eine Session mit expliziter Transaktion verwendet werden.
|
||||
|
||||
---
|
||||
|
||||
## Karte 6 | Basis
|
||||
**Q:** Wie funktioniert eine Transaktion in Omnis/Solution2?
|
||||
**A:** ```omnis
|
||||
Do lSess.$begin() Returns #F ;; START TRANSACTION
|
||||
Do lStmt.$execdirect(lSQL1) Returns #F
|
||||
Do lStmt.$execdirect(lSQL2) Returns #F
|
||||
If lAllesOK
|
||||
Do lSess.$commit() Returns #F ;; COMMIT
|
||||
Else
|
||||
Do lSess.$rollback() Returns #F ;; ROLLBACK
|
||||
End If
|
||||
```
|
||||
Immer `#F` (flag) pruefen! Bei Fehler sofort `$rollback()`.
|
||||
|
||||
---
|
||||
|
||||
## Karte 7 | Mittel
|
||||
**Q:** Was sind die wichtigsten PostgreSQL-Tuning-Parameter fuer Solution2?
|
||||
**A:** - `shared_buffers` = 25% RAM (Cache fuer Tabellendaten)
|
||||
- `effective_cache_size` = 75% RAM (Planer-Hint fuer OS-Cache)
|
||||
- `work_mem` = RAM / (max_connections * 4) (pro Query-Sortierung)
|
||||
- `maintenance_work_mem` = RAM / 32 (fuer VACUUM, CREATE INDEX)
|
||||
- `max_connections` = 100 (Standard, reicht fuer Omnis + n8n + pgAdmin)
|
||||
- `log_min_duration_statement` = 250ms (Slow-Query-Logging)
|
||||
|
||||
---
|
||||
|
||||
## Karte 8 | Mittel
|
||||
**Q:** Was passiert wenn in PostgreSQL ein `SELECT` ohne Transaktion laeuft?
|
||||
**A:** PostgreSQL wrapt jedes einzelne Statement automatisch in eine Transaktion (Auto-Commit). Das bedeutet: Ein einzelnes `SELECT` sieht immer einen konsistenten Snapshot. Aber wenn du ZWEI aufeinanderfolgende SELECTs ohne explizite Transaktion machst, koennen sie unterschiedliche Daten sehen, weil dazwischen ein anderer Benutzer etwas geaendert haben kann.
|
||||
|
||||
---
|
||||
|
||||
## Karte 9 | Mittel
|
||||
**Q:** Was sind die PostgreSQL Isolation Levels und welcher wird in Solution2 typisch verwendet?
|
||||
**A:** - `READ COMMITTED` (Standard in PostgreSQL und Solution2): Jedes Statement sieht nur Daten die VOR dem Statement committed wurden
|
||||
- `REPEATABLE READ`: Die gesamte Transaktion sieht den Snapshot vom Transaktionsbeginn
|
||||
- `SERIALIZABLE`: Strengste Isolation, verhindert Phantom Reads.
|
||||
Solution2 nutzt den Standard `READ COMMITTED`. Das reicht fuer die meisten ERP-Operationen.
|
||||
|
||||
---
|
||||
|
||||
## Karte 10 | Mittel
|
||||
**Q:** Was ist der Unterschied zwischen einer PostgreSQL `SEQUENCE` und einem Auto-Increment?
|
||||
**A:** In PostgreSQL gibt es kein echtes "Auto-Increment". Stattdessen:
|
||||
- `SERIAL` / `BIGSERIAL`: Erstellt automatisch eine Sequence und setzt `DEFAULT nextval('seq_name')`
|
||||
- `GENERATED ALWAYS AS IDENTITY`: Moderner Standard (PostgreSQL 10+), verhindert manuelle Werte
|
||||
- `SEQUENCE`: Eigenstaendiges Datenbankobjekt, kann von mehreren Tabellen geteilt werden.
|
||||
Solution2 nutzt haeufig eigene Counter-Tabellen statt Sequences, um Nummernkreise flexibler zu steuern.
|
||||
|
||||
---
|
||||
|
||||
## Karte 11 | Mittel
|
||||
**Q:** Wie funktioniert Soft-Delete in Solution2 und warum wird es verwendet?
|
||||
**A:** Jede Solution2-Tabelle hat eine Spalte `valid` (BOOLEAN). Statt `DELETE FROM parts WHERE id = 42` wird `UPDATE parts SET valid = false WHERE id = 42` verwendet. Gruende:
|
||||
- Referenzielle Integritaet bleibt erhalten (Foreign Keys zeigen noch auf den Datensatz)
|
||||
- Historische Daten bleiben fuer Reports verfuegbar
|
||||
- Versehentliches Loeschen ist reversibel.
|
||||
**WICHTIG:** Bei SELECTs immer `WHERE valid = true` filtern!
|
||||
|
||||
---
|
||||
|
||||
## Karte 12 | Mittel
|
||||
**Q:** Welche Audit-Spalten hat jede Solution2-Tabelle?
|
||||
**A:** - `created` (TIMESTAMP): Wann wurde der Datensatz erstellt
|
||||
- `modtime` (TIMESTAMP): Letzte Aenderung
|
||||
- `revisor` (INTEGER): ID des Benutzers der zuletzt geaendert hat
|
||||
- `valid` (BOOLEAN): Soft-Delete Flag (true = aktiv, false = geloescht)
|
||||
Diese Spalten werden automatisch von den T_Super-Methoden befuellt. Nie manuell setzen!
|
||||
|
||||
---
|
||||
|
||||
## Karte 13 | Fortgeschritten
|
||||
**Q:** Warum darf man Stammdaten (Artikel, Mitarbeiter, Adressen) NICHT per SQL-INSERT anlegen?
|
||||
**A:** Solution2 erstellt beim Anlegen automatisch abhaengige Datensaetze:
|
||||
- **Artikel**: Versionseintraege, Lifecycle, Lagerzuordnungen, Zusatzfeld-Defaults, Audit-Trail, Stuecklistenstrukturen
|
||||
- **Mitarbeiter**: Gruppenzuordnungen, Berechtigungen, Passwort-Records, Personaldaten
|
||||
- **Adressen**: Zahlungsbedingungen, Kommunikationseintraege, Rollenzuordnungen.
|
||||
Ein SQL-INSERT erzeugt nur den Hauptdatensatz — die abhaengigen Daten fehlen, was zu Fehlern und inkonsistenten Zustaenden fuehrt. Richtig: Ueber Solution2-UI oder rtDataImport-Service.
|
||||
|
||||
---
|
||||
|
||||
## Karte 14 | Fortgeschritten
|
||||
**Q:** Was ist `T_Super` in Solution2?
|
||||
**A:** `T_Super` ist die **Basis-Tabellenklasse** von der alle Tabellen-Klassen erben. Sie stellt gemeinsame Methoden bereit:
|
||||
- `$select()`: Daten laden mit automatischer Schema/WHERE-Behandlung
|
||||
- `$insert()`: Neuen Datensatz anlegen (setzt created, modtime, revisor automatisch)
|
||||
- `$update()`: Datensatz aendern (aktualisiert modtime und revisor)
|
||||
- `$delete()`: Soft-Delete (setzt valid = false).
|
||||
Es gibt auch `T_SuperVersion` fuer versionierte Tabellen (z.B. Artikelstaemme mit Aenderungshistorie).
|
||||
|
||||
---
|
||||
|
||||
## Karte 15 | Fortgeschritten
|
||||
**Q:** Was darf man per SQL aendern und was nicht?
|
||||
**A:** **Erlaubt (UPDATE):**
|
||||
- Enterprise-Daten (Firmenstamm), SystemKey, DBStatus
|
||||
- Nummernkreise zuruecksetzen (UPDATE nextnumber)
|
||||
- Konfigurationstabellen (Warengruppen, Zusatzfelder)
|
||||
- Passwort-Reset
|
||||
**Erlaubt (DELETE/TRUNCATE):**
|
||||
- Bewegungsdaten (Auftraege, Rechnungen) wenn sicher
|
||||
**VERBOTEN (INSERT):**
|
||||
- Artikel, Mitarbeiter, Adressen, Dokumente — immer ueber Solution2 UI oder Services!
|
||||
|
||||
---
|
||||
|
||||
## Karte 16 | Fortgeschritten
|
||||
**Q:** Wie behandelt Solution2 BLOBs (Binary Large Objects) in PostgreSQL?
|
||||
**A:** BLOBs werden in der Tabelle `documentcontent` gespeichert (z.B. angehaengte PDFs, Bilder). Bei der Migration von FrontBase nach PostgreSQL gibt es zwei Strategien:
|
||||
- **Phase-1 (Inline)**: BLOBs direkt mit den Daten importieren — einfach, aber langsam bei grossen DBs (> 2GB)
|
||||
- **Phase-2 (Separat)**: Erst Struktur + Daten, dann BLOBs nachtraeglich importieren — empfohlen fuer Produktion.
|
||||
PostgreSQL speichert grosse BLOBs via TOAST (The Oversized Attribute Storage Technique) automatisch komprimiert.
|
||||
|
||||
---
|
||||
|
||||
## Karte 17 | Fortgeschritten
|
||||
**Q:** Was ist TOAST in PostgreSQL?
|
||||
**A:** TOAST (The Oversized Attribute Storage Technique) ist PostgreSQLs Mechanismus fuer grosse Feldwerte. Wenn ein Wert groesser als ca. 2kB ist, wird er automatisch:
|
||||
1. Erst komprimiert (LZ-Kompression)
|
||||
2. Wenn immer noch zu gross: In eine separate TOAST-Tabelle ausgelagert.
|
||||
Das passiert transparent — fuer die Anwendung sieht es aus wie ein normales Feld. Relevant fuer Solution2s `documentcontent`-Tabelle mit grossen Anhaengen.
|
||||
|
||||
---
|
||||
|
||||
## Karte 18 | Fortgeschritten
|
||||
**Q:** Wie funktioniert die Fehlerbehandlung bei SQL-Operationen in Omnis?
|
||||
**A:** ```omnis
|
||||
Do lStmt.$execute(lParam) Returns #F
|
||||
If not(flag true)
|
||||
Calculate lCode as lSess.$lasterror
|
||||
Calculate lText as lSess.$lasterrortext
|
||||
OK message {SQL-Fehler [lCode]: [lText]}
|
||||
Do lSess.$rollback() Returns #F
|
||||
End If
|
||||
```
|
||||
**Wichtig:** `#F` (flag) wird nach JEDER SQL-Operation gesetzt. `$lasterror` liefert den PostgreSQL-Fehlercode, `$lasterrortext` die lesbare Meldung. Bei Deadlocks (error code 40P01) kann ein Retry sinnvoll sein.
|
||||
|
||||
---
|
||||
|
||||
## Karte 19 | Fortgeschritten
|
||||
**Q:** Was muss man bei Docker-Deployments fuer die PostgreSQL-Verbindung beachten?
|
||||
**A:** - `HostIP` in der INI muss der **Docker-Container-Name** sein (z.B. `postgres`), NICHT `localhost` oder `127.0.0.1`
|
||||
- Alle Container muessen im gleichen Docker-Network sein (z.B. `senex-net`)
|
||||
- `libpq.so` muss als Symlink im Omnis-Home-Verzeichnis existieren
|
||||
- `pg_hba.conf` muss Verbindungen vom Docker-Netzwerk erlauben: `host all all 172.16.0.0/12 scram-sha-256`
|
||||
- Session-Timeouts beachten: PostgreSQL schliesst idle Connections nach `idle_in_transaction_session_timeout`
|
||||
|
||||
---
|
||||
|
||||
## Karte 20 | Fortgeschritten
|
||||
**Q:** Wie prueft man in PostgreSQL langsame Queries und was bedeutet das fuer Solution2?
|
||||
**A:** ```sql
|
||||
-- Slow Query Log aktivieren (postgresql.conf)
|
||||
log_min_duration_statement = 250 -- Alles > 250ms loggen
|
||||
|
||||
-- Ausfuehrungsplan anzeigen
|
||||
EXPLAIN ANALYZE SELECT * FROM orders WHERE valid = true AND status = 'A';
|
||||
|
||||
-- Laufende Queries anzeigen
|
||||
SELECT pid, now() - pg_stat_activity.query_start AS duration, query
|
||||
FROM pg_stat_activity WHERE state = 'active';
|
||||
```
|
||||
**Typische Solution2-Bottlenecks:** Fehlende Indizes auf `valid`-Spalte, grosse JOINs ueber orders/orderitems ohne Einschraenkung, LIKE-Suchen ohne Index.
|
||||
|
|
@ -26,13 +26,10 @@ var Flashcards = {
|
|||
var total = decks.reduce(function(s, d) { return s + (d.card_count || 0); }, 0);
|
||||
var totalDue = decks.reduce(function(s, d) { return s + (parseInt(d.due) || 0); }, 0);
|
||||
|
||||
var h = '<div class="page-header" style="display:flex;justify-content:space-between;align-items:start;">';
|
||||
h += '<div><h1>Flashcards</h1>';
|
||||
var h = '<div class="page-header"><h1>Flashcards</h1>';
|
||||
h += '<p>' + total + ' Karten in ' + decks.length + ' Decks';
|
||||
if (totalDue > 0) h += ' — <strong style="color:var(--orange)">' + totalDue + ' faellig</strong>';
|
||||
h += '</p></div>';
|
||||
h += '<a href="/api/decks/export-anki" class="btn btn-small" style="white-space:nowrap;margin-top:4px;" download>Anki-Export</a>';
|
||||
h += '</div><div class="deck-grid">';
|
||||
h += '</p></div><div class="deck-grid">';
|
||||
|
||||
decks.forEach(function(d) {
|
||||
var reviewed = parseInt(d.reviewed) || 0;
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
# Deploy edu.senex.de to Pi
|
||||
set -e
|
||||
|
||||
PI_HOST="pi"
|
||||
PI_HOST="hafroes@192.168.10.65"
|
||||
PI_PATH="/mnt/nas-services/webapps/sites/edu"
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
LOCAL_EDU="$SCRIPT_DIR/../edu"
|
||||
|
|
@ -10,16 +10,14 @@ LOCAL_EDU="$SCRIPT_DIR/../edu"
|
|||
echo "=== Deploying edu.senex.de ==="
|
||||
|
||||
echo "Creating directories on Pi..."
|
||||
ssh $PI_HOST "sudo mkdir -p $PI_PATH/{css,js,api,content/flashcards,content/tutorials,content/cheatsheets} && sudo chown -R hafroes:hafroes $PI_PATH"
|
||||
ssh $PI_HOST "mkdir -p $PI_PATH/{css,js,api,content/flashcards,content/tutorials,content/cheatsheets}"
|
||||
|
||||
echo "Uploading files..."
|
||||
scp "$LOCAL_EDU/index.html" "$PI_HOST:$PI_PATH/"
|
||||
scp "$LOCAL_EDU/css/"* "$PI_HOST:$PI_PATH/css/"
|
||||
scp "$LOCAL_EDU/js/"* "$PI_HOST:$PI_PATH/js/"
|
||||
scp "$LOCAL_EDU/api/"* "$PI_HOST:$PI_PATH/api/"
|
||||
scp "$LOCAL_EDU/content/flashcards/"* "$PI_HOST:$PI_PATH/content/flashcards/" 2>/dev/null || true
|
||||
scp "$LOCAL_EDU/content/tutorials/"* "$PI_HOST:$PI_PATH/content/tutorials/" 2>/dev/null || true
|
||||
scp "$LOCAL_EDU/content/cheatsheets/"* "$PI_HOST:$PI_PATH/content/cheatsheets/" 2>/dev/null || true
|
||||
scp -r "$LOCAL_EDU/index.html" "$PI_HOST:$PI_PATH/"
|
||||
scp -r "$LOCAL_EDU/css/"* "$PI_HOST:$PI_PATH/css/"
|
||||
scp -r "$LOCAL_EDU/js/"* "$PI_HOST:$PI_PATH/js/"
|
||||
scp -r "$LOCAL_EDU/api/"* "$PI_HOST:$PI_PATH/api/"
|
||||
scp -r "$LOCAL_EDU/content/"* "$PI_HOST:$PI_PATH/content/" 2>/dev/null || true
|
||||
|
||||
echo "Restarting PHP-FPM (opcache)..."
|
||||
ssh $PI_HOST "docker restart php-fpm"
|
||||
|
|
|
|||
Laden …
Tabelle hinzufügen
Einen Link hinzufügen
In neuem Issue referenzieren