Chapter 06 · Advanced T-SQL: Windows, Aggregation, PIVOT, MERGE Alternatives, and JSON

JSON Functions, OPENJSON, JSON Construction, Indexing Strategies, and API Workloads

Use stable SQL Server JSON functions for API workloads, typed OPENJSON ingestion and computed-property indexing while separating SQL Server 2025 native JSON preview capabilities from the mandatory production-safe path.

Intermediate → Advanced120–155 minutesJSON ingest/query/index labSQL Server 2025 CU7 · compatibility 170Native json/JSON index clearly labeled PREVIEWLast reviewed: August 2026

Learning outcomes

ServiceHub receives partner API payloads containing flexible attributes that do not justify a new relational column for every optional field. A team stores raw text and uses string searches; another stores JSON but never validates it; a third wants to adopt SQL Server 2025 native json columns and JSON indexes immediately because “2025 is GA.” The correct design separates stable relational keys from semi-structured payloads, stable JSON functions from SQL Server 2025 preview features, and syntax validity from business-schema validity.

01

Validate and query JSON text with ISJSON, JSON_VALUE, JSON_QUERY, and OPENJSON while defining path and type expectations.

02

Shred arrays/objects with OPENJSON using an explicit WITH schema instead of leaking untyped text into relational logic.

03

Construct API results with FOR JSON while keeping ordering, null policy, and nesting explicit.

04

Index frequently searched JSON properties through typed computed extraction where appropriate and verify the resulting access path.

05

Distinguish stable nvarchar-based JSON support from SQL Server 2025 native json and CREATE JSON INDEX preview status before production adoption.

Current SQL Server 2025 status

As of this chapter review, Microsoft documents the native json data type as preview for SQL Server 2025 (17.x), although it is GA in some Azure offerings. CREATE JSON INDEX is also preview in SQL Server 2025. Mandatory labs therefore use mature nvarchar(max) JSON functionality; preview examples are optional and clearly separated.

1. JSON is a representation, not a replacement for relational invariants

sql · create a stable nvarchar JSON inbox
USE ServiceHubLab;GOIF SCHEMA_ID(N'lab06') IS NULL EXEC(N'CREATE SCHEMA lab06 AUTHORIZATION dbo;');DROP TABLE IF EXISTS lab06.ApiInbox;GOCREATE TABLE lab06.ApiInbox(  message_id bigint IDENTITY(1,1) NOT NULL CONSTRAINT PK_lab06_ApiInbox PRIMARY KEY,  source_system varchar(30) NOT NULL,  received_at datetime2(3) NOT NULL CONSTRAINT DF_lab06_ApiInbox_Received DEFAULT SYSUTCDATETIME(),  payload nvarchar(max) NOT NULL,  CONSTRAINT CK_lab06_ApiInbox_IsJson CHECK (ISJSON(payload)=1));INSERT lab06.ApiInbox(source_system,payload) VALUES('partnerA',N'{"workOrderId":5001,"priority":3,"customer":{"tier":"gold"},"tags":["urgent","hvac"]}'),('partnerA',N'{"workOrderId":5002,"priority":1,"customer":{"tier":"standard"},"tags":["inspection"]}');GO

ISJSON protects syntactic JSON validity. It does not prove that workOrderId exists, is unique, is a number, or refers to a real ServiceHub work order. Those are business constraints that belong in relational columns, typed extraction, validation code, or a defined ingestion contract.

2. Scalar, object, and rowset extraction are different operations

JSON_VALUE extracts a scalar value. JSON_QUERY returns an object or array fragment. OPENJSON turns JSON into rows; with an explicit WITH clause it also converts selected paths to declared SQL types. Prefer typed extraction at ingestion/query boundaries so failures and nullability are deliberate.

