Die Abfrage läuft in Access seit Jahren fehlerfrei – bis die Tabellen auf den SQL Server umziehen. Plötzlich liefert Format im Gruppierungsausdruck eine Fehlermeldung, Like „M*“ findet keinen einzigen Datensatz, und ein Aufruf von Nz endet mit der Meldung, der Server kenne diese Funktion nicht. Wer Access mit einem SQL Server als Backend betreibt, kommt früher oder später nicht mehr um T-SQL herum, die Abfragesprache des Servers. Was sich dabei gegenüber dem SQL von Access ändert und wie wir T-SQL aus Access und aus VBA heraus ausführen, zeigt der erste Teil dieser Reihe.
Beispieldatenbank
Als Beispiel dient durch die ganze Reihe eine kleine Bestellverwaltung: Kunden bestellen bei einem Händler für Lebensmittel, Büroartikel und Technik, Mitarbeiter betreuen die Bestellungen, Lieferanten beliefern das Sortiment.
Die Datenbank heißt Bestellverwaltung_TSQL und besteht aus diesen Tabellen:
- tblKategorien: die Warengruppen als zweistufige Hierarchie, über das Fremdschlüsselfeld OberKategorieID auf sich selbst verwiesen
- tblLieferanten: sieben Lieferanten aus Deutschland, Österreich und der Schweiz
- tblProdukte: dreißig Produkte mit Preis, Lagerbestand, dem Ja/Nein-Feld Auslaufartikel und einer optionalen Beschreibung
- tblKunden: zweiunddreißig Kunden, teils Firmen, teils Privatpersonen, mit leeren Feldern und Sonderzeichen in den Namen
- tblMitarbeiter: acht Mitarbeiter, über VorgesetzterID ebenfalls hierarchisch verknüpft
- tblBestellstatus: die fünf Bearbeitungsstufen einer Bestellung
- tblBestellungen: 330 Bestellungen von Januar 2024 bis September 2026
- tblBestelldetails: die 825 Positionen der Bestellungen
Die Namen folgen einer festen Konvention: Tabellen beginnen mit tbl, der Primärschlüssel einer Tabelle heißt wie die Tabelle in der Einzahl mit angehängtem ID (etwa ProduktID in tblProdukte), und Fremdschlüsselfelder tragen denselben Namen wie das Feld, auf das sie verweisen (KategorieID in tblProdukte verweist auf tblKategorien).
Das Skript Bestellverwaltung_TSQL.sql aus dem Download legt die Datenbank samt Daten an. Wir führen es in SQL Server Management Studio (kurz SSMS) auf einer lokalen SQL Server Express-Instanz aus. Es löscht eine vorhandene Datenbank gleichen Namens und erzeugt sie neu. Danach zeigt der Objekt-Explorer die acht Tabellen (siehe Bild 1).

Bild 1: Die Tabellen der Datenbank Bestellverwaltung_TSQL im Objekt-Explorer von SSMS
Das Access-Frontend SQLServer_EinstiegVonJetSQLZuTSQL.accdb enthält die acht Tabellen als Verknüpfungen mit den Namen dbo_tblKategorien, dbo_tblKunden und so weiter. Wie man solche Verknüpfungen von Hand oder per VBA anlegt, haben wir im Beitrag SQL Server: Tabellen per VBA verknüpfen (www.vbentwickler.de/485) gezeigt. Die VBA-Beispiele dieses Beitrags stehen im Modul mdlTSQLEinstiegBeispiele.
Ein Szenario: Monatsumsätze
Der Vertrieb möchte wissen, wie sich der Umsatz im Jahr 2026 auf die einzelnen Monate verteilt. Stornierte Bestellungen sollen nicht zählen. In Access liegt die Lösung auf der Hand: eine Auswahlabfrage auf den verknüpften Tabellen, die den Umsatz je Monat summiert. Sie steht als qryMonatsumsatzJet im Frontend (siehe Listing 1).
SELECT Format([b].[Bestelldatum],"yyyy-mm") AS Monat,
Sum([d].[Menge]*[d].[Einzelpreis]*(1-[d].[Rabatt])) AS Umsatz
FROM dbo_tblBestellungen AS b INNER JOIN dbo_tblBestelldetails AS d
ON b.BestellungID = d.BestellungID
WHERE b.Bestelldatum >= #1/1/2026# AND b.Bestelldatum < #1/1/2027#
AND b.BestellstatusID <> 5
GROUP BY Format([b].[Bestelldatum],"yyyy-mm")
ORDER BY Format([b].[Bestelldatum],"yyyy-mm");
Listing 1: Die Monatsumsätze 2026 in Access-SQL
Der Umsatz einer Position ist Menge mal Einzelpreis abzüglich Rabatt. Die Bestellungen mit der BestellstatusID 5 sind storniert und fallen heraus. Gegen reine Access-Tabellen würde die Abfrage sofort laufen. Auf den verknüpften SQL Server-Tabellen öffnet sie sich dagegen mit einer Fehlermeldung (siehe Bild 2).

