Zum Hauptinhalt springen

SQL — Daten abfragen und schreiben

SQL (Structured Query Language) ist die Sprache für den Umgang mit relationalen Datenbanken. In 42°OS begegnet dir SQL an zwei Stellen: beim Zugriff auf externe Datenbanken über den Database Agent und beim Zugriff auf den plattformeigenen Internal Storage.


Wann SQL in 42°OS?

Viele Unternehmen betreiben ERP- oder CRM-Systeme ohne nutzbare API — oder die verfügbare API wurde nicht lizenziert. In diesen Fällen ist der direkte Datenbankzugriff per SQL oft der pragmatischste Weg, um Daten zu lesen oder zu schreiben.

Darüber hinaus nutzt du SQL im Internal Storage für alles, was persistiert werden muss: Zustandsdaten einer State Machine, Mapping-Tabellen, Zwischenergebnisse, Gedächtnis-Einträge.


Die vier Grundoperationen

SELECT — Daten lesen

SELECT spalte1, spalte2
FROM tabellenname
WHERE bedingung
ORDER BY spalte1 DESC
LIMIT 100

SELECT * liest alle Spalten — praktisch zum Erkunden, aber in Produktiv-Workflows möglichst durch explizite Spaltennamen ersetzen.

-- Alle aktiven Kunden, neueste zuerst
SELECT kundennummer, name, email
FROM kunden
WHERE status = 'aktiv'
ORDER BY erstellt_am DESC
LIMIT 50

INSERT — Daten einfügen

INSERT INTO tabellenname (spalte1, spalte2, spalte3)
VALUES ('wert1', 'wert2', 'wert3')
INSERT INTO verarbeitete_dokumente (dokument_id, typ, lieferant, erstellt_am)
VALUES ('{{ dokument_id }}', '{{ typ }}', '{{ lieferant }}', datetime('now'))

UPDATE — Daten aktualisieren

UPDATE tabellenname
SET spalte1 = 'neuer_wert', spalte2 = 'anderer_wert'
WHERE bedingung
UPDATE bestellungen
SET status = 'verarbeitet', aktualisiert_am = datetime('now')
WHERE bestell_id = '{{ bestell_id }}'
Immer eine WHERE-Bedingung bei UPDATE

Ein UPDATE ohne WHERE-Bedingung aktualisiert alle Zeilen in der Tabelle. Im Zweifel lieber einmal zu viel prüfen.

DELETE — Daten löschen

DELETE FROM tabellenname
WHERE bedingung

Auch hier gilt: ohne WHERE-Bedingung werden alle Zeilen gelöscht.


Filtern und Vergleichen

-- Gleichheit
WHERE status = 'aktiv'

-- Ungleichheit
WHERE status != 'gelöscht'

-- Zahlenvergleiche
WHERE betrag > 1000
WHERE betrag BETWEEN 500 AND 2000

-- Text-Suche (Groß-/Kleinschreibung beachten)
WHERE name LIKE '%Muster%' -- enthält "Muster"
WHERE name LIKE 'Muster%' -- beginnt mit "Muster"

-- NULL-Prüfung
WHERE kundennummer IS NULL
WHERE kundennummer IS NOT NULL

-- Mehrere Werte
WHERE status IN ('neu', 'offen', 'in_bearbeitung')

-- Kombinieren
WHERE status = 'aktiv' AND betrag > 500
WHERE status = 'neu' OR status = 'offen'

Aggregieren und Gruppieren

SELECT
abteilung,
COUNT(*) AS anzahl_mitarbeiter,
AVG(gehalt) AS durchschnittsgehalt,
MAX(eintrittsdatum) AS letzter_eintritt
FROM mitarbeiter
WHERE status = 'aktiv'
GROUP BY abteilung
ORDER BY anzahl_mitarbeiter DESC

Häufige Aggregatfunktionen:

FunktionBedeutung
COUNT(*)Anzahl Zeilen
COUNT(spalte)Anzahl nicht-NULL-Werte
SUM(spalte)Summe
AVG(spalte)Durchschnitt
MIN(spalte)Kleinster Wert
MAX(spalte)Größter Wert

Tabellen verbinden: JOIN

SELECT
b.bestell_id,
b.datum,
k.name AS kundenname,
k.email
FROM bestellungen b
JOIN kunden k ON b.kunden_id = k.id
WHERE b.status = 'offen'
JOIN-TypBedeutung
JOIN / INNER JOINNur Zeilen die in beiden Tabellen vorkommen
LEFT JOINAlle Zeilen links, rechts NULL wenn kein Treffer
RIGHT JOINAlle Zeilen rechts, links NULL wenn kein Treffer

SQL in 42°OS: die zwei Wege

Internal Storage Agent — plattformintern

Für den plattformeigenen SQLite-Speicher. Kein externer Datenbankserver nötig, keine Credentials. SQL-Statement direkt in der Agent-Konfiguration eingeben, Liquid Templating für dynamische Werte:

SELECT dokument_id, typ, lieferant
FROM verarbeitete_dokumente
WHERE dokument_id = '{{ dokument_id }}'

Detailliert beschrieben im Kapitel Zustandsnormalisierung durch SQL-Persistenz.

Database Agent — externe Datenbanken

Für den Zugriff auf PostgreSQL, MySQL und Microsoft SQL Server. Credentials werden im Credential-Store der Plattform hinterlegt — Passwörter erscheinen nie im Klartext in der Agent-Konfiguration:

{
"host": "db.unternehmen.intern",
"database": "erp_produktion",
"username": "42os_readonly",
"password": "..."
}

Der Credential-Eintrag wird im Agent nur per Name referenziert:

db_credentials: erp_produktion_credentials
db_type: postgres

Das SQL-Statement selbst funktioniert identisch — inkl. Liquid Templating. Die Ausgabe landet standardmäßig als JSON-Array unter dem Schlüssel result in der Nachricht.

Read-Only-Zugang einrichten

Für Workflows die nur Daten lesen, empfiehlt sich ein dedizierter Datenbanknutzer mit reinen SELECT-Rechten. Das begrenzt den Schaden bei Konfigurationsfehlern und ist eine klare Sicherheitslinie.


Liquid Templating in SQL-Statements

Beide Agents unterstützen Liquid-Ausdrücke direkt im SQL:

-- Einfacher Wert
WHERE bestell_id = '{{ bestell_id }}'

-- Mit Standardwert falls Variable fehlt
LIMIT {{ limit | default: 100 }}

-- Datumsfilter
WHERE erstellt_am >= '{{ start_datum }}'
AND erstellt_am <= '{{ end_datum }}'

-- Bedingte WHERE-Klausel
{% if abteilung %}
WHERE abteilung = '{{ abteilung }}'
{% endif %}

Häufige Fehler

Fehlende Anführungszeichen bei Strings — Zahlen brauchen keine Anführungszeichen, Strings schon:

WHERE id = {{ id }}           -- ✅ Zahl
WHERE name = '{{ name }}' -- ✅ String
WHERE name = {{ name }} -- ❌ führt zu SQL-Fehler

Groß-/Kleinschreibung bei LIKE — SQLite ist standardmäßig case-insensitive für ASCII, PostgreSQL nicht. Für konsistentes Verhalten LOWER() nutzen:

WHERE LOWER(name) LIKE LOWER('%{{ suchbegriff }}%')

Zeitformat — SQLite erwartet Datumsangaben als Text im Format YYYY-MM-DD oder YYYY-MM-DDTHH:MM:SS. PostgreSQL hat eigene Datums-Typen und benötigt ggf. Casting: '{{ datum }}'::timestamp.