SQL Server 2025: Nativer Datentyp json statt NVARCHAR(MAX)
Eine Bestellung trifft über Ihre API ein: Kundennummer, Lieferadresse, Positionen – und plötzlich ein neues Feld für die Wunschgravur. Im JSON-Dokument ist das eine zusätzliche Zeile. Im relationalen Datenmodell beginnt dagegen möglicherweise eine Diskussion über Spalten, Migrationen und Deployment-Termine.
Deshalb speichern viele Anwendungen solche Dokumente als NVARCHAR(MAX) im SQL Server. Das funktioniert, ähnelt aber einem Paketlager, das jede Lieferung als langen Brief ablegt: Zum Auswerten muss der Inhalt wieder gelesen und interpretiert werden. SQL Server 2025 bietet dafür den nativen Datentyp json. Was ändert sich dadurch wirklich – und wann bleibt eine klassische Tabelle die bessere Wahl?
Der native Datentyp json: Funktionsumfang und Einstieg
JSON, kurz für JavaScript Object Notation, beschreibt Daten als Objekte, Arrays und einfache Werte. Der native Datentyp json speichert Dokumente in einer intern aufbereiteten binären Darstellung statt als gewöhnlichen Unicode-Text.
Die bekannten JSON-Funktionen bleiben dabei Ihre Werkzeuge. Sie müssen bestehende Abfragen also nicht grundsätzlich neu erfinden.
Wichtig für die Einführung: Verwechseln Sie die Verfügbarkeit einer Funktion nicht mit ihrem Freigabestatus. Prüfen Sie für Ihren SQL-Server-2025-Build die Microsoft-Dokumentation, insbesondere den Preview-/GA-Status des Datentyps und der JSON-Aggregatfunktionen. Angaben für Azure SQL lassen sich nicht automatisch auf eine lokale Installation übertragen.
Für die folgenden Beispiele legen wir zwei temporäre Tabellen an:
CREATE TABLE #DokumenteText
(
Id int NOT NULL PRIMARY KEY,
Payload nvarchar(max) NOT NULL,
CONSTRAINT CK_DokumenteText_Json
CHECK (ISJSON(Payload, OBJECT) = 1)
);
CREATE TABLE #DokumenteJson
(
Id int NOT NULL PRIMARY KEY,
Payload json NOT NULL
);
INSERT INTO #DokumenteText (Id, Payload)
VALUES
(1, N'{
"kunde": {"id": 42, "name": "Ada"},
"gesamt": 129.90,
"positionen": [
{"artikel": "Tastatur", "menge": 1},
{"artikel": "Kabel", "menge": 2}
]
}'),
(2, N'{
"kunde": {"id": 73, "name": "Linus"},
"gesamt": 49.50,
"positionen": [
{"artikel": "Maus", "menge": 1}
]
}');
INSERT INTO #DokumenteJson (Id, Payload)
SELECT Id, CAST(Payload AS json)
FROM #DokumenteText;
Führen Sie die weiteren Beispiele in derselben Sitzung aus, damit die temporären Tabellen erhalten bleiben.
Validierung beim Speichern: json versus NVARCHAR(MAX)
Eine gewöhnliche NVARCHAR(MAX)-Spalte akzeptiert auch Keine Bestellung vorhanden. Erst ein CHECK-Constraint – eine beim Schreiben geprüfte Bedingung – macht daraus einen Speicher für syntaktisch gültiges JSON.
Im Beispiel verlangt ISJSON(Payload, OBJECT) ausdrücklich ein JSON-Objekt. Ohne Typvorgabe prüft ISJSON auf ein gültiges Objekt oder Array. Der native Datentyp akzeptiert ebenfalls nur gültige JSON-Objekte oder -Arrays; alleinstehende Skalare wie 42 sind kein Ersatz dafür.
Gültiges JSON bedeutet allerdings nicht gültige Geschäftsdaten. Weder json noch ISJSON garantieren, dass kunde.id existiert, eine Artikelnummer bekannt ist oder eine Menge positiv ausfällt.
Auch doppelte Eigenschaftsnamen werden durch ISJSON nicht ausgeschlossen. Verlassen Sie sich deshalb nicht auf Dokumente mit mehrfach vorkommendem Schlüssel.
Ein typischer Fehler: CHECK (ISJSON(Payload) = 1) allein verhindert keine SQL-NULL-Werte. Dafür steht im Beispiel zusätzlich NOT NULL.
Speicherbedarf und Performance praxisnah vergleichen
Die binäre Darstellung kann Speicher sparen und das wiederholte Interpretieren des JSON-Texts reduzieren. Daraus folgt aber nicht: Jede Abfrage wird automatisch schneller.
Entscheidend sind unter anderem:
- Größe und Struktur der Dokumente,
- gelesene Eigenschaften und Filter,
- Häufigkeit von Inserts und Updates,
- vorhandene Indizes und Komprimierung,
- zusätzliche Konvertierungen zwischen Text und
json.
Messen Sie mit realistischen Dokumenten und ausreichend vielen Zeilen. Zwei Beispielbestellungen zeigen die Syntax, liefern aber keinen belastbaren Benchmark.
SET STATISTICS IO ON;
SET STATISTICS TIME ON;
SELECT SUM(TRY_CONVERT(decimal(12, 2),
JSON_VALUE(Payload, '$.gesamt')))
FROM #DokumenteText
WHERE JSON_VALUE(Payload, '$.kunde.id') = N'42';
SELECT SUM(TRY_CONVERT(decimal(12, 2),
JSON_VALUE(Payload, '$.gesamt')))
FROM #DokumenteJson
WHERE JSON_VALUE(Payload, '$.kunde.id') = N'42';
SET STATISTICS TIME OFF;
SET STATISTICS IO OFF;
Vergleichen Sie CPU-Zeit, Laufzeit und logische Lesezugriffe über mehrere Durchläufe. Kontrollieren Sie außerdem die tatsächlichen Ausführungspläne: Ein vollständiger Tabellenscan bleibt ein vollständiger Tabellenscan, auch mit binärem JSON.
Für den Speichervergleich eignen sich belegte Datenseiten, beispielsweise aus sys.dm_db_partition_stats. Vergleichen Sie identische Daten und Indexstrukturen. Die Länge eines zurückkonvertierten JSON-Texts beschreibt dagegen nicht die physische Speicherbelegung.
Praxistipp: Häufig gefilterte Eigenschaften können bei Textspeicherung über berechnete, passend typisierte Spalten indexiert werden. Ein nativer Datentyp ersetzt keine Indexstrategie. Neue JSON-spezifische Indexmöglichkeiten sollten Sie getrennt bewerten und deren Verfügbarkeit prüfen.
JSON_VALUE und JSON_QUERY: Werte und Fragmente auslesen
Ein JSON-Pfad ist die Adresse innerhalb eines Dokuments. $ bezeichnet die Wurzel, $.kunde.name die Eigenschaft name im Objekt kunde.
JSON_VALUE liefert einen einzelnen skalaren Wert, etwa Text oder eine Zahl. JSON_QUERY liefert dagegen ein Objekt oder Array:
SELECT
Id,
JSON_VALUE(Payload, '$.kunde.name') AS Kundenname,
TRY_CONVERT(decimal(12, 2),
JSON_VALUE(Payload, '$.gesamt')) AS Gesamt,
JSON_QUERY(Payload, '$.positionen') AS Positionen
FROM #DokumenteJson;
Ohne zusätzliche Rückgabetypangabe liefert JSON_VALUE grundsätzlich NVARCHAR(4000), nicht automatisch einen SQL-Zahlentyp. Für Berechnungen sollten Sie deshalb ausdrücklich konvertieren.
Standardmäßig gilt der lax-Modus: Fehlende Pfade oder ungeeignete Zielstrukturen ergeben in vielen Fällen NULL. Mit einem Pfad wie strict $.kunde.name werden entsprechende Strukturprobleme als Fehler sichtbar.
Eine häufige Falle sind lange Texte: Überschreitet ein skalarer Wert 4.000 Zeichen, liefert JSON_VALUE ohne Rückgabetypangabe im lax-Modus NULL, im strict-Modus einen Fehler. Für solche Inhalte eignet sich beispielsweise OPENJSON mit einer NVARCHAR(MAX)-Spalte.
OPENJSON: Dokumente in Tabellenform abfragen
OPENJSON verwandelt JSON-Inhalte in Zeilen. Zusammen mit CROSS APPLY können Sie damit jede Bestellung in ihre Positionen zerlegen:
SELECT
d.Id AS BestellungId,
p.Artikel,
p.Menge
FROM #DokumenteJson AS d
CROSS APPLY OPENJSON(
CAST(d.Payload AS nvarchar(max)),
'$.positionen'
)
WITH
(
Artikel nvarchar(100) '$.artikel',
Menge int '$.menge'
) AS p;
Die explizite Textkonvertierung vermeidet hier Unterschiede bei der direkten Unterstützung des nativen Datentyps durch OPENJSON. Berücksichtigen Sie ihre Kosten im Performancevergleich.
Die WITH-Klausel legt Namen, SQL-Datentypen und Pfade fest. Das ist meist übersichtlicher als viele einzelne Extraktionen. OPENJSON benötigt einen Datenbank-Kompatibilitätsgrad von mindestens 130.
Achten Sie auf die Zeilenzahl: Aus einer Bestellung mit zehn Positionen werden zehn Ergebniszeilen. Fehlt das Array oder ist es leer, liefert CROSS APPLY dafür keine Zeile. Soll die Bestellung trotzdem erscheinen, verwenden Sie OUTER APPLY.
JSON_OBJECTAGG und JSON_ARRAYAGG: Aus SQL-Abfragen JSON erzeugen
Aggregatfunktionen fassen mehrere Zeilen zusammen. JSON_ARRAYAGG erzeugt daraus ein Array, JSON_OBJECTAGG ein Objekt mit Schlüssel-Wert-Paaren:
DECLARE @Bestand TABLE
(
Bereich nvarchar(30) NOT NULL,
Artikel nvarchar(50) NOT NULL,
Menge int NOT NULL,
PRIMARY KEY (Bereich, Artikel)
);
INSERT INTO @Bestand (Bereich, Artikel, Menge)
VALUES
(N'Zubehör', N'Kabel', 12),
(N'Zubehör', N'Maus', 7),
(N'Eingabe', N'Tastatur', 4);
SELECT
Bereich,
JSON_ARRAYAGG(Artikel ORDER BY Artikel) AS Artikelliste,
JSON_OBJECTAGG(Artikel: Menge) AS Bestandsobjekt
FROM @Bestand
GROUP BY Bereich;
Für „Zubehör“ entstehen damit eine Artikelliste und ein Objekt, das Artikel auf Bestandszahlen abbildet. Die Funktionen liefern standardmäßig JSON-Text als NVARCHAR(MAX).
Definieren Sie die Array-Reihenfolge ausdrücklich, wenn sie fachlich wichtig ist. Bei Objekten sollten Schlüssel pro Gruppe eindeutig sein; im Beispiel garantiert das der Primärschlüssel.
Auch NULL verdient Aufmerksamkeit: JSON_ARRAYAGG lässt solche Werte standardmäßig weg, JSON_OBJECTAGG nimmt sie standardmäßig als JSON-null auf. Das kann für API-Verträge einen entscheidenden Unterschied machen.
JSON oder relationale Tabellen?
JSON passt gut zu variablen Zusatzattributen, externen Payloads und Konfigurationen, deren Struktur sich häufiger ändert.
Relationale Tabellen sind meist überlegen, wenn Sie:
- Beziehungen mit Foreign Keys absichern,
- Eigenschaften regelmäßig filtern, verbinden oder aggregieren,
- Eindeutigkeit und Pflichtfelder zuverlässig erzwingen,
- viele einzelne Werte unabhängig aktualisieren.
Oft gewinnt ein hybrides Modell: Bestellnummer, Kunde und Status stehen in regulären Spalten; selten benötigte Zusatzangaben im JSON-Dokument. Positionen gehören eher in eine eigene Tabelle, sobald Lagerbestand, Retouren oder Auswertungen daran hängen.
Fazit
Der native Datentyp json macht JSON zu einem besser integrierten Bestandteil von SQL Server. Er bietet Validierung beim Speichern und eine aufbereitete Darstellung mit möglichen Speicher- und Performancevorteilen.
Er macht aus einem Dokument aber kein relationales Schema. Modellieren Sie stabile Kerndaten relational, nutzen Sie JSON gezielt für flexible Bereiche – und lassen Sie realistische Messungen entscheiden, nicht das Etikett „nativ“.
Stand: November 2026. Funktionen, Versionen und Preise können sich seitdem geändert haben.
Sie möchten tiefer einsteigen? In unseren Schulungen vermitteln wir SQL Server, Power BI und KI praxisnah – online oder bei Ihnen vor Ort. Fragen? Kontaktieren Sie uns oder buchen Sie ein kostenloses Info-Gespräch.