SQL Server Json
SQL Server Json
SQL Server has quite a few features for storing and working with JSON data.
If you want to download a PDF version of this guide, enter your email below and you'll receive it
shortly.
Data can be represented in a JSON format in SQL Server so it can be read and understood by
other applications or parts of an application.
It's similar to HTML or XML - it represents your data in a certain format that is readable by people
but designed to be readable by applications.
A normalised database structure, one with tables and columns and relationships, works well for
most cases. Recent improvements in development practices also mean that altering a table is not
as major as it was in the past, so adjusting your database once it's in production is possible.
1
JSON in SQL Server
However, if your requirements mean that your data structure needs to be flexible, then a JSON
field may be good for your database.
One example may be where a user can add custom attributes. If it was done using a normalised
database, this may involve altering tables, or creating an Entity Attribute Value design, or some
other method.
If a JSON field was used for this, it would be much easier to add and maintain these custom
attributes.
The JSON data can also be stored in your database and processed by an ORM (Object Relational
Mapper) or your application code, so your database may not need to do any extra work.
{
"id": "1",
"username": "sstorm",
"location": "United States"
}
It uses a combination of different brackets, colons, and quotes to represent your data.
Name/Value Pair
JSON data is written as name/value pairs. A name/value pair is two values enclosed in quotes.
"username": "sstorm"
The name is "username" and the value is "sstorm". They are separated by a colon ":".
This means for the attribute of username, the value is sstorm. Names in JSON need to be enclosed
in double quotes.
2
JSON in SQL Server
Objects
JSON data can be enclosed in curly brackets which indicate it's an object.
{"username": "sstorm"}
This is the same data as the earlier example, but it's now an object. This means it can be treated as
a single unit by other areas of the application.
How does this help? It's good for when there are multiple attributes:
{
"username": "sstorm",
"location": "United States"
}
Additional attributes, or name/value pairs, can be added by using a comma to separate them.
You'll also notice in this example the curly brackets are on their own lines and the data is
indentented. This is optional: it's just done to make it more readable.
Arrays
JSON also supports arrays, which is a collection of records within an object. Arrays in JSON are
included in square brackets and have a name:
{
"username": "sstorm",
"location": "United States",
"posts": [
{
"id":"1",
"title":"Welcome"
},
{
"id":"4",
"title":"What started it all"
}
]
}
In this example, this object has an attribute called "posts". The value of posts is an array, which we
can see by the opening square bracket "[".
3
JSON in SQL Server
Inside the square bracket, we have a set of curly brackets, indicating an object, and inside those we
have an id of 1 and a title of Welcome. We have another set of curly brackets indicating another
object.
These two objects are posts and they are contained in an array.
If JSON is new to you, don't worry, it gets easier as you work with it more.
If you're experienced with JSON, you'll find the rest of the guide more useful as we go into the
details of working with JSON in SQL Server.
We create a new field to store our JSON data. Unlike other databases, SQL Server does not have a
JSON-specific data type. However, we can use an NVARCHAR field. We can also add a constraint
to ensure it is valid JSON.
Here's an example.
4
JSON in SQL Server
We have created a table called product. It has an id and a product name. There's also an attributes
column, which has the data type of NVARCHAR. The length of MAX allows up to 2GB of data.
What about validation? How do we know the text we add to this field is valid JSON and not just a
text string?
We can add a Check Constraint to the table to ensure the data is valid. Check Constraints are
inspected whenever data is added or updated in the table.
Or, we could add the constraint at the time we create the table:
The ISJSON field returns 1 if the specified field is JSON-compliant. In this case, whenever we make
changes to the attributes field, it must be a valid JSON document so that this check constraint
passes.
With this JSON validation, the data is automatically validated for us. We won't be able to store
invalid data in the table.
Also, we get to use the various SQL Server JSON functions on the JSON data to make working
with it easier.
Now we've got our field for storing JSON data, how do we add data to it?
We simply insert a record into our table as though it's a text value.
5
JSON in SQL Server
With the check constraint that uses the ISJSON function, the data will be validated before it is
inserted, and only valid data will be inserted.
We can run this statement and the record is inserted. If we select the data from the table, this is
what we see:
SELECT
id,
product_name,
attributes
FROM product;
id product_name attributes
1 Chair {"color": "brown", "material":"wood", "height":"60cm"}
Using the method above, we needed to enter in the data in exactly the right format.
6
JSON in SQL Server
Inserting Arrays
If you want to insert JSON data that contains arrays, you can enter it using text in a JSON format.
This will insert a new product that has an array of drawers. As you can probably see by this
statement, reading it (and writing it) is a bit tricky.
The INSERT statements will work, and the data will look like this:
SELECT
id,
product_name,
attributes
FROM product;
id product_name attributes
1 Chair {"color": "brown", "material":"wood", "height":"60cm"}
{"color": "black", "drawers": [{"side": "left", "height": "30cm"}, {"side": "left",
2 Desk "height": "40cm"}], "material": "metal"}
3 Side Table {"color": "brown", "material": ["metal", "wood"]}
How do we do that?
7
JSON in SQL Server
We can run a simple SELECT statement to see the data in the table.
SELECT
id,
product_name,
attributes
FROM product;
id product_name attributes
1 Chair {"color": "brown", "material":"wood", "height":"60cm"}
{"color": "black", "drawers": [{"side": "left", "height": "30cm"}, {"side": "left",
2 Desk "height": "40cm"}], "material": "metal"}
3 Side Table {"color": "brown", "material": ["metal", "wood"]}
This shows us the data in the JSON column, and it looks just like a text value.
The good thing with this is that any application can easily read this field and work with it how they
want (display it, filter it, and so on).
The JSON data is stored in something that looks like a text field. However, it's quite easy to get
attributes and values out of this text field and display them.
We can extract a value from the JSON field and display it in a separate column. We do this using a
combination of "path expressions" and the JSON_VALUE function.
We need to use the JSON_VALUE function to search for a particular attribute in the JSON value.
And we need to use a "path expression" to specify the attribute.
How do we do this?
First, let's write the path expression, and then put that into our JSON_VALUE function.
The path expression lets us specify the attribute we want to search for. It starts with a $ symbol,
and we specify a dot then the name of the attribute we're looking for.
8
JSON in SQL Server
For example, to specify the "color" attribute, out path expression would look like this:
'$.color'
'$.material'
If we had a height attribute enclosed in a dimensions attribute, our path expression would look like
this:
'$.dimensions.height'
We use the dot to specify the next level in the hierarchy of attributes.
How do we use this to filter our data? We combine this path expression with the JSON_VALUE
function.
Let's say we want to display the color attribute in a separate column in our results.
'$.color'
JSON_VALUE(attributes, '$.color')
SELECT
id,
9
JSON in SQL Server
product_name,
JSON_VALUE(attributes, '$.color') AS color,
attributes
FROM product;
This query will show all records, and show the color attribute as a separate column.
We can see another field using JSON_VALUE by specifying the attribute name:
SELECT
id,
product_name,
JSON_VALUE(attributes, '$.height') AS height,
attributes
FROM product;
Here we are extracting the attribute called height. This is available in some records but not others.
A null value is shown for records that don't have this attribute.
What about attributes that are arrays, such as "material" in this example?
10
JSON in SQL Server
SELECT
id,
product_name,
JSON_VALUE(attributes, '$.material') AS material,
attributes
FROM product;
What if we want to see an attribute that's inside another attribute? For example, the first of the
"drawer" attributes?
Because "drawer" is an array, we can't use the dot notation to get the attribute like this:
JSON_VALUE(attributes, '$.drawers.side')
This will return a null value as there is no attribute called side: it's part of an array.
JSON_VALUE(attributes, '$.drawers[0]')
The second object can be found using [1], the third object using [2], and so on.
So, our query to extract the first item in the array is:
SELECT
id,
product_name,
JSON_VALUE(attributes, '$.drawers[0]') AS drawer,
attributes
FROM product;
11
JSON in SQL Server
The only record that has an attribute of "drawer" is id 2, the Chair. But why isn't it showing in the
results?
It's because the drawers attribute is an array. It contains a series of other attributes, unlike color
and height and material which are single values.
The JSON_VALUE function only returns single values, or "scalar values" as they are called in the
documentation.
It works in the same way. We provide a JSON field and a path expression.
SELECT
id,
product_name,
JSON_QUERY(attributes, '$.drawers[0]') AS drawer,
attributes
FROM product;
We can see the differences in the results using the JSON_QUERY function. It shows the drawer
attribute.
So, as you can see, there are a range of ways you can use the JSON functions with a path
expression to get the attribute you want.
12
JSON in SQL Server
Let's say we wanted to see our Chair product, which has a brown color, wood material, and a
height of 60cm. But we want to filter on the JSON attributes for this example.
SELECT
id,
product_name,
attributes
FROM product
WHERE attributes = '{"color":"brown", "material":"wood",
"height":"60cm"}';
id product_name attributes
1 Chair {"color":"brown", "material":"wood", "height":"60cm"}
This works because the column is simply a NVARCHAR column, and as long as we provide the
exact full string, it will work.
Often we need to filter on one of the keys or values. How can we do that?
SELECT
id,
product_name,
attributes
FROM product
WHERE attributes LIKE '%"color":"brown"%';
id product_name attributes
1 Chair {"color":"brown", "material":"wood", "height":"60cm"}
3 Side Table {"color": "brown", "material": ["metal", "wood"]}
This does give us the result we want. However, using wildcard searches can be quite slow if there
is a lot of data in the table.
There are several features in SQL Server that make it possible to filter on JSON data.
13
JSON in SQL Server
Let's say we want to find all products where the color is brown. The color is a part of the attributes
JSON column in our table.
'$.color'
JSON_VALUE(attributes, '$.color')
SELECT
id,
product_name,
attributes
FROM product
WHERE JSON_VALUE(attributes, '$.color') = 'brown';
We've added this JSON_VALUE function to our WHERE clause, and added a condition where the
color is equal to "brown".
id product_name attributes
1 Chair {"color":"brown", "material":"wood", "height":"60cm"}
3 Side Table {"color": "brown", "material": ["metal", "wood"]}
We can see that the results only show records where the color attribute is brown.
So far we've seen examples of using different functions to read and filter JSON data.
14
JSON in SQL Server
The result has been that all of the JSON data has been displayed in a single column.
SQL Server includes a function that lets you expand the JSON fields and display them in a table.
This table shows a few columns: key, value, and type.
This can be useful for data in your tables, or if you're loading JSON from an external source and
want to translate it into your database tables.
OPENJSON(json_doc)
Here's what it looks like in our database. We'll use it on a JSON value, then see how to use it for
our table.
SELECT *
FROM OPENJSON(@json);
color brown 1
material wood 1
height 60cm 1
You can use this method even if there are arrays inside the JSON data.
15
JSON in SQL Server
';
SELECT *
FROM OPENJSON(@json);
color brown 1
Another way you can use this function is to specify the schema of the columns found in the result.
This is done using the WITH clause in your SELECT query.
Here's an example:
SELECT *
FROM OPENJSON(@json)
WITH (
color VARCHAR(50),
material VARCHAR(100),
height VARCHAR(10)
);
This will transpose the data into columns, and specify data types, which can be useful to INSERT
data.
16
JSON in SQL Server
If you want to get JSON data from a table and show it in a table format using OPENJSON, you'll
need to use the CROSS APPLY feature to reference a table.
SELECT
p.id,
p.product_name,
p.attributes,
a.[key],
a.[value]
FROM product p
CROSS APPLY OPENJSON (p.attributes) a;
I've used square brackets around key and value as they are reserved words.
17
JSON in SQL Server
We can see that each row is shown several times, as there are several attributes it's joined to
(color, material, drawers, height).
We can also use this concept and the WITH keyword to define a schema, to show the data in a
different way:
SELECT
p.id,
p.product_name,
p.attributes,
a.color,
a.material,
a.height
FROM product p
CROSS APPLY OPENJSON (p.attributes)
WITH (
color VARCHAR(50),
material VARCHAR(100),
height VARCHAR(10)
) AS a
Notice that the WITH clause is used to define the columns returned from the attributes field. We
have also added the color, material, and height into the SELECT clause.
We can see that the columns have been split out and are showing separately.
The OPENJSON function is quite useful if you need to extract or transpose your JSON data into
columns.
18
JSON in SQL Server
You could extract the string, do some substring and replacement work on the string, and add it
into the field, but that's error-prone and a lot of work.
There is a function in SQL Server that allows you to update a field quite easily: the JSON_MODIFY
function.
This function will return the updated JSON value. So, because we're updating an existing row in
the table, we use the UPDATE statement and this function.
SELECT
id,
product_name,
attributes
19
JSON in SQL Server
FROM product
WHERE id = 1;
id product_name attributes
1 Chair {"color":"brown", "material":"wood", "height":"60cm"}
Let's say we want to add a new attribute name and value, in addition to the color, height, and
material that already exist. This new attribute would be "width", and the value is 100cm.
UPDATE product
SET attributes = JSON_MODIFY(attributes, '$.width', '100cm')
WHERE id = 1;
The path is "$.width" as it's a width attribute at the top level (not within another attribute).
We can run the same SELECT statement as above to see the updated value.
id product_name attributes
1 Chair {"color":"brown", "material":"wood", "height":"60cm", "width":"100cm"}
We can see that "width" has been added to the list of attributes.
If you want to replace a value of an attribute inside a JSON field with another value, you can also
use the JSON_MODIFY function.
The path must refer to an existing attribute key. If it does not exist, then the attribute will be
added, as we saw in the earlier example.
id product_name attributes
1 Chair {"color":"brown", "material":"wood", "height":"60cm"}
20
JSON in SQL Server
We can update the value of color to black by using this JSON_MODIFY function. Because we are
updating an existing value, we use the UPDATE statement.
UPDATE product
SET attributes = JSON_MODIFY(attributes, '$.color', 'white')
WHERE id = 1;
We can select from this table and see the record is updated:
id product_name attributes
1 Chair {"color":"white", "material":"wood", "height":"60cm"}
SQL Server offers an "append" keyword as part of the JSON_MODIFY function. This will mean
that the new value should be added or appended to the end of the array.
Let's see an example. Say we have our Side Table product with a materials array of "metal" and
"wood":
id product_name attributes
3 Side Table {"color":"brown", "material":["metal", "wood"]}
We want to also say it has a material of plastic. Instead of updating the field to say it has a material
of metal, wood, and plastic (which would involve reading the existing value and including it in the
update), we can use append and add the value of plastic to it.
Here's the statement to do that. The word "append" goes before our JSON path inside the string.
UPDATE product
SET attributes = JSON_MODIFY(attributes, 'append $.material',
'plastic')
WHERE id = 3;
21
JSON in SQL Server
Once we run this statement, we can select from our table again and see our updated value:
id product_name attributes
3 Side Table {"color":"brown", "material":["metal", "wood", "plastic"]}
This only works if the target element is an array, which material is.
Strict means the path must exist, and Lax means it does not have to exist. Las is the default.
It can be useful to use strict if you need this for your application.
Deleting a row from your table is done in the same way as regular SQL. You can write an SQL
statement to delete the row that matches your ID, or using JSON_VALUE.
22
JSON in SQL Server
For example, to delete all rows where the color attribute is brown:
The other way to delete JSON data is to remove an attribute from a JSON field.
This is different to updating, as you're removing the attribute entirely rather than just updating its
value to something else.
We can do this with an UPDATE statement and the JSON_MODIFY function. We specify a
new_value of NULL.
The JSON_MODIFY function used in this way will remove data from a JSON field.
SELECT
id,
product_name,
attributes
FROM product
WHERE id = 1;
id product_name attributes
1 Chair {"color":"brown", "material":"wood", "height":"60cm"}
We can run an UPDATE statement with JSON_MODIFY to remove the "height" attribute.
UPDATE product
SET attributes = JSON_MODIFY(attributes, '$.height', NULL)
WHERE id = 2;
We can then select the data from the table again to see that it has been removed:
23
JSON in SQL Server
id product_name attributes
1 Chair {"color":"brown", "material":"wood"}
The function that SQL Server uses can also be used in regular SQL, not just in the check constraint.
This function is called ISJSON. You provide it with a value, and it returns 1 if it's a valid JSON string
and 0 if it is not.
ISJSON (value)
We can test if this is valid by looking at it for quotes and other symbols in the right places. Or we
can just pass it to the ISJSON function.
SELECT
ISJSON('{"color": "black", "depth": "100cm", "material": "wood"}') AS
valid_test;
valid_test
24
JSON in SQL Server
At first glance it may seem valid. Let's use the JSON_VALID function to check.
SELECT
ISJSON('{"color": "black", "depth" "100cm", "material": "wood"}') AS
valid_test;
valid_test
The result is 0 so it's not a valid JSON value. The result does not say where the issue is in the
provided string, but it tells you it's invalid so you can look closer at it.
So, given that you can add JSON columns to tables, extract fields, and get all the flexibility of JSON
fields with validation, wouldn't it be better to just store all of your data in JSON fields rather than
normalised tables?
Well, sometimes that might be a good idea. But then you may be better off using a NoSQL
database rather than SQL Server.
Another reason why using primarily JSON fields to store your data is not a good idea is that it can
struggle with performance.
25
JSON in SQL Server
Select Performance
For example, let's say we want to select all products where the color is brown. We can use the
JSON_VALUE function in the WHERE clause that we saw earlier in this guide:
SELECT
id,
product_name,
attributes
FROM product
WHERE JSON_VALUE(attributes, '$.color') = 'brown';
The execution plan shows a Full Table Scan step, which is a slow type of access. This might be OK
for our table, which only has a few records, but once you start working with larger tables it can be
quite slow.
So, using the JSON_VALUE function in the WHERE clause will mean a full table scan is used.
26
JSON in SQL Server
Fortunately, SQL Server allows you to define a virtual column on the table, and create an index on
that virtual column. This should make our query faster.
A virtual column is a column that is a calculation based on another column in the table.
SELECT
id,
product_name,
color,
attributes
FROM product;
Now, let's select from the table again, filtering on the virtual column instead of the JSON field.
SELECT
id,
product_name,
color,
attributes
FROM product
WHERE color = 'brown';
27
JSON in SQL Server
We can see that the step is still called "Table Scan". It looks like nothing has changed.
This could be because of the size of the table (only 3 rows) or the fact that "brown" exists in more
than half of the rows.
But, this demonstrates the concept of creating a virtual column and an index (also that an index is
not the right solution every time!).
Having said all of that, if you're creating virtual columns to be able to access data in SQL Server
JSON fields more efficiently just to make your application and database work, then perhaps the
JSON field is not right for your database. But only you would know that - each case is different.
28
JSON in SQL Server
So, what's the best way to work with JSON fields in SQL Server?
Here are some tips I can offer for using JSON in SQL Server. They may not apply to your
application or database but they are things to consider.
This is the concept of a black box. The application puts data in and reads data from it, and the
database doesn't care about what's inside the field.
It may or may not work for your situation, but consider taking this approach.
Conclusion
I hope you found this guide useful. Have you used JSON fields in SQL Server? What has your
experience been like? Do you have any questions? Feel free to use the comments section on the
post.
29