Bild 2: Access öffnet die Abfrage qryMonatsumsatzJet auf den verknüpften Tabellen nicht
Woran liegt das? Das Feld Bestelldatum hat im Server den Datentyp datetime2(0). In unserer Beispielkonfiguration bindet Access es als Datum/Uhrzeit, erweitert ein, und mit diesem Typ kommt Format im Gruppierungsausdruck nicht zurecht. Ob Access den Typ so anbindet, lässt sich in den Optionen der aktuellen Datenbank einstellen. Ersetzen wir Format durch Year und Month, läuft die Abfrage.
Auch die Summen bergen Überraschungen. Die Summe von Menge*Einzelpreis*(1-Rabatt) über alle Positionen ohne Gruppierung scheitert mit der Meldung Staffelung des Dezimalwerts führte zu Datenkürzung, und erst mit CCur um den Ausdruck herum liefert Access ein Ergebnis. Solche Umwege kosten Zeit.
Die Alternative ist, die Auswertung dem Server zu überlassen. Dort ist sie eine einzige Anweisung in der Sprache des Servers, in T-SQL. Dafür müssen wir zunächst klären, wie Access eine Abfrage verarbeitet.
Wie Access Abfragen verarbeitet
Eine Access-Abfrage auf verknüpfte Tabellen schickt Access nicht einfach an den Server. Die Datenbank-Engine von Access verarbeitet sie selbst. Sie übersetzt so viel wie möglich über den ODBC-Treiber in T-SQL und erledigt den Rest lokal, nachdem sie die benötigten Daten vom Server geholt hat. Was dabei schiefgehen kann, haben wir im Szenario gesehen.
Der Umzug der Tabellen allein macht Access-Funktionen wie Nz nicht unbrauchbar. Probleme entstehen erst, wenn eine Anweisung direkt auf dem Server ausgeführt wird, denn dort gelten die Regeln von T-SQL. Dafür gibt es in Access die Pass-Through-Abfrage, die ihren Text unverändert an den Server reicht. Sie lernen wir weiter unten kennen. Vorher werfen wir einen Blick auf das Werkzeug, mit dem wir T-SQL am bequemsten ausprobieren.
SQL Server Management Studio
SSMS ist die Verwaltungsoberfläche des SQL Servers. Wir starten es und melden uns an der Instanz .SQLEXPRESS mit der Windows-Authentifizierung an. Links erscheint der Objekt-Explorer mit den Datenbanken der Instanz, darunter Bestellverwaltung_TSQL.
Ein Abfragefenster öffnen wir mit Neue Abfrage. Wichtig ist die Datenbank, gegen die die Anweisung läuft: Sie steht in der Auswahlliste links neben dem Ausführen-Schalter. Alternativ stellen wir der Anweisung ein USE Bestellverwaltung_TSQL voran.
Als erste Abfrage holen wir die ersten drei Kunden, alphabetisch nach Nachnamen sortiert:
SELECT TOP (3) KundeID, Firma, Nachname, Ort FROM dbo.tblKunden ORDER BY Nachname;
Fast alles daran kennen wir aus Access. Neu ist dbo: Das ist das Schema, zu dem die Tabelle gehört. Tabellen, Sichten und gespeicherte Prozeduren liegen im SQL Server in einem Schema, und wenn wir nichts anderes angelegt haben, heißt es dbo. Das Semikolon am Ende beendet die Anweisung.
Mit F5 oder dem Schalter Ausführen läuft die Anweisung, und das Ergebnis erscheint im Ergebnisbereich unter dem Abfragefenster (siehe Bild 3).