sql · extract typed properties and array elements
USE ServiceHubLab;GOSELECT message_id,       TRY_CONVERT(bigint, JSON_VALUE(payload,'$.workOrderId')) AS work_order_id,       TRY_CONVERT(int, JSON_VALUE(payload,'$.priority')) AS priority,       JSON_VALUE(payload,'$.customer.tier') AS customer_tier,       JSON_QUERY(payload,'$.tags') AS tags_jsonFROM lab06.ApiInboxORDER BY message_id;GOSELECT i.message_id, j.[value] AS tagFROM lab06.ApiInbox AS iCROSS APPLY OPENJSON(i.payload,'$.tags') AS jORDER BY i.message_id,j.[key];GO

The array query uses OPENJSON because a collection is naturally a rowset. Do not split JSON arrays with string functions. If a required path is missing or has the wrong type, decide whether ingestion should reject the message, quarantine it, or store a nullable result; silent coercion is an application policy, not a JSON feature.

3. OPENJSON WITH is a typed ingestion boundary

sql · project JSON into typed relational columns
USE ServiceHubLab;GOSELECT i.message_id, x.work_order_id, x.priority, x.customer_tierFROM lab06.ApiInbox AS iCROSS APPLY OPENJSON(i.payload)WITH(  work_order_id bigint '$.workOrderId',  priority int '$.priority',  customer_tier nvarchar(20) '$.customer.tier') AS xORDER BY i.message_id;GO

This schema is explicit and reviewable. It also prevents downstream queries from repeatedly guessing lengths and types. OPENJSON is available at database compatibility level 130 or higher; the course baseline is 170, so the mandatory lab satisfies that prerequisite.

4. FOR JSON defines an API serialization contract

FOR JSON PATH serializes a rowset. Column aliases can create nested property paths, and options control behaviors such as null inclusion and outer array wrapping. The database can be an effective serializer for bounded API shapes, but large unbounded documents can increase CPU, memory, network payload, and coupling. Keep pagination and field ownership explicit.

sql · construct a small ordered API result
USE ServiceHubLab;GOSELECT TOP (2)       TRY_CONVERT(bigint,JSON_VALUE(payload,'$.workOrderId')) AS [workOrder.id],       TRY_CONVERT(int,JSON_VALUE(payload,'$.priority')) AS [workOrder.priority],       JSON_VALUE(payload,'$.customer.tier') AS [customer.tier]FROM lab06.ApiInboxORDER BY message_idFOR JSON PATH, ROOT('messages');GO

The outer SELECT determines which rows and their order; FOR JSON formats that result. If the API requires stable pagination, apply the deterministic ordering rules from Chapter 05. JSON serialization does not create consistency across independent requests.

5. Index the property you actually search, with an appropriate SQL type

For long-established SQL Server JSON designs, a computed column can expose a frequently queried scalar property and an ordinary index can target that expression. Cast/extract to the narrowest correct SQL type rather than indexing an unconstrained text expression. This gives the optimizer ordinary relational statistics and access paths while retaining the original payload.

sql · typed computed extraction and index
USE ServiceHubLab;GOALTER TABLE lab06.ApiInboxADD work_order_id AS TRY_CONVERT(bigint,JSON_VALUE(payload,'$.workOrderId'));GOCREATE INDEX IX_lab06_ApiInbox_WorkOrderIdON lab06.ApiInbox(work_order_id);GOSELECT message_id,source_system,work_order_idFROM lab06.ApiInboxWHERE work_order_id=5002;GO

Capture the actual plan at realistic scale. Two-row lab data may still scan because scanning is cheaper. The lesson proves indexability and schema design, not a fabricated performance win. A computed index also adds write/storage/statistics maintenance, so create it for measured search patterns rather than every possible JSON path.

6. SQL Server 2025 native JSON and JSON indexes are preview on SQL Server

SQL Server 2025 adds a native binary json type, and current Microsoft documentation marks that type as preview for SQL Server 2025. CREATE JSON INDEX is likewise preview and currently requires, among other documented restrictions, a clustered primary key on the table. Feature availability differs from Azure SQL Database/Managed Instance, so “works in Azure” does not automatically mean the on-prem SQL Server 2025 feature is GA.

