Abfragen erstellen in MSSQL beginnt mit einer klaren Frage: Welche Daten brauchst du, aus welchen Tabellen stammen sie und welche Zeilen sollen erscheinen? SQL Server beantwortet solche Fragen mit SELECT-Anweisungen. Dieser Leitfaden führt dich von der ersten Abfrage über WHERE und JOIN bis zu Gruppierung und Seitenausgabe. Alle Beispiele nutzen denselben kleinen Testdatensatz, damit du die Ergebnisse nachvollziehen kannst.
Was bedeutet „Abfragen erstellen in MSSQL“?
„MSSQL“ meint hier Microsoft SQL Server. Seine SQL-Erweiterung heißt Transact-SQL, kurz T-SQL. Mit einer SELECT-Abfrage liest du Daten; INSERT, UPDATE und DELETE verändern sie. Der frühere Beitrag vermischte diese Themen mit allgemeinen Aussagen über SQL. Hier konzentrieren wir uns auf lesende Abfragen und ihre typischen Bausteine.
Eine Abfrage kann eine oder mehrere Tabellen lesen. Du legst fest, welche Spalten ausgegeben, welche Zeilen gefiltert und in welcher Reihenfolge Ergebnisse angezeigt werden. SQL beschreibt das gewünschte Ergebnis; wie der Server es intern ermittelt, entscheidet der Optimierer anhand von Daten, Indizes und Statistiken. Die Microsoft-Referenz zu SELECT zeigt die vollständige Syntax.
Testdaten in temporären Tabellen anlegen
Führe diesen Block einmal in SQL Server Management Studio (SSMS) aus. Er erstellt zwei temporäre Tabellen für die aktuelle Verbindung und ändert keine vorhandenen Geschäftstabellen. Wenn du den Block im selben Abfragefenster erneut startest, werden nur diese beiden Demo-Tabellen neu angelegt. Die weiteren Beispiele funktionieren im selben Verbindungsfenster.
DROP TABLE IF EXISTS #DemoBestellungen;
DROP TABLE IF EXISTS #DemoKunden;
CREATE TABLE #DemoKunden (
KundeID int NOT NULL PRIMARY KEY,
Kundenname nvarchar(60) NOT NULL,
Stadt nvarchar(60) NOT NULL,
Land char(2) NOT NULL
);
CREATE TABLE #DemoBestellungen (
BestellungID int NOT NULL PRIMARY KEY,
KundeID int NOT NULL,
Bestelldatum date NOT NULL,
Status nvarchar(20) NOT NULL,
Betrag decimal(10, 2) NOT NULL
);
INSERT INTO #DemoKunden (KundeID, Kundenname, Stadt, Land)
VALUES (1, N'Anna', N'Berlin', 'DE'),
(2, N'Ben', N'Hamburg', 'DE'),
(3, N'Cara', N'Wien', 'AT'),
(4, N'Doro', N'Köln', 'DE');
INSERT INTO #DemoBestellungen
(BestellungID, KundeID, Bestelldatum, Status, Betrag)
VALUES (1001, 1, '20250110', N'abgeschlossen', 120.00),
(1002, 1, '20250115', N'offen', 80.00),
(1003, 2, '20250203', N'abgeschlossen', 200.00),
(1004, 3, '20250209', N'abgeschlossen', 50.00),
(1005, 2, '20250210', N'abgeschlossen', 30.00);
Die Kundin Doro hat bewusst keine Bestellung. Das hilft später, den Unterschied zwischen INNER JOIN und LEFT JOIN zu erkennen. DROP TABLE IF EXISTS ist ab SQL Server 2016 verfügbar; auf älteren Versionen müsstest du die Demo-Tabellen anders prüfen und entfernen.
Die erste SELECT-Abfrage: nur benötigte Spalten lesen
Mit SELECT wählst du Spalten, mit FROM die Datenquelle. Ein Alias nach AS gibt einer Ausgabespalte einen verständlichen Namen:
SELECT KundeID,
Kundenname AS Name,
Stadt
FROM #DemoKunden;
Die Abfrage liefert vier Zeilen. SELECT * ist für einen schnellen Blick auf kleine Testdaten praktisch, aber in dauerhaftem Anwendungscode solltest du die benötigten Spalten nennen. So bleibt klar, was die Abfrage liefert, und zusätzliche Tabellenspalten verändern die Ausgabe nicht unbemerkt. Einen vertiefenden Einstieg bietet der Beitrag zur SELECT-Anweisung in MSSQL.
Mit WHERE Zeilen filtern
WHERE schränkt die Zeilen vor einer späteren Gruppierung ein. Textwerte stehen in einfachen Anführungszeichen; für Unicode-Zeichenketten ist das vorangestellte N in T-SQL sinnvoll.
SELECT BestellungID, Bestelldatum, Betrag
FROM #DemoBestellungen
WHERE Status = N'abgeschlossen'
AND Betrag >= 100.00
ORDER BY BestellungID;
Hier erscheinen die Bestellungen 1001 und 1003. Mehrere Bedingungen kannst du mit AND und OR verbinden. Setze bei gemischten Bedingungen Klammern, damit die gewünschte Logik eindeutig ist. Für einen Zeitraum empfiehlt sich oft ein halboffenes Intervall:
SELECT BestellungID, Bestelldatum
FROM #DemoBestellungen
WHERE Bestelldatum >= '20250201'
AND Bestelldatum < '20250301'
ORDER BY Bestelldatum, BestellungID;
Die Abfrage findet alle drei Bestellungen aus Februar 2025. Das Muster funktioniert auch dann gut, wenn die Spalte später statt date einen Datentyp mit Uhrzeit verwendet. Schreibst du eine Funktion wie MONTH(Bestelldatum) direkt um die gefilterte Spalte, kann das die Nutzung eines passenden Index erschweren.
ORDER BY und TOP: Ergebnisse sortieren und begrenzen
Ohne ORDER BY ist die Reihenfolge von SQL-Ergebnissen nicht garantiert. Das gilt auch dann, wenn die Tabelle einen Primärschlüssel oder Index besitzt. Mit DESC sortierst du absteigend:
SELECT TOP (3) BestellungID, Bestelldatum, Betrag
FROM #DemoBestellungen
ORDER BY Bestelldatum DESC, BestellungID DESC;
Du erhältst die drei neuesten Bestellungen. Die zusätzliche BestellungID entscheidet bei gleichem Datum eindeutig über die Reihenfolge. TOP (3) ohne ORDER BY würde irgendeine Auswahl von drei Zeilen liefern, nicht verlässlich die neuesten. Microsoft empfiehlt diese Kombination ausdrücklich in der TOP-Dokumentation.
Abfragen aus zwei Tabellen mit JOIN erstellen
Eine Bestellung enthält die KundeID, aber nicht den Kundennamen. Mit einem INNER JOIN verbindest du passende Zeilen beider Tabellen. Tabellenaliasse wie k und b halten die Abfrage lesbar:
SELECT b.BestellungID,
b.Bestelldatum,
k.Kundenname,
b.Betrag
FROM #DemoBestellungen AS b
INNER JOIN #DemoKunden AS k
ON k.KundeID = b.KundeID
ORDER BY b.BestellungID;
Das Ergebnis umfasst fünf Bestellungen. Doro erscheint nicht, weil es zu ihr keine passende Bestellung gibt. Wenn du alle Kunden sehen möchtest, auch solche ohne Bestellung, beginnt die Abfrage bei den Kunden und verwendet LEFT JOIN:
SELECT k.KundeID,
k.Kundenname,
b.BestellungID
FROM #DemoKunden AS k
LEFT JOIN #DemoBestellungen AS b
ON b.KundeID = k.KundeID
ORDER BY k.KundeID, b.BestellungID;
Bei Doro ist BestellungID NULL. Willst du gezielt Kunden ohne Bestellung finden, ergänze WHERE b.BestellungID IS NULL. Prüfe bei LEFT JOIN genau, wohin ein Filter auf die rechte Tabelle gehört: WHERE b.Status = N'abgeschlossen' entfernt die Zeilen ohne passende Bestellung wieder aus dem Ergebnis. Ein Filter in der ON-Bedingung hat eine andere Wirkung und kann die Kunden ohne Treffer erhalten.
COUNT, SUM, GROUP BY und HAVING einsetzen
Wenn du statt einzelner Bestellungen eine Übersicht pro Kunde benötigst, gruppierst du die verbundenen Daten. COUNT(b.BestellungID) zählt bei einem LEFT JOIN nur tatsächlich gefundene Bestellungen. COUNT(*) würde auch Doros Ergebniszeile mitzählen und damit fälschlich eine Bestellung melden.
SELECT k.KundeID,
k.Kundenname,
COUNT(b.BestellungID) AS AnzahlBestellungen,