Get Array Index from JSON data SQL Server - sql

{
"Name": ["dokumen_1","dokumen_2","dokumen_3","dokumen_4"],
"Date": [0,0,0,0],
"Progress": [0,0,0,0]
}
I want to fetch Date and Progress value according to Name position.

I changed the date and progress values for a better illustration
NOTE: in 2016 the JSON_VALUE has to be a literal
Example
Declare #JSON varchar(max) = '
{
"Name":["dokumen_1","dokumen_2","dokumen_3","dokumen_4"],
"Date":[1,2,3,4],
"Progress":[11,12,13,12]
}'
Select Name = value
,Date = JSON_VALUE(#JSON,'$.Date['+[key]+']')
,Progress = JSON_VALUE(#JSON,'$.Progress['+[key]+']')
From openjson(#JSON,N'$.Name')
Results
Name Date Progress
dokumen_1 1 11
dokumen_2 2 12
dokumen_3 3 13
dokumen_4 4 12

Another possible approach is a combination of OPENJSON() with default schema and appropriate JOINs. Of course, you need at least SQL Server 2016 to use the built-in JSON support.
DECLARE #json varchar(max) = '
{
"Name":["dokumen_1","dokumen_2","dokumen_3","dokumen_4"],
"Date":[101,102,103,104],
"Progress":[201,202,203,204]
}'
SELECT n.[value] AS Name, d.[value] AS Date, p.[value] AS Progress
FROM OPENJSON(#json, '$.Name') n
LEFT JOIN OPENJSON(#json, '$.Date') d ON n.[key] = d.[key]
LEFT JOIN OPENJSON(#json, '$.Progress') p ON n.[key] = p.[key]

Related

How to parse json data then save it as a variable in sql server

Will i am sql server to create json which i import using ASP.NET
My select function:
SELECT Sales.SalesID,InvoiceNum,FORMAT(InvoiceDate, 'dd-MMM-yy') AS InvoiceDate,ProductName,Qty,Price
FROM Sales
INNER JOIN (SalesDetails
INNER JOIN Products
ON Products.ProductID = SalesDetails.ProductID)
ON Sales.SalesID = SalesDetails.SalesID
FOR JSON AUTO
My JSON Output:
{
"SalesID":1,
"InvoiceNum":"111",
"InvoiceDate":"11-Feb-19",
"Products":[
{
"ProductName":"Name",
"SalesDetails":[
{
"Qty":5,
"Price":100
}
]
},
{
"ProductName":"Name",
"SalesDetails":[
{
"Qty":10,
"Price":210.00
}
]
}
]
}
My Desired Output:
{
"SalesID":1,
"InvoiceNum":"11",
"InvoiceDate":"11-Feb-19",
"Products":[
{
"ProductName":"Name",
"Qty":5,
"Price":100
},
{
"ProductName":"Name",
"Qty":10,
"Price":210
}
]
}
I want to save this as a table with OpenJson After outputing this
My details query is:
My attempt with JSON PATH:
Select:
DECLARE #json NVARCHAR(MAX);
SET #json = (SELECT Sales.SalesID,InvoiceNum,FORMAT(InvoiceDate, 'dd-MMM-yy') AS InvoiceDate,ProductName AS "Products.Name",Qty AS "Products.Qty",Price AS "Products.Price"
FROM Sales
INNER JOIN (SalesDetails
INNER JOIN Products
ON Products.ProductID = SalesDetails.ProductID)
ON Sales.SalesID = SalesDetails.SalesID
FOR JSON PATH
SELECT * FROM OPENJSON(#json)
WITH (
SalesID INT 'strict $.SalesID',
InvoiceNum NVARCHAR(50) '$.InvoiceNum',
InvoiceDate NVARCHAR(9) '$.InvoiceDate',
Products NVARCHAR(MAX) '$.Products' AS JSON
)
My output:
Any help is appreciated.
You may try to generate the expected output using FOR JSON PATH. With FOR JSON AUTO, the format of the JSON output is automatically determined based on the order of columns in the SELECT list and their source tables and this format can't be changed.
It's difficult without test data, but the following statement is a possible solution to your problem:
DECLARE #json nvarchar(max);
SELECT #json = (
SELECT
s.SalesID,
s.InvoiceNum,
FORMAT(s.InvoiceDate, 'dd-MMM-yy') AS InvoiceDate,
c.Products
FROM Sales s
CROSS APPLY (
SELECT p.ProductName, sd.Qty, sd.Price
FROM SalesDetails sd
INNER JOIN Products p ON p.ProductID = sd.ProductID
WHERE sd.SalesID = s.SalesID
FOR JSON PATH
) c (Products)
FOR JSON PATH
)
After that you may try to parse the generated JSON:
SELECT * FROM OPENJSON(#json) WITH (
SalesID INT 'strict $.SalesID',
InvoiceNum NVARCHAR(50) '$.InvoiceNum',
InvoiceDate NVARCHAR(9) '$.InvoiceDate',
Products NVARCHAR(MAX) '$.Products' AS JSON
)

Modifying multiple JSON array elements in SQL Server 2017

I have a SQL Server 2017 table Orders which has an OrderId primary key and nvarchar(max) column Details. This column contains a json string which represents an array of "items". Here is a sample:
{ items[
{
"id": 1,
"isDeleted": false
},
{
"id": 2,
"isDeleted": false
},
{
"id": 3,
"isDeleted": false
},
{
"id": 4,
"isDeleted": false
}
] }
I am trying to figure out if there is a way to have a single (or few) SQL statement which will allow me to update one or more of the isDeleted attributes in the Details column of this table, given an OrderId for the record in the table and also a list of Ids in the Details column to update.
So for instance, I would like to update Ids 2 and 3 to be true in the Details JSON string record for a given OrderId. I know I can do this in a while loop and using json_modify, but I am wondering if there is a more elegant solution with some combination of json_modify, json_query or openjson.
Thanks in advance for any suggestions.
You may use one of the following approaches:
Parse the Details JSON for each OrderId uisng OPENJSON() and explicit schema. The result is a table with columns, defined in the WITH clause. Update this table and return the changed data as JSON again using FOR JSON.
Parse the Details JSON for each OrderId uisng OPENJSON() and default schema. The result is a table with columns key, value and type and one row for each item (JSON object) in the items JSON array. Update this table and generate the items JSON array with string-based approach (I don't think that FOR JSON can generate an array of scalar values / JSON objects). Update the JSON in the source table with JSON_MODIFY().
Generate and execute a dynamic statement using JSON_MODIFY()
Table with data:
CREATE TABLE Orders (OrderId int, Details nvarchar(max))
INSERT INTO Orders (OrderId, Details)
VALUES
(1, N'{"items":[{"id":1,"isDeleted":false},{"id":2,"isDeleted":false},{"id":3,"isDeleted":false},{"id":4,"isDeleted":false}]}'),
(2, N'{"items":[{"id":11,"isDeleted":false},{"id":12,"isDeleted":false},{"id":13,"isDeleted":false}]}')
Table with IDs:
CREATE TABLE ItemIds (id int)
INSERT INTO ItemIds (id) VALUES (1), (3)
Statement with OPENJSON() and explicit schema:
UPDATE Orders
SET Details = (
SELECT
j.id AS id,
CONVERT(bit, CASE WHEN i.id IS NOT NULL THEN 1 ELSE j.isDeleted END) AS isDeleted
FROM OPENJSON(Details, '$.items') WITH (
id int '$.id',
isDeleted bit '$.isDeleted'
) j
LEFT OUTER JOIN ItemIds i ON j.id = i.id
FOR JSON AUTO, ROOT('Items')
)
WHERE OrderId = 1
Statement with OPENJSON() and default schema:
UPDATE Orders
SET Details = JSON_MODIFY(
Details,
'$.items',
JSON_QUERY((
SELECT CONCAT(
'[',
STRING_AGG(
CASE
WHEN i.id IS NULL THEN j.[value]
ELSE JSON_MODIFY(j.[value], '$.isDeleted', CONVERT(bit, 1))
END,
','
),
']'
)
FROM OPENJSON(Details, '$.items') j
LEFT OUTER JOIN ItemIds i ON CONVERT(int, JSON_VALUE(j.[value], '$.id')) = i.id
))
)
WHERE OrderId = 1
Dynamic statement:
DECLARE #stm nvarchar(max)
SELECT #stm = STRING_AGG(
CONCAT(
'UPDATE Orders ',
'SET Details = JSON_MODIFY(Details, ''$.items[', a.[key], '].isDeleted'', CONVERT(bit, 1)) ',
'WHERE OrderId = ', o.OrderId, ';'
),
' '
)
FROM Orders o
CROSS APPLY (
SELECT o.OrderId, j1.[key]
FROM OPENJSON(o.Details, '$.items') j1
CROSS APPLY OPENJSON(j1.[value]) WITH (id int '$.id') j2
WHERE j2.id IN (SELECT id FROM ItemIds)
) a
WHERE o.OrderId = 1
PRINT #stm
EXEC sp_executesql #stm
Result:
OrderId Details
1 {"items":[{"id":1,"isDeleted":true},{"id":2,"isDeleted":false},{"id":3,"isDeleted":true},{"id":4,"isDeleted":false}]}
2 {"items":[{"id":11,"isDeleted":false},{"id":12,"isDeleted":false},{"id":13,"isDeleted":false}]}
SQL Server is perfectly capable of performing such operation. It is another question if this is good design though.
This is just a demo and not production ready code, so there is a lot of space for improvement:
-- param section
DECLARE #OrderId INT = 1;
DECLARE #t TABLE(id INT, new_val NVARCHAR(10));
INSERT INTO #t(id, new_val) VALUES(1, 'true'),(3, 'true');
--- single query
WITH cte AS (
SELECT o.*,
s.[key],
JSON_VALUE(s.value, '$.id') AS id,
JSON_VALUE(s.value, '$.isDeleted') AS isDeleted
FROM Orders o
CROSS APPLY OPENJSON(o.Details ,N'$.items') s
WHERE o.OrderId = #OrderId
), cte_new AS (
SELECT DISTINCT c.OrderId, c.Details, s.Details_new
FROM cte c
CROSS APPLY (
SELECT c2.id, isDeleted = COALESCE(t.new_val, c2.IsDeleted)
FROM cte c2
LEFT JOIN #t t
ON c2.id = t.id
WHERE c2.OrderId = c.OrderId
FOR JSON AUTO) s(Details_new)
)
UPDATE o
SET Details = cn.Details_new
FROM Orders o
JOIN cte_new cn
ON o.OrderId = cn.OrderId;
db<>fiddle demo
How it works:
Parse JSON to tabular format
Perform data manipulation(here using #t as parameter)
Aggregate back to JSON
Perform UPDATE
I don't have the right version of SQL Server to test out this code. But, you should be able to query and modify the data and generate a new json string.
DECLARE #json nvarchar(max) = '{"items" : [{"id": 1, "isDeleted": false}, {"id": 2, "isDeleted": false}, {"id": 3, "isDeleted": false}, {"id": 4, "isDeleted": false}]}'
SELECT *
FROM OPENJSON(#json)
WITH (id int '$.items.id', isDeleted bit '$.items.isDeleted')

Add range to an array with JSON_MODIFY

I am trying to add an array to another array using JSON_MODIFY.
The situation is, I have an array kind stored json data in database. It looks like this:
declare #base nvarchar(max) = '[{"name":"base"}]';
And I am getting another set of data which is also in the shape of array:
declare #test1 nvarchar(max) = '[{"name":"test1"},{"name":"example1"}]';
I am trying to use JSON_MODIFY and JSON_QUERY magics to append them together but it gives me unexpected results.
declare #base nvarchar(max) = '[{"name":"base"}]';
declare #test1 nvarchar(max) = '[{"name":"test1"},{"name":"example1"}]';
set #base = JSON_MODIFY(#base,'append $',JSON_QUERY(#test1));
select #base;
Output:
[{"name":"base"}, [{"name":"test1"},{"name":"example1"}]]
But what I want is using those methods to make it work like kind of Add-Range:
[{"name":"base"},{"name":"test1"},{"name":"example1"}]
I am kind of lost on this process and I don't know where to look at for this kind of functionality.
I will use this from a C# service to directly modify through the code. That's why I cannot use Store procedures and functions as well.
Edit #1:
With regarding to reply from #Salman A, i appreciate your answer but the thing is, as i said earlier, it is a little bit difficult to use on my query running through code. Which is:
declare #test1 nvarchar(max) = '[{"name":"test1"},{"name":"example1"}]';
UPDATE dbo.ExampleTable
SET [Data] = JSON_MODIFY([Data], 'append $', JSON_QUERY(#test1))
WHERE [UniqueId] = 'some_guid_here'
I have tried it to adapt the answer that i like this :
declare #test1 nvarchar(max) = '[{"name":"test1"},{"name":"example1"}]';
UPDATE dbo.ExampleTable
SET [Data] = (
select [Data] = JSON_MODIFY([Data],'append $',item)
from OPENJSON(#test1)
with ([item] nvarchar(max) '$' as JSON)
)
WHERE [UniqueId] = 'some_id'
Actually, it works if #test1 only have 1 item, but in case of having more than 1 item in #test1, it gives the error:
Subquery returned more than 1 value. This is not permitted when the subquery follows = .....
What is the logical way to use this in a update set subquery
You can use OPENJSON to convert the array to rows and append items one by one:
declare #base nvarchar(max) = '[{"name":"base"}]';
declare #test1 nvarchar(max) = '[{"name":"test1"},{"name":"example1"}]';
select #base = json_modify(#base, 'append $', item)
from openjson(#test1)
with ([item] nvarchar(max) '$' as json);
select #base;
Returns:
[{"name":"base"},{"name":"test1"},{"name":"example1"}]
Revised answer for update query
If you're using SQL Server 2017+ then a reasonably safe solution is to concatenate the array using STRING_AGG but build individual rows using JSON functions. It is relatively easy to use this idea in an update query:
DECLARE #base NVARCHAR(MAX) = '[{"name":"base"}]';
DECLARE #test NVARCHAR(MAX) = '[{"foo":"bar"},{"baz":"meh"}]';
SELECT '[' + STRING_AGG(jsonstr, ',') WITHIN GROUP (ORDER BY pos) + ']'
FROM (
SELECT value, 1000 + [key] FROM OPENJSON(#base)
UNION ALL
SELECT value, 2000 + [key] FROM OPENJSON(#test)
) AS x(jsonstr, pos);
Alternately, you can use a recursive CTE that calls JSON_MODIFY multiple times to build the JSON; you can use the result in update query:
CREATE TABLE t(
id INT NOT NULL PRIMARY KEY IDENTITY,
data NVARCHAR(MAX)
);
INSERT INTO t(data) VALUES
('[{"name":"1.1"}]'),
('[{"name":"2.1"},{"name":"2.2"}]');
WITH rows(data, pos) AS (
SELECT value, [key]
FROM OPENJSON('[{"foo":"bar"},{"baz":"meh"}]')
), rcte(id, data, pos) AS (
SELECT id, data, -1
FROM t
UNION ALL
SELECT prev.id, JSON_MODIFY(prev.data, 'append $', JSON_QUERY(curr.data)), prev.pos + 1
FROM rcte AS prev
JOIN rows AS curr ON curr.pos = prev.pos + 1
)
UPDATE t
SET data = (
SELECT TOP 1 data
FROM rcte
WHERE id = t.id
ORDER BY pos DESC
);
Demo on db<>fiddle

SQL CSV as Query Results Column

I have the following SQL which queries a single table, single row, and returns the results as a comma separate string e.g.
Forms
1, 10, 4
SQL :
DECLARE #tmp varchar(250)
SET #tmp = ''
SELECT #tmp = #tmp + Form_Number + ', '
FROM Facility_EI_Forms_Required
WHERE Facility_ID = 11 AND EI_Year=2012 -- single Facility, single year
SELECT SUBSTRING(#tmp, 1, LEN(#tmp) - 1) AS Forms
The Facility_EI_Forms_Required table has three records for Facility_ID = 11
Facility_ID EI_Year Form_Number
11 2012 1
11 2012 10
11 2012 4
Form_number is a varchar field.
And I have a Facility table with Facility_ID and Facility_Name++.
How do I create a query to query all Facilites for a given year and produce the CSV output field?
I have this so far:
DECLARE #tmp varchar(250)
SET #tmp = ''
SELECT TOP 100 A.Facility_ID, A.Facility_Name,
(
SELECT #tmp = #tmp + B.Form_Number + ', '
FROM B
WHERE B.Facility_ID = A.Facility_ID
AND B.EI_Year=2012
)
FROM Facility A, Facility_EI_Forms_Required B
But it gets syntax errors on using #tmp
My guess is this is too complex a task for a query and a stored procedure may be need, but I have little knowledge of SPs. Can this be done with a nested query?
I tried a Scalar Value Function
ALTER FUNCTION [dbo].[sp_func_EI_Form_List]
(
-- Add the parameters for the function here
#p1 int,
#pYr int
)
RETURNS varchar
AS
BEGIN
-- Declare the return variable here
DECLARE #Result varchar
-- Add the T-SQL statements to compute the return value here
DECLARE #tmp varchar(250)
SET #tmp = ''
SELECT #tmp = #tmp + Form_Number + ', '
FROM OIS..Facility_EI_Forms_Required
WHERE Facility_ID = #p1 AND EI_Year = #pYr -- single Facility, single year
SELECT #Result = #tmp -- SUBSTRING(#tmp, 1, LEN(#tmp) - 1)-- #p1
-- Return the result of the function
RETURN #Result
END
The call
select Facility_ID, Facility.Facility_Name,
dbo.sp_func_EI_Form_List(Facility_ID,2012)
from facility where Facility_ID=11
returns
Facility_ID Facility_Name Form_List
11 Hanson Aggregates 1
so it is only returning the first record instead of all three. What am I doing wrong?
Try the following approach, which is an analogy to SO answer Concatenate many rows into a single text string. I hope it is correct, as I cannot try it out without having the schema and some demo data (maybe you can add schema and data to your question):
Select distinct A.Facility_ID, A.Facility_Name,
substring(
(
Select ',' + B.Form_Number AS [text()]
From Facility_EI_Forms_Required B
Where B.Facility_ID = A.Facility_ID
AND B.EI_Year=2012
ORDER BY B.Facility_ID
For XML PATH ('')
), 2, 1000) [Form_List]
From Facility A

SQL SERVER 2014 - Split String & Convert to INT

I have a string
1,2,3|5
After Split with | I have
1,2,3, 5
Now I want to convert 1,2,3 to INT. How can I achieve this?
Following is my query
Declare #tmpReferContentRule = "1,2,3|5"
SELECT items
FROM splitbystring(#tmpReferContentRule,'|')
WHERE id = 1
I want to use it in
SELECT name
FROM tmptable
WHERE id in(SELECT items FROM splitbystring(#tmpReferContentRule,'|'))
id is type of integer
The above query throws an error
1,2,3 could not be converted to int.
Please suggest a solution.
You can try this -
Declare #tmpReferContentRule varchar(20) = '1,2,3|5'
;with cte
AS
(
SELECT items as subitem from dbo.splitbystring(#tmpReferContentRule,'|')
)
SELECT name FROM tmptable WHERE id IN
(
SELECT t.items FROM cte
CROSS APPLY (SELECT * from dbo.splitbystring (subitem,',')) t
)
Try this methods
select name from tmptable as t1 inner join (#tmpReferContentRule,'|') as t2 on
','+t2.items+',' like '%,'+cast(t1.id as varchar(10)+',%'
where t2.id=1
You can convert that string into table with the help of xml:
DECLARE #string nvarchar(10) = '1,2,3|5',
#xml xml
SELECT #xml = CAST('<r><a>' + REPLACE(REPLACE(#string,',','</a><a>'),'|','</a><b>') +'</b></r>' as xml)
SELECT t.v.value('.','int') as id
FROM #xml.nodes('/r/a') as t(v)
Output:
id
1
2
3
I see you have been already using a split string function
I have a similar split string sql function, I used it as follows twice in your script
Declare #tmpReferContentRule varchar(max) = '1,2,3|5'
SELECT *
FROM Table1 t
inner join (
select
s2.val
from dbo.Split(#tmpReferContentRule,'|') s1
cross apply dbo.Split(s1.val,',') s2
WHERE
s1.id = 1
) s on t.id = s.val