First published on MSDN on Oct 06, 2015
CREATE TABLE Families (
--id int identity constraint PK_JSON_ID primary key,
doc nvarchar(max) CONSTRAINT [Properly formatted JSON] CHECK (ISJSON(doc)>0)
)
INSERT INTO Families
VALUES(N'{
"id": "AndersenFamily",
"lastName": "Andersen",
"parents": [
{ "firstName": "Thomas" },
{ "firstName": "Mary Kay"}
],
"children": [
{
"firstName": "Henriette Thaulow", "gender": "female", "grade": 5,
"pets": [{ "givenName": "Fluffy" }]
}
],
"address": { "state": "WA", "county": "King", "city": "seattle" },
"creationDate": 1431620472,
"isRegistered": true
}'),
(N'{
"id": "WakefieldFamily",
"parents": [
{ "familyName": "Wakefield", "givenName": "Robin" },
{ "familyName": "Miller", "givenName": "Ben" }
],
"children": [
{
"familyName": "Merriam",
"givenName": "Jesse",
"gender": "female", "grade": 1,
"pets": [
{ "givenName": "Goofy" },
{ "givenName": "Shadow" }
]
},
{
"familyName": "Miller",
"givenName": "Lisa",
"gender": "female",
"grade": 8 }
],
"address": { "state": "NY", "county": "Manhattan", "city": "NY" },
"creationDate": 1431620462,
"isRegistered": false
}')
SELECT *
FROM Families f
WHERE f.id = "AndersenFamily"
SELECT doc
FROM Families f
WHERE JSON_VALUE(f.doc, '$.id') = N'AndersenFamily'
SELECT *
FROM Families.address.state
SELECT JSON_VALUE(doc, '$.address.state')
FROM Families
WHERE JSON_VALUE(doc, '$.address.state') IS NOT NULL
SELECT {"Name":f.id, "City":f.address.city} AS Family
FROM Families f
WHERE f.address.city = f.address.state
SELECT JSON_VALUE(f.doc, '$.id') AS Name, JSON_VALUE(f.doc, '$.address.city') AS City
FROM Families f
WHERE JSON_VALUE(f.doc, '$.address.city') = JSON_VALUE(f.doc, '$.address.state')
SELECT Name, City
FROM Families f
CROSS APPLY OPENJSON(f.doc)
WITH ( Name nvarchar(100) '$.id',
City nvarchar(100) '$.address.city',
State nvarchar(100) '$.address.state')
WHERE City = State
SELECT id AS Name, [address.city] AS City
FROM Families f
CROSS APPLY OPENJSON(f.doc)
WITH ( id nvarchar(100),
[address.city] nvarchar(100),
[address.state] nvarchar(100))
WHERE [address.city] = [address.state]
SELECT *
FROM Families
WHERE Families.id IN ('AndersenFamily', 'WakefieldFamily')
SELECT *
FROM Families
WHERE JSON_VALUE(doc, '$.id') IN ('AndersenFamily', 'WakefieldFamily')
SELECT *
FROM Families.children
SELECT JSON_QUERY(doc, '$.children')
FROM Families
SELECT *
FROM c IN Families.children
SELECT c.value
FROM Families f
CROSS APPLY OPENJSON(f.doc, '$.children') as c
SELECT f.address
FROM Families f
WHERE f.id = "AndersenFamily"
SELECT JSON_QUERY(f.doc, '$.address')
FROM Families f
WHERE JSON_VALUE(f.doc, '$.id') = N'AndersenFamily'
SELECT address
FROM Families f
CROSS APPLY OPENJSON(f.doc)
WITH (id nvarchar(10), address nvarchar(max) AS JSON)
WHERE id = N'AndersenFamily'
SELECT *
FROM Families.children[0] c
WHERE c.grade BETWEEN 1 AND 5
SELECT f.*
FROM Families f
WHERE CAST(JSON_VALUE(f.doc, '$.children[0].grade') AS int) BETWEEN 1 AND 5
SELECT f.*
FROM Families f
CROSS APPLY OPENJSON(doc, '$.children[0]') WITH(grade int) c
WHERE c.grade BETWEEN 1 AND 5
SELECT c.givenName
FROM c IN Families.children
WHERE c.grade = 8
SELECT c.givenName
FROM Families f
CROSS APPLY OPENJSON(f.doc, '$.children')
WITH(grade int, givenName nvarchar(100)) c
WHERE c.grade = 8
SELECT c.givenName
FROM Families f
JOIN c IN f.children
WHERE f.id = 'WakefieldFamily'
ORDER BY f.address.city ASC
SELECT JSON_VALUE(c.value, '$.givenName') AS givenName
FROM Families f
CROSS APPLY OPENJSON(f.doc, '$.children') AS c
WHERE JSON_VALUE(f.doc, '$.id') = 'WakefieldFamily'
ORDER BY JSON_VALUE(f.doc, '$.address.city') ASC
SELECT JSON_VALUE(c.value, '$.givenName') AS givenName
FROM Families f
CROSS APPLY OPENJSON(f.doc, '$.children') AS c
WHERE JSON_VALUE(f.doc, '$.id') = 'WakefieldFamily'
ORDER BY JSON_VALUE(f.doc, '$.address.city') ASC
FOR JSON PATH
SELECT f.id
FROM Families f
JOIN f.NonExistent
SELECT Families.*
FROM Families
CROSS APPLY OPENJSON(doc, '$.NonExistent')
for-each(Family f in Families)
{
for-each(Child c in f.children)
{
for-each(Pet p in c.pets)
{
return (Tuple(f.id AS familyName,
c.givenName AS childGivenName,
c.firstName AS childFirstName,
p.givenName AS petName));
}
}
}
SELECT
f.id AS familyName,
c.givenName AS childGivenName,
c.firstName AS childFirstName,
p.givenName AS petName
FROM Families f
JOIN c IN f.children
JOIN p IN c.pets
SELECT JSON_VALUE(f.doc, '$.id') AS familyName,
JSON_VALUE(c.value, '$.givenName') AS childGivenName,
JSON_VALUE(c.value, '$.firstName') AS childFirstName,
JSON_VALUE(p.value, '$.givenName') AS petName
FROM Families f
CROSS APPLY OPENJSON(f.doc, '$.children') as c
CROSS APPLY OPENJSON (c.value, '$.pets') as p
SELECT JSON_VALUE(f.doc, '$.id') AS familyName,
c.givenName AS childGivenName,
c.firstName AS childFirstName,
p.givenName AS petName
FROM Families f
CROSS APPLY OPENJSON(f.value, '$.children')
WITH (givenName nvarchar(100), firstName nvarchar(100), pets nvarchar(max) AS JSON) as c
CROSS APPLY OPENJSON (pets)
WITH (givenName nvarchar(100)) as p
SELECT familyName,
c.givenName AS childGivenName,
c.firstName AS childFirstName,
p.givenName AS petName
FROM Families f
CROSS APPLY OPENJSON(f.value)
WITH (familyName nvarchar(100), children nvarchar(max) AS JSON)
CROSS APPLY OPENJSON(children)
WITH (givenName nvarchar(100), firstName nvarchar(100), pets nvarchar(max) AS JSON) as c
CROSS APPLY OPENJSON (pets)
WITH (givenName nvarchar(100)) as p
SQL Server JSON query syntax compared to DocumentDB
In this article I will show you similarities and differences between SQl Server JSON and DocumentDB query syntax.
SQL Server 2016/Azure SQL Db are relational databases that will have support for handling JSON data. JSON support in SQL Server uses standard T-SQL syntax for querying JSON data with some additional built-in functions that enables you to read values of objects from JSON data. JSON can be used with all components/features in SQL Server such as In-memory OLTP, Column store, temporal, etc.
DocumentDB is a write optimized and schema agnostic document database purpose built for JSON and JavaScript; it does not require any schema or secondary indices in order to serve queries. Being a database designed for JSON and JavaScript, DocumentDB uses JavaScript as its underlying type system and supports both SQL as well as JavaScript queries.
Both SQL Server 2016 and DocumentDB enable you to query JSON documents.DocumentDB has nice syntax for querying JSON documents – you can find some good examples on DocumentDB site . Sql Server provides built-in functions for accessing JSON fields (JSON_VALUE), fragments (JSON_QUERY) and opening JSON documents (OPENJSON). In this post, I will show you some queries that can be executed in DocumentDB and equivalent Sql Server queries.
I have used DocumentDB as a reference because it has easy-to-understand syntax, so I believe that you will easily understand how to use equivalent Sql Server queries.
JSON Collections
DocumentDB stores JSON documents in collections. Since Sql Server does not have collections we will create simple table that would contain documents same as in the DocumentDB site :
CREATE TABLE Families (
--id int identity constraint PK_JSON_ID primary key,
doc nvarchar(max) CONSTRAINT [Properly formatted JSON] CHECK (ISJSON(doc)>0)
)
INSERT INTO Families
VALUES(N'{
"id": "AndersenFamily",
"lastName": "Andersen",
"parents": [
{ "firstName": "Thomas" },
{ "firstName": "Mary Kay"}
],
"children": [
{
"firstName": "Henriette Thaulow", "gender": "female", "grade": 5,
"pets": [{ "givenName": "Fluffy" }]
}
],
"address": { "state": "WA", "county": "King", "city": "seattle" },
"creationDate": 1431620472,
"isRegistered": true
}'),
(N'{
"id": "WakefieldFamily",
"parents": [
{ "familyName": "Wakefield", "givenName": "Robin" },
{ "familyName": "Miller", "givenName": "Ben" }
],
"children": [
{
"familyName": "Merriam",
"givenName": "Jesse",
"gender": "female", "grade": 1,
"pets": [
{ "givenName": "Goofy" },
{ "givenName": "Shadow" }
]
},
{
"familyName": "Miller",
"givenName": "Lisa",
"gender": "female",
"grade": 8 }
],
"address": { "state": "NY", "county": "Manhattan", "city": "NY" },
"creationDate": 1431620462,
"isRegistered": false
}')
You can optionally add a primary key or any other index if you want; however, this is not required for this tutorial. The same collection is created in DocumentDB examples , so you can easily compare results of execution in both systems.
Basic queries
We can start with the simplest queries. For example, the following query will return the documents where the id field matches AndersenFamily:
SELECT *
FROM Families f
WHERE f.id = "AndersenFamily"
Equivalent query in Sql Server would be:
SELECT doc
FROM Families f
WHERE JSON_VALUE(f.doc, '$.id') = N'AndersenFamily'
In Sql Server you can use JSON_VALUE function to access value of JSON property on some path.
In DocumentDB you can select all states in address objects in family documents:
SELECT *
FROM Families.address.state
Equivalent Sql Server query would be:
SELECT JSON_VALUE(doc, '$.address.state')
FROM Families
WHERE JSON_VALUE(doc, '$.address.state') IS NOT NULL
In DocumentDB If some families don’t have an address.state value, they will be excluded in the query result. Therefore we need to add WHERE clause to exclude them because in SQL Server they will be returned as NULL values.
This query projects a Name and City, when the address' city has the same name as the state. In this case, "NY, NY" matches.
SELECT {"Name":f.id, "City":f.address.city} AS Family
FROM Families f
WHERE f.address.city = f.address.state
Equivalent Sql Server query is:
SELECT JSON_VALUE(f.doc, '$.id') AS Name, JSON_VALUE(f.doc, '$.address.city') AS City
FROM Families f
WHERE JSON_VALUE(f.doc, '$.address.city') = JSON_VALUE(f.doc, '$.address.state')
SQL Server provides you separate function OPENJSON that can open json document in the doc column and you can specify what fields you want to use as well as the types of the fields:
SELECT Name, City
FROM Families f
CROSS APPLY OPENJSON(f.doc)
WITH ( Name nvarchar(100) '$.id',
City nvarchar(100) '$.address.city',
State nvarchar(100) '$.address.state')
WHERE City = State
Beside the fact that fields are strongly types you can directly use their aliases in the queries. This might help if you have a lot of properties in the same query.
If you don’t want to use path you can name you property following path syntax:
SELECT id AS Name, [address.city] AS City
FROM Families f
CROSS APPLY OPENJSON(f.doc)
WITH ( id nvarchar(100),
[address.city] nvarchar(100),
[address.state] nvarchar(100))
WHERE [address.city] = [address.state]
Sql Server will use path syntax in the property names and lookup properties on the same paths.
In DocumentDB the IN keyword can be used to check whether a specified value matches any value in a list. For example, this query returns all family documents where the id is one of "WakefieldFamily" or "AndersenFamily".
SELECT *
FROM Families
WHERE Families.id IN ('AndersenFamily', 'WakefieldFamily')
Equivalent Sql Server query would use standard T-Sql IN predicate:
SELECT *
FROM Families
WHERE JSON_VALUE(doc, '$.id') IN ('AndersenFamily', 'WakefieldFamily')
Accessing sub entities
If you have complex JSON documents you might want to access various sub-entities such as children or pets in the example above. The following DocumentDB query returns all children objects from family documents:
SELECT *
FROM Families.children
Equivalent Sql server query would look like:
SELECT JSON_QUERY(doc, '$.children')
FROM Families
JSON_QUERY function is similar to JSON_VALUE. The main difference is that it returns JSON fragments (e.g. entire JSON sub-objects or sub-arrays within the documents), while JSON_VALUE returns scalars (i.e. number, strings true/false values). In this case we are using this function to return child objects at $.children path.
Now let's look at another query that performs iteration over children in the collection. Note the difference in the output array. This example splits children and flattens the results into a single array.
SELECT *
FROM c IN Families.children
Equivalent Sql server query is:
SELECT c.value
FROM Families f
CROSS APPLY OPENJSON(f.doc, '$.children') as c
OPENJSON will open all JSON objects in the $.children array and return them as dynamically created table rows. Each JSON element in the array is returned as value column.
In DocumentDB we can combine fragments and scalar values in the same query:
SELECT f.address
FROM Families f
WHERE f.id = "AndersenFamily"
Equivalent Sql server query would combine JSON_VALUE and JSON_QUERY functions:
SELECT JSON_QUERY(f.doc, '$.address')
FROM Families f
WHERE JSON_VALUE(f.doc, '$.id') = N'AndersenFamily'
Equivalent query with OPENJSON function is:
SELECT address
FROM Families f
CROSS APPLY OPENJSON(f.doc)
WITH (id nvarchar(10), address nvarchar(max) AS JSON)
WHERE id = N'AndersenFamily'
Specifying columns in WITH clause is equivalent to JSON_VALUE function. If you want to reference sub-object (like in JSON_QUERY) you would need to specify NVARCHAR(max) type and add AS JSON clause. Without AS JSON clause, OPENJSON will assume that you want scalar property with the same name and since it will not find it, it will return NULL.
The following query returns all family documents in which the first child's grade is between 1-5 (both inclusive):
SELECT *
FROM Families.children[0] c
WHERE c.grade BETWEEN 1 AND 5
In Sql Server we can use one the following query:
SELECT f.*
FROM Families f
WHERE CAST(JSON_VALUE(f.doc, '$.children[0].grade') AS int) BETWEEN 1 AND 5
If you want to avoid CAST function, you can use OPENJSON with WITH schema:
SELECT f.*
FROM Families f
CROSS APPLY OPENJSON(doc, '$.children[0]') WITH(grade int) c
WHERE c.grade BETWEEN 1 AND 5
Following query returns names of children at eight grades:
SELECT c.givenName
FROM c IN Families.children
WHERE c.grade = 8
Equivalent Sql Server query is:
SELECT c.givenName
FROM Families f
CROSS APPLY OPENJSON(f.doc, '$.children')
WITH(grade int, givenName nvarchar(100)) c
WHERE c.grade = 8
JOINS
One of the important functionalities is ability to join parent object with their child entities. In DocumentDB you use JOIN clause to join families and their children the same way as yo would do in Sql Server with relational tables:
SELECT c.givenName
FROM Families f
JOIN c IN f.children
WHERE f.id = 'WakefieldFamily'
ORDER BY f.address.city ASC
Joins between parent and child objects are similar to joins between parent and child tables in Sql server. If you want to join parent and child JSON entities in Sql Server, you can open it using CROSS APPLY operator:
SELECT JSON_VALUE(c.value, '$.givenName') AS givenName
FROM Families f
CROSS APPLY OPENJSON(f.doc, '$.children') AS c
WHERE JSON_VALUE(f.doc, '$.id') = 'WakefieldFamily'
ORDER BY JSON_VALUE(f.doc, '$.address.city') ASC
You can also format results as JSON text using FOR JSON clause:
SELECT JSON_VALUE(c.value, '$.givenName') AS givenName
FROM Families f
CROSS APPLY OPENJSON(f.doc, '$.children') AS c
WHERE JSON_VALUE(f.doc, '$.id') = 'WakefieldFamily'
ORDER BY JSON_VALUE(f.doc, '$.address.city') ASC
FOR JSON PATH
Behavior of JOIN in DocumentDB is similar to the behavior of CROSS APPLY in Sql Server. In the following example, the result is empty since the cross product of each document from source and an empty set is empty:
SELECT f.id
FROM Families f
JOIN f.NonExistent
In Sql server you will also not get any results if CROSS APPLY references non-existing array:
SELECT Families.*
FROM Families
CROSS APPLY OPENJSON(doc, '$.NonExistent')
In the most complex case, you might join several parent/child objects to get something like the following pseudo-code:
for-each(Family f in Families)
{
for-each(Child c in f.children)
{
for-each(Pet p in c.pets)
{
return (Tuple(f.id AS familyName,
c.givenName AS childGivenName,
c.firstName AS childFirstName,
p.givenName AS petName));
}
}
}
In DocumentDB you can use multiple JOIN conditions:
SELECT
f.id AS familyName,
c.givenName AS childGivenName,
c.firstName AS childFirstName,
p.givenName AS petName
FROM Families f
JOIN c IN f.children
JOIN p IN c.pets
In Sql Server you would use multiple CROSS APPLY operators, as it is shown in the following query:
SELECT JSON_VALUE(f.doc, '$.id') AS familyName,
JSON_VALUE(c.value, '$.givenName') AS childGivenName,
JSON_VALUE(c.value, '$.firstName') AS childFirstName,
JSON_VALUE(p.value, '$.givenName') AS petName
FROM Families f
CROSS APPLY OPENJSON(f.doc, '$.children') as c
CROSS APPLY OPENJSON (c.value, '$.pets') as p
As an alternative you can use OPENJSON with schema:
SELECT JSON_VALUE(f.doc, '$.id') AS familyName,
c.givenName AS childGivenName,
c.firstName AS childFirstName,
p.givenName AS petName
FROM Families f
CROSS APPLY OPENJSON(f.value, '$.children')
WITH (givenName nvarchar(100), firstName nvarchar(100), pets nvarchar(max) AS JSON) as c
CROSS APPLY OPENJSON (pets)
WITH (givenName nvarchar(100)) as p
Or:
SELECT familyName,
c.givenName AS childGivenName,
c.firstName AS childFirstName,
p.givenName AS petName
FROM Families f
CROSS APPLY OPENJSON(f.value)
WITH (familyName nvarchar(100), children nvarchar(max) AS JSON)
CROSS APPLY OPENJSON(children)
WITH (givenName nvarchar(100), firstName nvarchar(100), pets nvarchar(max) AS JSON) as c
CROSS APPLY OPENJSON (pets)
WITH (givenName nvarchar(100)) as p
Although syntax in SQL Server and DocumentDB is slightly different, you might notice that most of the queries you can write in both systems.
References
JSON Data (SQL Server) - MSDN - Microsoft
https://azure.microsoft.com/en-us/documentation/articles/documentdb-sql-query/
https://azure.microsoft.com/en-us/blog/azure-documentdb-javascript-as-modern-day-t-sql/
Updated Mar 23, 2019
Version 2.0JovanPop
Microsoft
Joined March 07, 2019
SQL Server Blog
Follow this blog board to get notified when there's new activity