Lektion 1 von 9
Erste Abfragen: SELECT, WHERE und ORDER BY
Du holst gezielt Spalten und Zeilen aus einer Tabelle, rechnest in der Abfrage (z.B. MWST), sortierst und erstellst Top-Listen mit LIMIT.
Worum es geht
Luca ist im ersten Lehrjahr als Informatiker Applikationsentwicklung bei der Bachmann Haustechnik AG in Emmen. Der Sanitär-Grosshändler beliefert Installateure in der ganzen Zentralschweiz mit Armaturen, Rohren und Badkeramik. Kunden, Artikel und Bestellungen liegen in einer MariaDB-Datenbank. Wer bisher eine Liste brauchte, schrieb dem externen Programmierer eine Mail und wartete ein paar Tage.
An Lucas zweitem Arbeitstag steht Lagerchef Pius Achermann an seinem Pult: "Ich brauche eine Liste aller Artikel über 200 Franken, die teuersten zuerst. Und für die Inventur: Wo liegt bei uns am meisten Geld im Regal?" Mit einem Excel-Export würde Luca eine Stunde lang filtern, sortieren und Formeln ziehen. Mit SQL sind es fünf Zeilen und zwei Minuten, und morgen läuft dieselbe Abfrage wieder, ohne dass jemand klickt.
Darum geht es in diesem Kurs: Du stellst einer Datenbank präzise Fragen, änderst Daten so, dass nichts Ungewolltes passiert, und hältst die Datenbank schnell und gesichert. In dieser ersten Lektion schreibst du deine ersten Abfragen mit SELECT, wählst Zeilen mit WHERE aus, sortierst mit ORDER BY und rechnest direkt in der Abfrage, zum Beispiel die Mehrwertsteuer.
Die Übungsdatenbank der Bachmann Haustechnik AG
Die echte Datenbank hat rund 4000 Kunden und 60'000 Bestellungen. Für die Labore arbeitest du mit einer verkleinerten Kopie, die gleich aufgebaut ist: 15 Kunden, 15 Artikel, 20 Bestellungen. So kannst du jedes Ergebnis noch von Auge nachprüfen, und genau das ist am Anfang Gold wert.
| Tabelle | Wichtige Spalten | Beispielzeile |
|---|---|---|
kunde | kunde_id, firma, ort, kanton, telefon, kreditlimit, letzte_bestellung | 1, Sanitär Hofer AG, Emmen, LU |
artikel | artikel_id, artikelnr, bezeichnung, kategorie_id, lieferant_id, preis, lagerbestand | 2, AR-1002, Waschtischmischer Pura, 189.00, 40 |
kategorie | kategorie_id, bezeichnung | 1, Armaturen |
lieferant | lieferant_id, name, ort | 1, Aquatec Armaturen AG, Unterkulm |
bestellung | bestellung_id, kunde_id, datum, versandt_am, total | 1019, 1, 2026-09-14, NULL, 1929.00 |
bestellposition | bestellung_id, artikel_id, menge, einzelpreis | 1019, 12, 3, 398.00 |
Wie solche Tabellen mit Primär- und Fremdschlüsseln entstehen, kennst du aus den Modulen 162 und 164. Hier steht die Struktur bereits. Deine Aufgabe ist es, Antworten herauszuholen.
SELECT und FROM: welche Spalten?
Die einfachste Abfrage liest alles aus einer Tabelle:
SELECT * FROM artikel;Der Stern bedeutet "alle Spalten". Zum Erkunden einer unbekannten Tabelle ist das praktisch. In Abfragen, die du weitergibst oder in ein Programm einbaust, nennst du die Spalten aber einzeln. Dann siehst du auf einen Blick, was geliefert wird, die Spaltenreihenfolge bleibt stabil, wenn jemand die Tabelle erweitert, und es werden keine unnötigen oder heiklen Daten übertragen, etwa Kreditlimiten.
SELECT artikelnr, bezeichnung, preis
FROM artikel;Eine Abfrage kann auch rechnen. Das Ergebnis erscheint als zusätzliche Spalte, die Tabelle selbst bleibt unverändert:
SELECT artikelnr,
bezeichnung,
preis * lagerbestand AS lagerwert
FROM artikel;Mit AS lagerwert gibst du der berechneten Spalte einen Alias, also einen Anzeigenamen. Ohne Alias hiesse die Spalte im Ergebnis preis * lagerbestand, was in einem CSV-Export oder in einem Programm unpraktisch ist. Auch bestehende Spalten kannst du umbenennen, zum Beispiel preis AS preis_exkl.
In der Schweiz gilt seit dem 1. Januar 2024 ein MWST-Normalsatz von 8.1 %. Den Bruttopreis rechnest du so:
SELECT artikelnr,
preis AS preis_exkl,
ROUND(preis * 1.081, 2) AS preis_inkl_mwst
FROM artikel;ROUND(wert, 2) rundet auf zwei Nachkommastellen. Beim Waschtischmischer Pura ergibt 189.00 × 1.081 genau 204.309, gerundet 204.31.
Pius will von allen ausverkauften Artikeln nur Artikelnummer und Lagerbestand sehen. Welche Abfrage liefert genau das?
WHERE: welche Zeilen?
Mit WHERE gibst du eine Bedingung an. Nur Zeilen, für die sie zutrifft, kommen ins Ergebnis.
| Operator | Bedeutung | Beispiel |
|---|---|---|
= | gleich | kanton = 'ZG' |
<> oder != | ungleich | kanton <> 'LU' |
<, > | kleiner, grösser | preis > 200 |
<=, >= | kleiner gleich, grösser gleich | datum >= '2026-01-01' |
Drei Schreibregeln, die du ab sofort immer einhältst:
- Text steht in einfachen Anführungszeichen:
'ZG','Emmen'. - Zahlen stehen ohne Anführungszeichen und mit Dezimalpunkt:
96.50, nie96,50. - Datumswerte schreibst du im ISO-Format
'JJJJ-MM-TT':'2026-09-14'.
SELECT firma, ort
FROM kunde
WHERE kanton = 'ZG';firma | ort
-----------------------------+---------
Bad & Wärme Zug AG | Zug
Installa Baar GmbH | Baar
Rohr-Express Meier | Cham
Bühlmann Gebäudetechnik AG | RotkreuzDie Datenbank geht dabei gedanklich Zeile für Zeile durch die Tabelle kunde, prüft bei jeder Zeile, ob kanton = 'ZG' zutrifft, und behält die passenden. Von diesen gibt sie nur die Spalten aus, die im SELECT stehen.
ORDER BY und LIMIT: Reihenfolge und Menge
Ohne ORDER BY ist die Reihenfolge des Ergebnisses nicht festgelegt. Oft sieht sie zufällig richtig aus, aber nach einem Update des Servers oder mit einem neuen Index kann sie sich ändern. Wenn die Reihenfolge wichtig ist, gibst du sie deshalb immer an:
SELECT firma, kanton, ort
FROM kunde
ORDER BY kanton ASC, firma ASC;ASC (aufsteigend) ist der Standard und darf fehlen, DESC sortiert absteigend. Bei mehreren Sortierkriterien entscheidet zuerst das erste. Nur bei Gleichstand kommt das zweite zum Zug.
Mit LIMIT begrenzt du die Anzahl Zeilen, typischerweise für eine Top-Liste:
SELECT artikelnr, bezeichnung, preis
FROM artikel
ORDER BY preis DESC
LIMIT 3;LIMIT funktioniert in MariaDB, MySQL und SQLite gleich. Im Microsoft SQL Server schreibst du stattdessen SELECT TOP 3 .... Die Klauseln einer Abfrage stehen immer in derselben Reihenfolge: SELECT, FROM, WHERE, ORDER BY, LIMIT.
Bring die Teile der Abfrage "die fünf günstigsten Artikel, die an Lager sind" in die Reihenfolge, in der du sie schreiben musst.
- 1
FROM artikel
- 2
SELECT artikelnr, bezeichnung, preis
- 3
WHERE lagerbestand > 0
- 4
ORDER BY preis ASC
- 5
LIMIT 5;
Schritt für Schritt: Wo liegt am meisten Geld im Regal?
Pius will für die Inventur wissen, welche drei Artikel den höchsten Lagerwert haben. So gehst du vor:
Schritt 1: Die Frage klären. "Am meisten Geld im Regal" heisst: Lagerwert = Preis × Lagerbestand, pro Artikel. Gewünscht sind Artikelnummer, Bezeichnung und Lagerwert, nur die drei grössten.
Schritt 2: Die Tabelle bestimmen. Preis und Bestand stehen beide in artikel. Du brauchst keine zweite Tabelle.
Schritt 3: Spalten und Berechnung schreiben.
SELECT artikelnr, bezeichnung, preis * lagerbestand AS lagerwert
FROM artikel;Zwischenergebnis: 15 Zeilen, unsortiert, zum Beispiel AR-1001 | Küchenarmatur Linea | 6936 (289 × 24) und ET-2003 | Dichtungsset Thermostat | 0, weil dieser Artikel ausverkauft ist.
Schritt 4: Sortieren. ORDER BY lagerwert DESC stellt den grössten Wert nach oben. Im ORDER BY darfst du den Alias verwenden.
Schritt 5: Begrenzen. LIMIT 3 schneidet nach der dritten Zeile ab. Wichtig: Erst wird sortiert, dann abgeschnitten. Ohne Sortierung wären es irgendwelche drei Artikel.
Schritt 6: Ergebnis prüfen.
-- Inventur 2026: die drei Artikel mit dem höchsten Lagerwert (Preis x Bestand)
SELECT artikelnr,
bezeichnung,
preis * lagerbestand AS lagerwert
FROM artikel
ORDER BY lagerwert DESC
LIMIT 3;artikelnr | bezeichnung | lagerwert
----------+--------------------------------+----------
RO-3001 | Verbundrohr 16 mm, Rolle 50 m | 9240
AR-1002 | Waschtischmischer Pura | 7560
AR-1001 | Küchenarmatur Linea | 6936Rechne eine Zeile von Hand nach: 154.00 × 60 = 9240. Stimmt. Diese Gewohnheit, mindestens einen Wert nachzurechnen, bewahrt dich später vor peinlichen Auswertungen.
Schritt 7: Kommentieren und ablegen. Die Kommentarzeile mit -- beschreibt, welche Frage die Abfrage beantwortet. Luca speichert die Datei als inventur_top3.sql. Nächstes Jahr ist die Antwort einen Doppelklick entfernt.
Jetzt bist du dran. Die Labore laufen mit SQLite direkt im Browser. SELECT, WHERE, ORDER BY, LIMIT und ROUND funktionieren dort genau wie in MariaDB.
Labor: die Liste für den Lagerchef
Pius möchte alle Artikel sehen, die mehr als CHF 200 kosten, die teuersten zuerst.
Gib die Spalten artikelnr, bezeichnung und preis aus (in dieser Reihenfolge) und sortiere nach Preis absteigend.
Die Tabelle artikel ist bereits angelegt und mit den 15 Artikeln der Übungsdatenbank gefüllt.
Tabellen und Testdaten ansehen
PRAGMA foreign_keys = ON;
CREATE TABLE kategorie (
kategorie_id INTEGER PRIMARY KEY,
bezeichnung VARCHAR(40) NOT NULL
);
CREATE TABLE lieferant (
lieferant_id INTEGER PRIMARY KEY,
name VARCHAR(60) NOT NULL,
ort VARCHAR(40) NOT NULL
);
CREATE TABLE artikel (
artikel_id INTEGER PRIMARY KEY,
artikelnr VARCHAR(10) NOT NULL UNIQUE,
bezeichnung VARCHAR(60) NOT NULL,
kategorie_id INTEGER NOT NULL REFERENCES kategorie (kategorie_id),
lieferant_id INTEGER NOT NULL REFERENCES lieferant (lieferant_id),
preis DECIMAL(8,2) NOT NULL,
lagerbestand INTEGER NOT NULL
);
INSERT INTO kategorie (kategorie_id, bezeichnung) VALUES
(1, 'Armaturen'),
(2, 'Ersatzteile'),
(3, 'Rohrsysteme'),
(4, 'Badkeramik'),
(5, 'Werkzeug');
INSERT INTO lieferant (lieferant_id, name, ort) VALUES
(1, 'Aquatec Armaturen AG', 'Unterkulm'),
(2, 'Tubex AG', 'Sursee'),
(3, 'Porzella AG', 'Langenthal'),
(4, 'Fixwerk GmbH', 'Baar');
INSERT INTO artikel (artikel_id, artikelnr, bezeichnung, kategorie_id, lieferant_id, preis, lagerbestand) VALUES
(1, 'AR-1001', 'Küchenarmatur Linea', 1, 1, 289.00, 24),
(2, 'AR-1002', 'Waschtischmischer Pura', 1, 1, 189.00, 40),
(3, 'AR-1003', 'Duschthermostat Clima', 1, 1, 412.00, 12),
(4, 'AR-1004', 'Brausegarnitur Fino', 1, 4, 96.50, 30),
(5, 'ET-2001', 'Kartusche 35 mm', 2, 1, 38.90, 150),
(6, 'ET-2002', 'Strahlregler M24', 2, 1, 7.80, 400),
(7, 'ET-2003', 'Dichtungsset Thermostat', 2, 4, 14.50, 0),
(8, 'RO-3001', 'Verbundrohr 16 mm, Rolle 50 m', 3, 2, 154.00, 60),
(9, 'RO-3002', 'Pressfitting T-Stück 16 mm', 3, 2, 6.40, 800),
(10, 'RO-3003', 'Pressfitting Winkel 20 mm', 3, 2, 7.20, 650),
(11, 'BK-4001', 'Waschtisch Opal 60 cm', 4, 3, 245.00, 18),
(12, 'BK-4002', 'Wand-WC Terra spülrandlos', 4, 3, 398.00, 9),
(13, 'WZ-5001', 'Pressbacke 16 mm', 5, 4, 219.00, 5),
(14, 'WZ-5002', 'Rohrabschneider 3 bis 35 mm', 5, 4, 58.00, 22),
(15, 'WZ-5003', 'Entgratwerkzeug Inox', 5, 4, 34.50, 14);Labor: Preisliste mit MWST
Für eine Aktion der Rohrsysteme (kategorie_id 3) braucht der Verkauf eine Preisliste mit und ohne Mehrwertsteuer (Normalsatz 8.1 %).
| Spalte | Inhalt |
|---|---|
artikelnr | Artikelnummer |
bezeichnung | Bezeichnung |
preis_exkl | Preis ohne MWST |
preis_inkl_mwst | Preis mit 8.1 % MWST, auf 2 Nachkommastellen gerundet |
Nur Artikel der Kategorie 3, sortiert nach Artikelnummer.
Tabellen und Testdaten ansehen
PRAGMA foreign_keys = ON;
CREATE TABLE kategorie (
kategorie_id INTEGER PRIMARY KEY,
bezeichnung VARCHAR(40) NOT NULL
);
CREATE TABLE lieferant (
lieferant_id INTEGER PRIMARY KEY,
name VARCHAR(60) NOT NULL,
ort VARCHAR(40) NOT NULL
);
CREATE TABLE artikel (
artikel_id INTEGER PRIMARY KEY,
artikelnr VARCHAR(10) NOT NULL UNIQUE,
bezeichnung VARCHAR(60) NOT NULL,
kategorie_id INTEGER NOT NULL REFERENCES kategorie (kategorie_id),
lieferant_id INTEGER NOT NULL REFERENCES lieferant (lieferant_id),
preis DECIMAL(8,2) NOT NULL,
lagerbestand INTEGER NOT NULL
);
INSERT INTO kategorie (kategorie_id, bezeichnung) VALUES
(1, 'Armaturen'),
(2, 'Ersatzteile'),
(3, 'Rohrsysteme'),
(4, 'Badkeramik'),
(5, 'Werkzeug');
INSERT INTO lieferant (lieferant_id, name, ort) VALUES
(1, 'Aquatec Armaturen AG', 'Unterkulm'),
(2, 'Tubex AG', 'Sursee'),
(3, 'Porzella AG', 'Langenthal'),
(4, 'Fixwerk GmbH', 'Baar');
INSERT INTO artikel (artikel_id, artikelnr, bezeichnung, kategorie_id, lieferant_id, preis, lagerbestand) VALUES
(1, 'AR-1001', 'Küchenarmatur Linea', 1, 1, 289.00, 24),
(2, 'AR-1002', 'Waschtischmischer Pura', 1, 1, 189.00, 40),
(3, 'AR-1003', 'Duschthermostat Clima', 1, 1, 412.00, 12),
(4, 'AR-1004', 'Brausegarnitur Fino', 1, 4, 96.50, 30),
(5, 'ET-2001', 'Kartusche 35 mm', 2, 1, 38.90, 150),
(6, 'ET-2002', 'Strahlregler M24', 2, 1, 7.80, 400),
(7, 'ET-2003', 'Dichtungsset Thermostat', 2, 4, 14.50, 0),
(8, 'RO-3001', 'Verbundrohr 16 mm, Rolle 50 m', 3, 2, 154.00, 60),
(9, 'RO-3002', 'Pressfitting T-Stück 16 mm', 3, 2, 6.40, 800),
(10, 'RO-3003', 'Pressfitting Winkel 20 mm', 3, 2, 7.20, 650),
(11, 'BK-4001', 'Waschtisch Opal 60 cm', 4, 3, 245.00, 18),
(12, 'BK-4002', 'Wand-WC Terra spülrandlos', 4, 3, 398.00, 9),
(13, 'WZ-5001', 'Pressbacke 16 mm', 5, 4, 219.00, 5),
(14, 'WZ-5002', 'Rohrabschneider 3 bis 35 mm', 5, 4, 58.00, 22),
(15, 'WZ-5003', 'Entgratwerkzeug Inox', 5, 4, 34.50, 14);Der Strahlregler M24 kostet CHF 7.80 ohne MWST. Welchen Wert liefert ROUND(preis * 1.081, 2) für diesen Artikel?
Typische Fehler
- Text ohne Anführungszeichen.
WHERE kanton = ZGliefert in MariaDB den Fehler 1054 "Unknown column 'ZG' in 'where clause'". Ohne Anführungszeichen hält die DatenbankZGfür einen Spaltennamen. Korrektur:WHERE kanton = 'ZG'. - Doppelte statt einfache Anführungszeichen. MariaDB akzeptiert
"ZG"in der Grundeinstellung, aber im Standard-SQL, im SQL Server und in PostgreSQL bezeichnen doppelte Anführungszeichen Spalten- oder Tabellennamen. Gewöhne dir einfache Anführungszeichen an, dann funktioniert dein SQL überall. - Alias im WHERE.
WHERE lagerwert > 5000scheitert in MariaDB mit "Unknown column 'lagerwert'", weilWHEREvor demSELECTausgewertet wird und den Alias noch nicht kennt. Korrektur: den Ausdruck wiederholen, alsoWHERE preis * lagerbestand > 5000. ImORDER BYist der Alias erlaubt. SQLite ist hier grosszügiger, verlass dich aber nicht darauf. - Dezimalkomma.
WHERE preis > 96,50ergibt einen Syntaxfehler, und imSELECTwürde das Komma sogar still zwei Spalten erzeugen:96und50. Korrektur:96.50. - Reihenfolge erwartet, aber nicht verlangt. Eine Liste ohne
ORDER BYsieht heute sortiert aus und morgen nicht mehr. Wer eine Reihenfolge braucht, schreibt sie hin.
Zusammenfassung
SELECTwählt die Spalten,FROMdie Tabelle,WHEREdie Zeilen,ORDER BYdie Reihenfolge,LIMITdie Anzahl.- Nenne Spalten einzeln statt mit
*, sobald eine Abfrage weitergegeben oder eingebaut wird. - Berechnete Spalten wie
preis * lagerbestandbekommen mitASeinen sprechenden Alias.ROUND(x, 2)rundet auf Rappen. - Text in einfache Anführungszeichen, Zahlen mit Dezimalpunkt, Datum im Format
'JJJJ-MM-TT'. - Ohne
ORDER BYgibt es keine garantierte Reihenfolge.LIMITwirkt erst nach dem Sortieren. - Rechne mindestens eine Zeile des Ergebnisses von Hand nach und kommentiere jede Abfrage mit der Frage, die sie beantwortet.
Mit deiner eigenen KI vertiefen
Kopiere einen Prompt in Claude, ChatGPT oder Claude Code. Er macht die KI zur Lernbegleitung statt zum Lösungsautomaten.
Übungsdatenbank in Docker mit Claude Code aufsetzen Claude Code
Du richtest mit Claude Code eine eigene MariaDB in Docker mit der Bachmann-Übungsdatenbank ein und verstehst jeden Schritt, weil du die Befehle selbst ausführst.