Bild 3: Das erste SELECT im Abfragefenster von SSMS, darunter das Ergebnis
In SSMS-Skripten steht häufig die Zeile GO. Sie ist kein T-SQL-Befehl, sondern ein Trennzeichen für SSMS und das Kommandozeilenwerkzeug sqlcmd. Es teilt ein Skript in einzelne Stapel (Batches), die nacheinander an den Server gehen.
Das Skript der Beispieldatenbank nutzt es zum Beispiel, damit die Datenbank angelegt ist, bevor das erste CREATE TABLE an sie gerichtet wird. In Pass-Through-Abfragen gehört GO nicht hinein.
Zeichenketten
Der auffälligste Unterschied zeigt sich bei einem Textkriterium. In Access fassen wir Text in doppelte Anführungszeichen ein. Die Abfrage nach den Kunden aus Köln lautet in der SQL-Ansicht einer Access-Abfrage so:
SELECT KundeID, Vorname, Nachname, Ort FROM dbo_tblKunden WHERE Ort = "Köln";
In T-SQL stehen Zeichenketten in einfachen Anführungszeichen:
SELECT KundeID, Vorname, Nachname, Ort FROM dbo.tblKunden WHERE Ort = N'Köln';
Vor dem Text steht hier ein N. Es kennzeichnet das Literal als Unicode-Text (nvarchar) und verhindert, dass ein Zeichen durch die Zeichensatzumsetzung verloren geht. Wir setzen es vor jeden Text, der Umlaute oder andere Sonderzeichen enthalten kann.
Die doppelten Anführungszeichen sind in T-SQL nicht verboten, aber sie bedeuten etwas anderes: Bei eingeschaltetem QUOTED_IDENTIFIER – das ist die Voreinstellung in SSMS und beim ODBC-Treiber – kennzeichnen sie einen Objektnamen, so wie bei Access die eckigen Klammern. Ein „Köln“ sucht der Server deshalb als Spaltennamen. Wie die Fehlermeldung dazu aussieht, sehen wir im Abschnitt über die Fehlermeldungen des Servers. Kommt ein Apostroph im Text vor, verdoppeln wir ihn. Der Kunde Sean O’Brien findet sich so:
SELECT KundeID, Vorname, Nachname FROM dbo.tblKunden WHERE Nachname = N'O''Brien';
Platzhalter in LIKE
Auch die Platzhalter ändern sich. In Access suchen wir mit * nach beliebig vielen und mit ? nach genau einem Zeichen. Die Kunden, deren Nachname mit S beginnt, finden wir dort so:
SELECT Nachname FROM dbo_tblKunden WHERE Nachname Like "S*";
In T-SQL heißen die Platzhalter % für beliebig viele Zeichen und _ für genau ein Zeichen:
SELECT Nachname FROM dbo.tblKunden WHERE Nachname LIKE N'S%';
Der Platzhalter * löst in T-SQL keinen Fehler aus, er ist dort ein gewöhnliches Zeichen. Die Abfrage WHERE Nachname LIKE N’M*‘ sucht daher nach dem wörtlichen Nachnamen M* und liefert keine Datensätze, statt Meier und Müller zu finden. Wer Abfragen aus Access übernimmt, sollte jedes Like auf diese Platzhalter hin prüfen.
Datumswerte
In Access schreiben wir Datumsliterale in Rautezeichen und im amerikanischen Format: #9/15/2026#. T-SQL kennt diese Schreibweise nicht. Der Server beantwortet sie mit der Meldung Falsche Syntax in der Nähe von „#“.
Ein Datum steht in T-SQL als Zeichenkette in einfachen Anführungszeichen. Damit es unabhängig von den Spracheinstellungen des Servers richtig gelesen wird, schreiben wir es als ‚20260915‘, also Jahr, Monat und Tag ohne Trennzeichen.
Die zweite Falle betrifft die Uhrzeit. Die Spalte Bestelldatum in tblBestellungen hat den Datentyp datetime2(0) und speichert Datum und Uhrzeit. Die Bestellung vom 15. September 2026 ist um 11:30:00 Uhr eingegangen. Wir zählen die Bestellungen dieses Tages zunächst mit einem Vergleich auf Gleichheit:
SELECT COUNT(*) FROM dbo.tblBestellungen WHERE Bestelldatum = '20260915';
Das Ergebnis ist 0, denn ‚20260915‘ bedeutet Mitternacht, und die Bestellung stammt von 11:30 Uhr. Wir brauchen einen Zeitraum, der den ganzen Tag umfasst:
SELECT COUNT(*) FROM dbo.tblBestellungen WHERE Bestelldatum >= '20260915' AND Bestelldatum < '20260916';
Jetzt zählt die Abfrage 1. Der Zeitraum beginnt am Tag selbst und endet vor dem nächsten Tag. Diese Schreibweise ist die Standardlösung, denn sie vergleicht das Feld unverändert mit Grenzwerten und lässt dem Server damit alle Möglichkeiten, einen Index zu nutzen. Alternativ können wir das Feld mit CAST(Bestelldatum AS date) auf das Datum kürzen. Das ist bequemer zu lesen, wandelt aber den Feldinhalt um. Bei Umwandlungen kann der Server dann unter Umständen keine effiziente Indexsuche mehr nutzen, wie wir im Teil über Indizes sehen werden.
NULL
Mit NULL verhält sich T-SQL grundsätzlich wie Access: Ein Vergleich mit NULL ist weder wahr noch falsch. Das Kriterium WHERE Firma = NULL liefert deshalb nie einen Datensatz. Die Frage nach leeren Feldern lautet WHERE Firma IS NULL. In der Beispieldatenbank sind bei sechzehn Kunden keine Firmen hinterlegt, es sind die Privatkunden. Die Abfrage mit = NULL zählt 0, die Abfrage mit IS NULL zählt 16:
SELECT COUNT(*) FROM dbo.tblKunden WHERE Firma IS NULL;
Texte verketten
Interessant wird es beim Zusammensetzen von Texten. In Access gibt es zwei Operatoren: & verkettet und behandelt Null als leeren Text, + verkettet und gibt Null zurück, wenn ein Teil Null ist. T-SQL hat nur das +, und dieses verhält sich wie das + in Access.
Wir wollen Kunden mit Nachnamen und Firma in Klammern anzeigen, etwa als Nachname (Firma), Privatkunden dagegen nur mit dem Nachnamen. Der erste Versuch verkettet mit +:
SELECT TOP (4) Nachname + N' (' + Firma + N')'
FROM dbo.tblKunden
ORDER BY KundeID;
Bei Privatkunden liefert diese Abfrage NULL, denn ein + mit NULL ergibt wieder NULL. Wir können den Klammerausdruck mit ISNULL durch einen leeren Text ersetzen, wenn er NULL ist:
SELECT TOP (4)
Nachname + ISNULL(N' (' + Firma + N')', N'')
FROM dbo.tblKunden
ORDER BY KundeID;
Jetzt steht bei Privatkunden nur der Nachname. Dieses Muster kennen Access-Entwickler schon, in Access schreiben wir es als Nachname & (“ (“ + Firma + „)“). Als Alternative gibt es die Funktion CONCAT, die NULL wie einen leeren Text behandelt. Sie hat allerdings einen Nachteil: Mit CONCAT(Nachname, N‘ (‚, Firma, N‘)‘) erscheinen bei Privatkunden leere Klammern, weil die Klammern als eigene Argumente stehen.
Ja/Nein und andere Datentypen
Für die Felder unserer Tabellen müssen wir wissen, welcher Access-Datentyp welchem SQL Server-Typ entspricht. Die wichtigsten Zuordnungen:
- AutoWert: int mit IDENTITY
- Zahl (Long Integer): int
- Zahl (Integer): smallint
- Währung: money oder decimal mit passend gewählter Genauigkeit, etwa decimal(19,4); für Preise mit nur zwei Nachkommastellen genügt decimal(9,2)
- Ja/Nein: bit
- Kurzer Text: nvarchar(n)
- Langer Text: nvarchar(max)
- Datum/Uhrzeit: date für reine Datumswerte, datetime2 für Datum mit Uhrzeit
Das Ja/Nein-Feld verdient besondere Aufmerksamkeit. Das Feld Auslaufartikel in tblProdukte ist ein bit. In Access ist Wahr der Wert -1, in T-SQL ist ein gesetztes bit die 1. Das hat konkrete Folgen: Die Bedingung WHERE Auslaufartikel = -1, aus Access gewohnt, liefert in T-SQL keinen Fehler, aber auch keinen Datensatz. Die Bedingung WHERE Auslaufartikel = True scheitert mit dem Fehler Ungültiger Spaltenname „True“, denn T-SQL kennt kein True. Richtig ist:
SELECT Produkt FROM dbo.tblProdukte WHERE Auslaufartikel = 1;
Auch IIF(Auslaufartikel, …) funktioniert nicht, weil ein bit in T-SQL kein Wahrheitswert ist. Dort muss ein Vergleich stehen, also IIF(Auslaufartikel = 1, …).
Funktionen
Viele Funktionen, die wir in Access-Abfragen verwenden, stammen aus VBA und gibt es im SQL Server nicht. Die Datenbank-Engine des Servers kennt dafür eigene Gegenstücke. Eine erste Übersicht:
- Nz: ISNULL oder COALESCE; COALESCE akzeptiert mehr als zwei Argumente und ist Standard-SQL
- Iif: IIF in der Schreibweise von T-SQL (der Vergleich muss ausdrücklich stehen) oder die allgemeinere CASE WHEN … THEN … ELSE … END-Anweisung
- Format: FORMAT für Zahlen und Datumswerte, bei großen Mengen langsamer als CONVERT mit Stilnummer
- Date/Now: CAST(GETDATE() AS date) beziehungsweise GETDATE() oder SYSDATETIME()
- Left/Mid/Len: LEFT, SUBSTRING und LEN; Mid entspricht SUBSTRING mit Startposition und Länge. Achtung: LEN berücksichtigt im Gegensatz zu Len in VBA keine nachgestellten Leerzeichen
- InStr: CHARINDEX; die Parameter stehen in umgekehrter Reihenfolge (gesuchter Text zuerst)
- DateAdd/DateDiff: DATEADD und DATEDIFF; der Datumsteil steht ohne Anführungszeichen, etwa DATEDIFF(DAY, Bestelldatum, Lieferdatum)
- Year/Month/Day: gleichnamige Funktionen
- DLookup/DSum/DCount: gibt es nicht; wir ersetzen sie durch Unterabfragen oder Verknüpfungen, davon handelt der dritte Teil
Ein Beispiel mit drei dieser Funktionen ist die Produktliste der ersten vier Produkte mit Beschreibung, Auslauf-Kennzeichen und formatiertem Preis (siehe Listing 2).
SELECT TOP (4)
Produkt,
ISNULL(Beschreibung, N'(keine)') AS Beschreibung,
IIF(Auslaufartikel = 1, N'ja', N'nein') AS Auslauf,
FORMAT(Einzelpreis, 'N2', 'de-DE') AS Preis,
Lagerbestand
FROM dbo.tblProdukte
ORDER BY ProduktID;
Listing 2: Die Funktionen ISNULL, IIF und FORMAT in einer Abfrage
Unser exklusives Angebot für Dich!
(Gilt für den Abschluss eines Jahres-Abonnements.)
Hier geht’s weiter →Die ersten 4 Wochen kostenlos testen – voller Zugriff auf alle Artikel, vollständigen Code und Beispieldatenbanken. Kein Risiko: Wenn es nicht passt, kündigst Du einfach innerhalb der ersten vier Wochen.
Oder hast Du eine konkrete Frage zu Deiner eigenen Access-Anwendung?
Vielleicht stellt Deine Anwendung Dich vor eine Herausforderung, zu der Du bisher keine Lösung findest. Schlechte Performance, kein ausreichender Zugriffsschutz, Du bist unsicher über Dein Datenmodell oder Dein Code liefert unerklärliche Fehler?
In unserem kostenlosen Access-Audit schaut sich André Minhorst persönlich gemeinsam mit Dir Deine Lösung per Zoom an – und zeigt Dir, wo Datenmodell, VBA-Code, Ergonomie und Sicherheit Optimierungspotenzial bieten.
Jetzt kostenloses Access-Audit anfordern →