Modulseite

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.

ca. 40 Min.0/5 Checks gelöst

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.

TabelleWichtige SpaltenBeispielzeile
kundekunde_id, firma, ort, kanton, telefon, kreditlimit, letzte_bestellung1, Sanitär Hofer AG, Emmen, LU
artikelartikel_id, artikelnr, bezeichnung, kategorie_id, lieferant_id, preis, lagerbestand2, AR-1002, Waschtischmischer Pura, 189.00, 40
kategoriekategorie_id, bezeichnung1, Armaturen
lieferantlieferant_id, name, ort1, Aquatec Armaturen AG, Unterkulm
bestellungbestellung_id, kunde_id, datum, versandt_am, total1019, 1, 2026-09-14, NULL, 1929.00
bestellpositionbestellung_id, artikel_id, menge, einzelpreis1019, 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:

SQL
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.

SQL
SELECT artikelnr, bezeichnung, preis
FROM artikel;

Eine Abfrage kann auch rechnen. Das Ergebnis erscheint als zusätzliche Spalte, die Tabelle selbst bleibt unverändert:

SQL
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:

SQL
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.

Check 1 · Eine AntwortEinstieg

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.

OperatorBedeutungBeispiel
=gleichkanton = 'ZG'
<> oder !=ungleichkanton <> 'LU'
<, >kleiner, grösserpreis > 200
<=, >=kleiner gleich, grösser gleichdatum >= '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, nie 96,50.
  • Datumswerte schreibst du im ISO-Format 'JJJJ-MM-TT': '2026-09-14'.
SQL
SELECT firma, ort
FROM kunde
WHERE kanton = 'ZG';
Text
firma                        | ort
-----------------------------+---------
Bad & Wärme Zug AG           | Zug
Installa Baar GmbH           | Baar
Rohr-Express Meier           | Cham
Bühlmann Gebäudetechnik AG   | Rotkreuz

Die 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:

SQL
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:

SQL
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.

Check 2 · ReihenfolgeEinstieg

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. 1

    FROM artikel

  2. 2

    SELECT artikelnr, bezeichnung, preis

  3. 3

    WHERE lagerbestand > 0

  4. 4

    ORDER BY preis ASC

  5. 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.

SQL
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.

SQL
-- 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;
Text
artikelnr | bezeichnung                    | lagerwert
----------+--------------------------------+----------
RO-3001   | Verbundrohr 16 mm, Rolle 50 m  | 9240
AR-1002   | Waschtischmischer Pura         | 7560
AR-1001   | Küchenarmatur Linea            | 6936

Rechne 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.

Check 3 · Code-LaborEinstieg

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.

Code-Labor · SQL · SQLite
Tabellen und Testdaten ansehen
SQL
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);
Strg + Enter
Check 4 · Code-LaborFortgeschritten

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 %).

SpalteInhalt
artikelnrArtikelnummer
bezeichnungBezeichnung
preis_exklPreis ohne MWST
preis_inkl_mwstPreis mit 8.1 % MWST, auf 2 Nachkommastellen gerundet

Nur Artikel der Kategorie 3, sortiert nach Artikelnummer.

Code-Labor · SQL · SQLite
Tabellen und Testdaten ansehen
SQL
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);
Strg + Enter
Check 5 · BerechnenFortgeschritten

Der Strahlregler M24 kostet CHF 7.80 ohne MWST. Welchen Wert liefert ROUND(preis * 1.081, 2) für diesen Artikel?

CHF

Typische Fehler

  1. Text ohne Anführungszeichen. WHERE kanton = ZG liefert in MariaDB den Fehler 1054 "Unknown column 'ZG' in 'where clause'". Ohne Anführungszeichen hält die Datenbank ZG für einen Spaltennamen. Korrektur: WHERE kanton = 'ZG'.
  2. 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.
  3. Alias im WHERE. WHERE lagerwert > 5000 scheitert in MariaDB mit "Unknown column 'lagerwert'", weil WHERE vor dem SELECT ausgewertet wird und den Alias noch nicht kennt. Korrektur: den Ausdruck wiederholen, also WHERE preis * lagerbestand > 5000. Im ORDER BY ist der Alias erlaubt. SQLite ist hier grosszügiger, verlass dich aber nicht darauf.
  4. Dezimalkomma. WHERE preis > 96,50 ergibt einen Syntaxfehler, und im SELECT würde das Komma sogar still zwei Spalten erzeugen: 96 und 50. Korrektur: 96.50.
  5. Reihenfolge erwartet, aber nicht verlangt. Eine Liste ohne ORDER BY sieht heute sortiert aus und morgen nicht mehr. Wer eine Reihenfolge braucht, schreibt sie hin.

Zusammenfassung

  • SELECT wählt die Spalten, FROM die Tabelle, WHERE die Zeilen, ORDER BY die Reihenfolge, LIMIT die Anzahl.
  • Nenne Spalten einzeln statt mit *, sobald eine Abfrage weitergegeben oder eingebaut wird.
  • Berechnete Spalten wie preis * lagerbestand bekommen mit AS einen sprechenden Alias. ROUND(x, 2) rundet auf Rappen.
  • Text in einfache Anführungszeichen, Zahlen mit Dezimalpunkt, Datum im Format 'JJJJ-MM-TT'.
  • Ohne ORDER BY gibt es keine garantierte Reihenfolge. LIMIT wirkt 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.