{"id":106060,"date":"2025-05-07T06:34:00","date_gmt":"2025-05-07T06:34:00","guid":{"rendered":"https:\/\/www.red-gate.com\/simple-talk\/?p=106060"},"modified":"2026-03-12T15:49:06","modified_gmt":"2026-03-12T15:49:06","slug":"techniques-to-query-azure-sqls-new-json-datatype","status":"publish","type":"post","link":"https:\/\/www.red-gate.com\/simple-talk\/databases\/sql-server\/t-sql-programming-sql-server\/techniques-to-query-azure-sqls-new-json-datatype\/","title":{"rendered":"Azure SQL Native JSON Data Type: Query Techniques"},"content":{"rendered":"\n<p>Azure SQL Database introduced a native JSON data type that stores JSON documents more efficiently than VARCHAR(MAX) and enables native CHECK constraints for JSON validation. Alongside the new data type, Azure SQL added JSON_ARRAYAGG (aggregate JSON values into an array), JSON_OBJECTAGG (aggregate key-value pairs into an object), and JSON_OBJECT (construct JSON objects from arguments) &#8211; all ANSI SQL-standard functions. <\/p>\n\n\n\n<p>These features are confirmed for SQL Server 2025, making the techniques in this article directly applicable to on-premises SQL Server in the near future. This guide covers creating JSON columns, adding validation constraints, and using the new aggregation functions for practical JSON construction scenarios.<\/p>\n\n\n\n<p>The new JSON field type and new functions in Azure SQL brings a new world of possibilities for JSON manipulation in SQL Databases. These features are slated to be a part of SQL Server 2025 as well.<\/p>\n\n\n\n<p>Let\u2019s analyze some practical uses of these features.<\/p>\n\n\n\n<p><strong>Read also: <\/strong><a href=\"https:\/\/www.red-gate.com\/simple-talk\/databases\/sql-server\/t-sql-programming-sql-server\/effective-strategies-for-storing-and-parsing-json-in-sql-server\/\">Storing and parsing JSON in SQL Server with VARCHAR\/NVARCHAR<\/a><\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-creating-and-using-a-json-column\">Creating and using a JSON column<\/h2>\n\n\n\n<p>Before this new field type, JSON data was typically stored in <code>varchar(max)<\/code> columns. There are many features to use with JSON values stored in <code>varchar(max)<\/code> columns and variables, but storing JSON as regular strings is still limited.<\/p>\n\n\n\n<p>The built-in JSON type expands the possibilities. Using an actual JSON column, it becomes easier to build constraints related to JSON columns, for example.<\/p>\n\n\n\n<p>Let&#8217;s see how to create a table and insert records using this new type.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">DROP TABLE IF EXISTS dbo.testOrders;\nCREATE TABLE dbo.testOrders (\n    order_id int NOT NULL IDENTITY,\n    order_info JSON NOT NULL\n);\nINSERT INTO dbo.testOrders (order_info)\nVALUES ('\n{\n    \"OrderNumber\": \"S043659\",\n    \"Date\":\"2022-05-24T08:01:00\",\n    \"AccountNumber\":\"AW29825\",\n    \"Price\":59.99,\n    \"Quantity\":1\n}'), ('\n{\n    \"OrderNumber\": \"S043661\",\n    \"Date\":\"2022-05-20T12:20:00\",\n    \"AccountNumber\":\"AW73565\",\n    \"Price\":24.99,\n    \"Quantity\":3\n}');\nselect * \nfrom testOrders;<\/pre>\n\n\n\n<p>The output from the <code>SELECT<\/code> statement will look like this. If you grab the value from the JSON columns, you will see it looks just like the input.<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"608\" height=\"169\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/03\/a-screenshot-of-a-computer-ai-generated-content-m-2.png\" alt=\"A screenshot of a computer\n\nAI-generated content may be incorrect.\" class=\"wp-image-106061\" srcset=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/03\/a-screenshot-of-a-computer-ai-generated-content-m-2.png 608w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/03\/a-screenshot-of-a-computer-ai-generated-content-m-2-300x83.png 300w\" sizes=\"auto, (max-width: 608px) 100vw, 608px\" \/><\/figure>\n\n\n\n<p><\/p>\n\n\n\n<p>The format does not make a difference when you insert the data, if you change the first of the inputs to one long string, like the following:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">VALUES ('{\"OrderNumber\": \"S043659\", \"Date\":\"2022-05-24T08:01:00\",\n          \"AccountNumber\":\"AW29825\",\"Price\":59.99,\"Quantity\":1}')<\/pre>\n\n\n\n<p>There will be no difference between the values you see in the structures and output.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-creating-a-check-constraint-for-a-json-type\">Creating a CHECK Constraint for a JSON type<\/h2>\n\n\n\n<p>One of many advantages of having a native JSON type is that you can use it to validate the structure (or values) in the JSON data. Consider the table in the example above: How could we prevent inserted records with bad JSON values based on our requirements?<\/p>\n\n\n\n<p>For example, how can we prevent a JSON order without the attribute Price to be inserted? The creation of a constraint is much easier than it was before. :<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">alter table testOrders\n  add constraint chkPrice \n     CHECK (JSON_PATH_EXISTS(order_info, '$.Price') = 1) <\/pre>\n\n\n\n<p>As a result, we have an actual JSON validation when data is inserted or modified. Any attempt to insert poorly formed order structures will be blocked, as illustrated in the image below:<\/p>\n\n\n\n<p>Try to execute this code:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">INSERT INTO dbo.testOrders (order_info)\nVALUES ('\n{\n    \"OrderNumber\": \"S0436663\",\n    \"Date\":\"2022-05-24T08:01:00\",\n    \"AccountNumber\":\"AW29827\",\n    \"Quantity\":1\n}')<\/pre>\n\n\n\n<p>And it will not create the data, but will fail with an error similar to the following:<\/p>\n\n\n\n<p><code>Msg 547, Level 16, State 0, Line 1<\/code><\/p>\n\n\n\n<p><code>The INSERT statement conflicted with the CHECK constraint \"chkPrice\". The conflict occurred in database \"Testing\", table \"dbo.testOrders\", column 'order_info'.<\/code><\/p>\n\n\n\n<p>In this way, we can ensure our data will be way more consistent by using JSON fields and constraints.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-methods-to-query-the-data\">Methods to Query the Data<\/h2>\n\n\n\n<p>There are at least two different methods to query the data: using the <code>FOR JSON<\/code> or using the <code>JSON_ARRAYAGG<\/code> aggregation function. <code>FOR JSON<\/code> is not new, it was introduced in SQL Server 2016.<\/p>\n\n\n\n<p><code>JSON_ARRAYAGG<\/code>, on the other hand, is one of the new JSON functions released in Azure SQL. It is an aggregation function, such as <code>SUM<\/code>, <code>MAX<\/code> or <code>MIN<\/code>. This means this function can aggregate the content of a field among many records and generate a JSON array as a result. As with every aggregation function, this can also be done according to the use of a <code>GROUP BY<\/code>.<\/p>\n\n\n\n<p>It\u2019s important to compare the new function with previously existing features to generate JSON data and in this way discover the new possibilities this function brings to us. Let&#8217;s analyze how both work and understand what was already available and what are the new features.<\/p>\n\n\n\n<p>Let&#8217;s create a table for a customers and orders type scenario:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">DROP TABLE IF EXISTS dbo.testJSON;\nCREATE TABLE testJSON\n    (Id int identity(1,1) not null,\n     CustomerName varchar(50),\n     Orders JSON)<\/pre>\n\n\n\n<p>The example is based on the <em>AdventureworksLT<\/em> database. This database can be provisioned as a sample database when provisioning an <strong>Azure SQL Database<\/strong> on Azure.<\/p>\n\n\n\n<p><em>Editor note: it is a relatively painless operation if you have any experience with Azure SQL Databases. You can find more details here about restoring the database to your environment: <a href=\"https:\/\/learn.microsoft.com\/en-us\/sql\/samples\/adventureworks-install-configure?view=sql-server-ver16&amp;tabs=ssms\">AdventureWorks Sample Databases<\/a><\/em><\/p>\n\n\n\n<p>This database has customers, order headers and order details. We can join these records and transform them into a JSON document for each customer.<\/p>\n\n\n\n<p>We can use <code>FOR JSON<\/code> to create a JSON result and <code>CROSS APPLY<\/code> to get the orders for each customer. We need to use <code>INSERT<\/code><strong>\/<\/strong><code>SELECT<\/code> to insert the records in the test table.<\/p>\n\n\n\n<p>This is the code to fill the test table, it will create data with arrays in the Orders column:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">insert into testJSON(Id, CustomerName, Orders)\n   select FirstName + ' ' + LastName as CustomerName, t.* \n   from SalesLT.Customer  c\n        cross apply \n          (select (select ProductID, UnitPrice, \n                          OrderQty, so.SalesOrderID \n                   from SalesLT.SalesOrderDetail so\n        inner join\n          SalesLT.SalesOrderHeader SOH\n          on so.SalesOrderID=SOH.SalesOrderID\n          where SOH.CustomerID = c.CustomerID \n                            for JSON auto) as product) t\n       where t.product is not null<\/pre>\n\n\n\n<p>Take a look at the data:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">select *\nfrom  testJson;<\/pre>\n\n\n\n<p>And what you will see is that there is a square bracket that denotes an array of values:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"698\" height=\"343\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/03\/a-screenshot-of-a-computer-ai-generated-content-m-3.png\" alt=\"A screenshot of a computer\n\nAI-generated content may be incorrect.\" class=\"wp-image-106062\" srcset=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/03\/a-screenshot-of-a-computer-ai-generated-content-m-3.png 698w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/03\/a-screenshot-of-a-computer-ai-generated-content-m-3-300x147.png 300w\" sizes=\"auto, (max-width: 698px) 100vw, 698px\" \/><\/figure>\n\n\n\n<p><\/p>\n\n\n\n<p>After creating the test table, it\u2019s time to query it with our new functions. Let&#8217;s suppose we want to retrieve all the orders, together. The two methods, <code>FOR JSON<\/code> and <code>JSON_ARRAYAGG<\/code> allow this. What we need to discover is if there is any difference in the result.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"h-using-the-for-json-and-analyzing-the-result\">Using the FOR JSON and Analyzing the Result<\/h3>\n\n\n\n<p>Let\u2019s analyze the <strong>FOR JSON<\/strong> method. This query below is an example of this method:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">select (\n        select Orders \n        from testJSON for JSON auto) as Orders<\/pre>\n\n\n\n<p>This query has two <code>SELECT<\/code> statements, one inside another. If we don&#8217;t build in this way, the JSON can be truncated. We can use <a href=\"https:\/\/jsonhero.io\/&quot; \\t &quot;_self\">JSON Hero website<\/a> to analyze the result of the query. Just save the results to a file and import it into the tool to analyze it. <code>FOR JSON<\/code> joins the content from all the records in an array.<\/p>\n\n\n\n<p>Each record has an array, because this is the original content inside the field <code>Orders<\/code>, resulting in an array of arrays, or close to this.<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"1346\" height=\"450\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/03\/a-screenshot-of-a-computer-ai-generated-content-m-4.png\" alt=\"A screenshot of a computer\n\nAI-generated content may be incorrect.\" class=\"wp-image-106063\" srcset=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/03\/a-screenshot-of-a-computer-ai-generated-content-m-4.png 1346w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/03\/a-screenshot-of-a-computer-ai-generated-content-m-4-300x100.png 300w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/03\/a-screenshot-of-a-computer-ai-generated-content-m-4-1024x342.png 1024w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/03\/a-screenshot-of-a-computer-ai-generated-content-m-4-768x257.png 768w\" sizes=\"auto, (max-width: 1346px) 100vw, 1346px\" \/><\/figure>\n\n\n\n<p><\/p>\n\n\n\n<p>As you may notice in the image above, what we have is not exactly an array of array. The <code>FOR JSON<\/code> creates an additional element in the result: It creates an object wrapping each record in the structure.<\/p>\n\n\n\n<p>In this way, the result becomes like this:<\/p>\n\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>Each record becomes an object with the fields we are recovering as properties<\/li>\n\n\n\n<li>We are recovering one single field, <code>Orders<\/code>. The object has only this property<\/li>\n\n\n\n<li>The content of the field <code>Orders<\/code> is an array<\/li>\n\n\n\n<li>In each object\/record, the property orders contain an array, the actual content of the field<\/li>\n\n\n\n<li>All the objects\/records become one element inside a bigger array<\/li>\n<\/ul>\n<\/div>\n\n\n<p><strong>In short:<\/strong> We have an array of objects containing one single property which contains an array.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"h-using-the-json-arrayagg-and-analyzing-the-result\">Using the JSON_ARRAYAGG and Analyzing the Result<\/h3>\n\n\n\n<p>Let\u2019s analyze how to reach the same result or similar using JSON_ARRAYAGG. The query will be like this:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">select JSON_ARRAYAGG(Orders)\nfrom testJSON;<\/pre>\n\n\n\n<p>Once again, we can use the JSON Hero website to analyze the result of the query.<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"1379\" height=\"703\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/03\/a-screenshot-of-a-computer-ai-generated-content-m-5.png\" alt=\"A screenshot of a computer\n\nAI-generated content may be incorrect.\" class=\"wp-image-106064\" srcset=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/03\/a-screenshot-of-a-computer-ai-generated-content-m-5.png 1379w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/03\/a-screenshot-of-a-computer-ai-generated-content-m-5-300x153.png 300w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/03\/a-screenshot-of-a-computer-ai-generated-content-m-5-1024x522.png 1024w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/03\/a-screenshot-of-a-computer-ai-generated-content-m-5-768x392.png 768w\" sizes=\"auto, (max-width: 1379px) 100vw, 1379px\" \/><\/figure>\n\n\n\n<p><\/p>\n\n\n\n<p>The result is an array of arrays of objects. The content of the records was used &#8220;as is&#8221; to build the JSON. The record already contains an array of objects, they are joined in a bigger array. In this situation, there is no wrapper object.<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"1381\" height=\"756\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/03\/a-screenshot-of-a-computer-ai-generated-content-m-6.png\" alt=\"A screenshot of a computer\n\nAI-generated content may be incorrect.\" class=\"wp-image-106065\" srcset=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/03\/a-screenshot-of-a-computer-ai-generated-content-m-6.png 1381w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/03\/a-screenshot-of-a-computer-ai-generated-content-m-6-300x164.png 300w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/03\/a-screenshot-of-a-computer-ai-generated-content-m-6-1024x561.png 1024w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/03\/a-screenshot-of-a-computer-ai-generated-content-m-6-768x420.png 768w\" sizes=\"auto, (max-width: 1381px) 100vw, 1381px\" \/><\/figure>\n\n\n\n<p><\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"h-the-difference-between-both-methods\">The difference between both methods<\/h3>\n\n\n\n<p><code>FOR JSON<\/code>: Includes a wrapper object and add each field as one property of the wrapper object. The result is an array of the wrapper objects.<\/p>\n\n\n\n<p><code>JSON_ARRAYAGG<\/code>: The output includes the result from the database &#8220;as is&#8221;, without any wrapper object.<\/p>\n\n\n\n<p>We can make the <code>JSON_ARRAYAGG<\/code> generate the same result as the <code>FOR JSON<\/code>, but we can\u2019t make the <code>FOR JSON<\/code> generate the same result as the <code>JSON_ARRAYAGG<\/code>.<\/p>\n\n\n\n<p>However, we can achieve the same result as the <code>FOR JSON<\/code> using the function <code>JSON_OBJECT()<\/code> together with <code>JSON_ARRAYAGG<\/code>. The function <code>JSON_OBJECT<\/code> can create the wrapper object which is automatically created by the <code>FOR JSON<\/code>. This is the query which will make the <code>JSON_ARRAYAGG<\/code> result the same as the <code>FOR JSON<\/code> result:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">select JSON_ARRAYAGG(JSON_Object('Orders':Orders))\nfrom testJSON;<\/pre>\n\n\n\n<p>Looking at the output, you can see now it is an array of Orders objects as it starts like this:<\/p>\n\n\n\n<p><code>[{\"Orders\":[{\"ProductID\":714,\"UnitPrice\":29.9940,\"OrderQty\":3,\"SalesOrderID\":71782},<\/code><\/p>\n\n\n\n<p>Just like the <code>FOR JSON<\/code> output did.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"h-json-arrayagg-is-an-aggregate-function\">JSON_ARRAYAGG is an aggregate function<\/h3>\n\n\n\n<p>Another important difference between the <code>FOR JSON<\/code> and <code>JSON_ARRAYAGG<\/code> is that while the first is applied over the entire result of a select, the last one is an aggregation function, it can be used together with a <code>GROUP BY<\/code>.<\/p>\n\n\n\n<p>Let&#8217;s consider an example. The <code>AdventureworksLT<\/code> sample table has a table <code>Customer<\/code>. This table has a column named <code>SalesPerson<\/code>. Each sales person has multiple customers, and each customer has multiple orders.<\/p>\n\n\n\n<p>This is an example of the total sales for each customer:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">select c.SalesPerson,Count(*) as OrderCount\nfrom   SalesLT.Customer AS C\n\t     join SalesLT.SalesOrderHeader as SOH\n\t\t\ton C.CustomerId = SOH.CustomerId\ngroup by c.SalesPerson;<\/pre>\n\n\n\n<p>Which will show you that there are a limited number of orders in this database<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">SalesPerson                OrderCount\n-------------------------- -----------\nadventure-works\\jae0       14\nadventure-works\\linda3     9\nadventure-works\\shu0       9<\/pre>\n\n\n\n<p>We can group the records by <code>SalesPerson<\/code> and generate the JSON of the orders for each <code>SalesPerson<\/code>. <code>JSON_ARRAYAGG<\/code> is better for this because it&#8217;s an aggregation function. If we try to do the same with <code>FOR JSON<\/code>, we will need at least some subqueries, making it more difficult.<\/p>\n\n\n\n<p>This is an example of <code>JSON_ARRAYAGG<\/code> used together a <code>GROUP BY<\/code>:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">select SalesPerson, JSON_ARRAYAGG(\n       JSON_OBJECT('CompanyName':c.CompanyName, \n                   'ProductId':od.ProductID, \n                   'UnitPrice':od.UnitPrice,\n                   'OrderQty':od.OrderQty) ) \nfrom SalesLT.Customer c\n  inner join SalesLT.SalesOrderHeader oh\n\t    on oh.CustomerID=c.CustomerID\n  inner join SalesLT.SalesOrderDetail od\n\t\ton od.SalesOrderID=oh.SalesOrderID\ngroup by SalesPerson;<\/pre>\n\n\n\n<p>In this example we are using the function <code>JSON_OBJECT<\/code> to create an object for each order, joining fields from three tables, <code>Customer<\/code>, <code>SalesOrderHeader<\/code> and <code>SalesOrderDetail<\/code>.<\/p>\n\n\n\n<p>The <code>JSON_ARRAYAGG<\/code> joins the individual JSON objects into an array, according to the <code>GROUP BY<\/code>. In other words, this function creates an array of orders for each sales person, like the image below:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"494\" height=\"108\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/03\/a-screenshot-of-a-computer-ai-generated-content-m-7.png\" alt=\"A screenshot of a computer\n\nAI-generated content may be incorrect.\" class=\"wp-image-106066\" srcset=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/03\/a-screenshot-of-a-computer-ai-generated-content-m-7.png 494w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/03\/a-screenshot-of-a-computer-ai-generated-content-m-7-300x66.png 300w\" sizes=\"auto, (max-width: 494px) 100vw, 494px\" \/><\/figure>\n\n\n\n<p><\/p>\n\n\n\n<p>In the last column, if you execute the query, you can see all the sales for each salesperson<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"h-summarizing-the-differences-between-the-methods\">Summarizing the differences between the methods<\/h3>\n\n\n\n<p>We can notice in this comparison the differences between the new function <code>JSON_ARRAYAGG<\/code> and the <code>FOR JSON<\/code>.<\/p>\n\n\n\n<p>There are small differences, and the same result could be achieved using previous methods.<\/p>\n\n\n\n<p>When we need to make a <code>GROUP BY<\/code>, the <code>JSON_ARRAYAGG<\/code> is way easier than the <code>FOR JSON<\/code>.<\/p>\n\n\n\n<p>Another benefit of the new function is the fact it&#8217;s part of ANSI SQL:2016. I would like to hear from you how important this is for you or how important the new features are for you. In my opinion, it&#8217;s more of a matter of a &#8220;kind-of&#8221; competition, because MySQL, PostgreSQL and Oracle already support this function.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"h-json-objectagg\">JSON_OBJECTAGG<\/h3>\n\n\n\n<p><code>JSON_OBJECTAGG<\/code> is one more function recently added to <strong>Azure SQL<\/strong>. It\u2019s also an aggregation function, but this one creates a JSON object and not a JSON array. What&#8217;s the purpose?<\/p>\n\n\n\n<p>It&#8217;s more difficult to identify the purpose of <code>JSON_OBJECTAGG<\/code>. The documentation explains it has two parameters: Two columns, one will be used as the JSON property name and the other will be used as the value of the property. Why would we like to transform only two columns into a JSON where the name of the properties are the values of one of the columns?<\/p>\n\n\n\n<p>The question is in fact the answer: <em>When would we like to use the content of a column as the name of JSON properties?<\/em><\/p>\n\n\n\n<p>If we remember that JSON properties can be compared to columns in a table, using the content of a column as the name of columns is in fact a pivot table. In this way, using <code>JSON_OBJECTAGG<\/code> can achieve a similar result as creating a <code>PIVOT<\/code> table, but the function generates a JSON.<\/p>\n\n\n\n<p>As an example, I will use <code>AdventureWorksLT<\/code> data. We will start with a query which could be transformed into a pivot table.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">with qry as (\n    select pc.Name as Category, \n        c.CompanyName,\n        OrderQty * UnitPrice as TotalPrice\n    from SalesLT.SalesOrderDetail sod\n    inner join SalesLT.SalesOrderHeader SOH\n        on sod.SalesOrderID= soh.SalesOrderID\n    inner join SalesLT.Product p\n        on sod.ProductID=p.ProductID\n    inner join SalesLT.ProductCategory pc\n        on p.ProductCategoryID=pc.ProductCategoryID\n    inner join SalesLT.Customer c\n        on c.CustomerID=SOH.CustomerID)\nselect Category,CompanyName, sum(TotalPrice) as TotalSales\nfrom qry\ngroup by Category, CompanyName;<\/pre>\n\n\n\n<p>This query creates a list of orders grouped by the product category and the company name and with the orders totaled.<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"365\" height=\"311\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/03\/a-screenshot-of-a-computer-ai-generated-content-m-8.png\" alt=\"A screenshot of a computer\n\nAI-generated content may be incorrect.\" class=\"wp-image-106067\" srcset=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/03\/a-screenshot-of-a-computer-ai-generated-content-m-8.png 365w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/03\/a-screenshot-of-a-computer-ai-generated-content-m-8-300x256.png 300w\" sizes=\"auto, (max-width: 365px) 100vw, 365px\" \/><\/figure>\n\n\n\n<p><\/p>\n\n\n\n<p>We can transform this into a pivot table where each company is a row, each category is a column with the total of the category for that company.<\/p>\n\n\n\n<p>The <code>JSON_OBJECTAGG<\/code> can do this, transforming the result in JSON format.<\/p>\n\n\n\n<p><strong>Read also: <\/strong><a href=\"https:\/\/www.red-gate.com\/simple-talk\/databases\/sql-server\/t-sql-programming-sql-server\/consuming-json-strings-in-sql-server\/\">Consuming JSON strings with custom T-SQL parsing<\/a><\/p>\n\n\n\n<h4 class=\"wp-block-heading\" id=\"h-pivot-vs-json-objectagg\">Pivot vs JSON_OBJECTAGG<\/h4>\n\n\n\n<p>If we apply a <code>PIVOT<\/code> operator to this result set, we could transform the category into columns and leave the company name as rows.<\/p>\n\n\n\n<p>However, the SQL syntax to make a pivot has one limitation: The column names are fixed, we need to specify them one by one. The image below shows an example of how the column names are explicitly defined in the query:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"742\" height=\"286\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/03\/a-screenshot-of-a-computer-program-ai-generated-c.png\" alt=\"A screenshot of a computer program\n\nAI-generated content may be incorrect.\" class=\"wp-image-106068\" srcset=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/03\/a-screenshot-of-a-computer-program-ai-generated-c.png 742w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/03\/a-screenshot-of-a-computer-program-ai-generated-c-300x116.png 300w\" sizes=\"auto, (max-width: 742px) 100vw, 742px\" \/><\/figure>\n\n\n\n<p><\/p>\n\n\n\n<p>This syntax can pose a limit: If the number of columns is too high and dynamic, it will be very difficult to make a query using the <code>PIVOT<\/code> method. Considering the example above if there are too many different foods, it would be difficult to make the query.<\/p>\n\n\n\n<p>In this way, if we compare a pivot table with the <code>JSON_OBJECTAGG<\/code>, the difference is exactly this: The pivot table is limited to creating fixed columns, but the <code>JSON_OBJECTAGG<\/code> uses the field content to create columns dynamically, it&#8217;s not fixed. The fact the result is in JSON format also makes the result more flexible: too many columns may not be so good in a query.<\/p>\n\n\n\n<p>The example below transforms the <code>CompanyName<\/code> and <code>TotalSales<\/code> into properties of a JSON object for each one of the categories, grouping the different company names to build a single JSON Object. Compared with the <code>PIVOT<\/code> version, it\u2019s like if the company name were transformed into a column.<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \">with qry as (\n    select pc.Name as Category, \n        c.CompanyName,\n        OrderQty * UnitPrice as TotalPrice\nfrom SalesLT.SalesOrderDetail sod\n    inner join SalesLT.SalesOrderHeader SOH\n        on sod.SalesOrderID= soh.SalesOrderID\n    inner join SalesLT.Product p\n        on sod.ProductID=p.ProductID\n    inner join SalesLT.ProductCategory pc\n        on p.ProductCategoryID=pc.ProductCategoryID\n    inner join SalesLT.Customer c\n        on c.CustomerID=SOH.CustomerID),\ndinPivot as (\n    select Category,CompanyName, sum(TotalPrice) as TotalSales\n    from qry\n    group by Category, CompanyName)\nselect Category, JSON_OBJECTAGG(CompanyName:TotalSales) as SalesByCustomer\nfrom dinPivot\ngroup by Category\n<\/pre><\/div>\n\n\n\n<p>This is how the results look:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"1007\" height=\"237\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/03\/a-screenshot-of-a-computer-ai-generated-content-m-9.png\" alt=\"A screenshot of a computer\n\nAI-generated content may be incorrect.\" class=\"wp-image-106069\" srcset=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/03\/a-screenshot-of-a-computer-ai-generated-content-m-9.png 1007w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/03\/a-screenshot-of-a-computer-ai-generated-content-m-9-300x71.png 300w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/03\/a-screenshot-of-a-computer-ai-generated-content-m-9-768x181.png 768w\" sizes=\"auto, (max-width: 1007px) 100vw, 1007px\" \/><\/figure>\n\n\n\n<p><\/p>\n\n\n\n<p>You will notice that in this snippet of the output:<\/p>\n\n\n\n<p><code>\"Action Bicycle Specialists\":432.0000,\"Bulk Discount Store\":216.0000<\/code><\/p>\n\n\n\n<p>The company names are now attributes, and the total sales are the property values.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Next step: Creating a new Object with this data<\/h4>\n\n\n\n<p>The previous result has two columns, <code>Category<\/code> and <code>SalesByCustomer<\/code>.<\/p>\n\n\n\n<p>We can transform these two columns into an object. This transformation is row-by-row, and each row will generate an object. In this way, we can use the function <code>JSON_OBJECT<\/code>, because no row aggregation is needed.<\/p>\n\n\n\n<p>The query will become like this:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \">with qry as (\n    select pc.Name as Category, \n        c.CompanyName,\n        OrderQty * UnitPrice as TotalPrice\n    from SalesLT.SalesOrderDetail sod\n    inner join SalesLT.SalesOrderHeader SOH\n        on sod.SalesOrderID= soh.SalesOrderID\n    inner join SalesLT.Product p\n        on sod.ProductID=p.ProductID\n    inner join SalesLT.ProductCategory pc\n        on p.ProductCategoryID=pc.ProductCategoryID\n    inner join SalesLT.Customer c\n        on c.CustomerID=SOH.CustomerID),\ndinPivot as (\n    select Category,CompanyName, sum(TotalPrice) as TotalSales\n    from qry\n    group by Category, CompanyName),\nfirstLevel as \n    (select Category, JSON_OBJECTAGG(CompanyName:TotalSales) as SalesByCustomer\n    from dinPivot\n    group by Category)\nselect JSON_OBJECT(Category:SalesByCustomer) as Categories\nfrom firstLevel;\n<\/pre><\/div>\n\n\n\n<p>This is the result:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"1003\" height=\"271\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/03\/a-screenshot-of-a-computer-ai-generated-content-m-10.png\" alt=\"A screenshot of a computer\n\nAI-generated content may be incorrect.\" class=\"wp-image-106070\" srcset=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/03\/a-screenshot-of-a-computer-ai-generated-content-m-10.png 1003w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/03\/a-screenshot-of-a-computer-ai-generated-content-m-10-300x81.png 300w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/03\/a-screenshot-of-a-computer-ai-generated-content-m-10-768x208.png 768w\" sizes=\"auto, (max-width: 1003px) 100vw, 1003px\" \/><\/figure>\n\n\n\n<p><\/p>\n\n\n\n<p>With this now as a snippet of the output:<\/p>\n\n\n\n<p><code>{\"Bike Racks\":\"{\\\"Action Bicycle Specialists\\\":432.0000,\\\"Bulk Discount Store\\\":216.0000<\/code><\/p>\n\n\n\n<p>Each row is now an object, with the name of the product, with the company name and total sales for each in the output. The <code>\\<\/code> are to escape the double quotes, since each of the company names is inside of another set of double quotes.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Aggregating the rows<\/h4>\n\n\n\n<p>At this stage we can use what we learned before to aggregate all the rows in a single JSON array using <code>JSON_ARRAYAGG<\/code><\/p>\n\n\n\n<p>This is the final query:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">with qry as (\n    select pc.Name as Category, \n        c.CompanyName,\n        OrderQty * UnitPrice as TotalPrice\n    from SalesLT.SalesOrderDetail sod\n    inner join SalesLT.SalesOrderHeader SOH\n        on sod.SalesOrderID= soh.SalesOrderID\n    inner join SalesLT.Product p\n        on sod.ProductID=p.ProductID\n    inner join SalesLT.ProductCategory pc\n        on p.ProductCategoryID=pc.ProductCategoryID\n    inner join SalesLT.Customer c\n        on c.CustomerID=SOH.CustomerID),\ndinPivot as (\n    select Category,CompanyName, sum(TotalPrice) as TotalSales\n    from qry\n    group by Category, CompanyName),\nfirstLevel as \n    (select Category, JSON_OBJECTAGG(CompanyName:TotalSales) as SalesByCustomer\n    from dinPivot\n    group by Category)\nselect JSON_ARRAYAGG(JSON_OBJECT(Category:SalesByCustomer)) as Categories\nfrom firstLevel;<\/pre>\n\n\n\n<p>This is the result:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"1018\" height=\"110\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/03\/word-image-106060-11.png\" alt=\"\" class=\"wp-image-106071\" srcset=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/03\/word-image-106060-11.png 1018w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/03\/word-image-106060-11-300x32.png 300w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/03\/word-image-106060-11-768x83.png 768w\" sizes=\"auto, (max-width: 1018px) 100vw, 1018px\" \/><\/figure>\n\n\n\n<p><\/p>\n\n\n\n<p>Now the output is an array of objects, each representing a product type.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Summary<\/h2>\n\n\n\n<p>The new JSON field and the creation of constraints over it are a powerful feature. The new functions provide additional capabilities for very specific scenarios and add the ANSI features to Azure SQL.<\/p>\n\n\n\n<section id=\"my-first-block-block_e461baad5e7f48fc72f037b036451731\" class=\"my-first-block alignwide\">\n    <div class=\"bg-brand-600 text-base-white py-5xl px-4xl rounded-sm bg-gradient-to-r from-brand-600 to-brand-500 red\">\n        <div class=\"gap-4xl items-start md:items-center flex flex-col md:flex-row justify-between\">\n            <div class=\"flex-1 col-span-10 lg:col-span-7\">\n                <h3 class=\"mt-0 font-display mb-2 text-display-sm\">Simple Talk is brought to you by Redgate Software<\/h3>\n                <div class=\"child:last-of-type:mb-0\">\n                                            Take control of your databases with the trusted Database DevOps solutions provider. Automate with confidence, scale securely, and unlock growth through AI.                                    <\/div>\n            <\/div>\n                            <a href=\"https:\/\/www.red-gate.com\/solutions\/overview\/\" class=\"btn btn--secondary btn--lg\">Discover how Redgate can help you<\/a>\n                    <\/div>\n    <\/div>\n<\/section>\n\n\n<section id=\"faq\" class=\"faq-block my-5xl\">\n    <h2>FAQs: The JSON data type in Azure SQL Database<\/h2>\n\n                        <h3 class=\"mt-4xl\">1. What is the JSON data type in Azure SQL Database?<\/h3>\n            <div class=\"faq-answer\">\n                <p>The JSON data type is a native column type in Azure SQL Database (available since 2024) that stores JSON documents in an optimized binary format rather than as plain text in VARCHAR columns. It provides automatic validation (only valid JSON is accepted), more efficient storage, and serves as the foundation for JSON-specific CHECK constraints. Declare it with: CREATE TABLE t (data JSON NOT NULL).<\/p>\n            <\/div>\n                    <h3 class=\"mt-4xl\">2. Will the JSON data type be available in SQL Server 2025?<\/h3>\n            <div class=\"faq-answer\">\n                <p>Yes. The native JSON data type and the new JSON aggregation functions (JSON_ARRAYAGG, JSON_OBJECTAGG, JSON_OBJECT) are confirmed for SQL Server 2025. The techniques demonstrated with Azure SQL Database will apply directly to on-premises SQL Server once 2025 reaches general availability.<\/p>\n            <\/div>\n                    <h3 class=\"mt-4xl\">3. What does JSON_ARRAYAGG do in Azure SQL?<\/h3>\n            <div class=\"faq-answer\">\n                <p>JSON_ARRAYAGG is an aggregate function that collects values from multiple rows into a single JSON array. For example, SELECT JSON_ARRAYAGG(ProductName) FROM Products WHERE Category = &#8216;Electronics&#8217; returns a JSON array like [&#8220;Laptop&#8221;, &#8220;Phone&#8221;, &#8220;Tablet&#8221;]. It works with GROUP BY for grouped arrays and can be combined with JSON_OBJECT for nested structures.<\/p>\n            <\/div>\n            <\/section>\n","protected":false},"excerpt":{"rendered":"<p>Explore Azure SQL&#8217;s native JSON data type with CHECK constraints, JSON_ARRAYAGG, JSON_OBJECTAGG, and JSON_OBJECT functions. These features are coming to SQL Server 2025 &#8211; learn the techniques now.&hellip;<\/p>\n","protected":false},"author":50808,"featured_media":106493,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[53,143531],"tags":[4880,159319,4337,4252],"coauthors":[6810],"class_list":["post-106060","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-featured","category-t-sql-programming-sql-server","tag-json","tag-sqlserver2025publicpreview","tag-t-sql-functions","tag-t-sql-programming"],"acf":[],"_links":{"self":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/106060","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/users\/50808"}],"replies":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/comments?post=106060"}],"version-history":[{"count":15,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/106060\/revisions"}],"predecessor-version":[{"id":109128,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/106060\/revisions\/109128"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/media\/106493"}],"wp:attachment":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/media?parent=106060"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/categories?post=106060"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/tags?post=106060"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/coauthors?post=106060"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}