sql · optional preview exploration—do not require in the course lab
-- OPTIONAL PREVIEW ONLY: verify current docs/build and enable/use preview features as required.-- Do not deploy this snippet as a production-default recommendation.CREATE TABLE lab06.JsonPreviewExample(  id bigint NOT NULL PRIMARY KEY CLUSTERED,  doc json NOT NULL);-- CREATE JSON INDEX IX_JsonPreviewExample_Doc--   ON lab06.JsonPreviewExample(doc) FOR ('$.workOrderId');

Preview status can change after this lesson is published. Re-check Microsoft Learn, release notes, servicing notes, and any preview-enable requirements on the exact build before testing. The mandatory course path remains functional without the preview type/index.

Wrong approach

“SQL Server 2025 is GA, so every feature shown under SQL Server 2025 is production GA.” Product GA and feature GA are separate statuses. Current documentation explicitly labels the native json type and CREATE JSON INDEX as preview for SQL Server 2025.

7. Production judgment and chapter cleanup

Use JSON for attributes whose shape genuinely benefits from semi-structured storage, but keep stable identifiers, relationships, security boundaries, and high-value predicates relational when that improves integrity and operability. Validate syntax, define business schema rules, bound document size, protect sensitive fields, and monitor parse/extraction CPU plus computed-index write cost. Avoid logging entire sensitive payloads by default.

sql · cleanup disposable Chapter 06 objects
USE ServiceHubLab;GODROP TABLE IF EXISTS lab06.JsonPreviewExample;DROP TABLE IF EXISTS lab06.ApiInbox;DROP TABLE IF EXISTS lab06.ExternalTicket;DROP TABLE IF EXISTS lab06.TechnicianMetric;DROP TABLE IF EXISTS lab06.InvoiceLine;DROP TABLE IF EXISTS lab06.WorkEvent;GOIF SCHEMA_ID(N'lab06') IS NOT NULL   AND NOT EXISTS (SELECT 1 FROM sys.objects WHERE schema_id=SCHEMA_ID(N'lab06'))  EXEC(N'DROP SCHEMA lab06;');GO

Chapter 06 completes the advanced query-shaping layer: windows analyze detail rows, advanced GROUP BY produces multiple grains, PIVOT reshapes controlled dimensions, OUTPUT/UPSERT logic handles writes under concurrency, and JSON bridges semi-structured API payloads. Chapter 07 moves into transactions, locking, row versioning, isolation, and deadlocks—the mechanisms that explain the concurrency tradeoffs introduced here.

Check your understanding

  1. What does ISJSON prove, and what does it not prove?
  2. When is OPENJSON preferable to JSON_VALUE?
  3. Why use a typed computed column for a frequently searched JSON property?
  4. What is the SQL Server 2025 status of the native json type in the current documentation?
  5. Why is CREATE JSON INDEX not a mandatory production recommendation in this course chapter?
Review the answers

It proves syntactic JSON validity for the checked expression; it does not prove required business fields, types, relationships, or uniqueness.

When the JSON value is naturally a rowset/array/object that needs shredding, especially with an explicit typed WITH schema.

It gives the property a stable SQL type and allows ordinary indexing/statistics for that search path.

Preview for SQL Server 2025, even though the feature is GA in some Azure SQL offerings.

Current Microsoft documentation marks it preview in SQL Server 2025, so the stable mandatory path uses mature nvarchar JSON plus relational/computed indexing.

Authoritative references

Keep knowledge open

Help the academy stay free and grow.

If these tutorials save you time, a small donation supports new lessons, technical review, diagrams, examples, and long-term maintenance.

ETHEthereum / ERC-20 only
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0

Send only Ethereum or ERC-20 compatible assets to this address.