Reputation: 71
Using SQL Server, I want to take column data and copy it into a json object column
I am using SQL Server to a query a column and a json data. What I want to do is to copy the data in column ename
to fieldvalue
column in the code below. If I could do it using SQL that would be great.
SELECT
a.id, a.ssn, a.ename, p.CaptionName, p.FieldName, p.FieldType, p.FieldValue
FROM
tablename as a
CROSS APPLY
OPENJSON (details)
WITH (CaptionName NVARCHAR(100),
FieldName NVARCHAR(100),
FieldType NVARCHAR(15),
FieldValue NVARCHAR(50)) AS P
WHERE
p.captionname = 'txtEname'
AND a.ssn = '000-00-0000'
My json string in the details column
[{"CaptionName":"txtEname","FieldName":null,"FieldType":null,"FieldValue":""}]
I'm really not that good with sql which is what i want to use. after copying the data to the json object i will remove the ename column.
Upvotes: 3
Views: 46201
Reputation: 29943
If I understand your question, then one possible approach (if you use SQL Server 2017+) is to use OPENJSON()
and string manipulations with STRING_AGG()
:
Table:
CREATE TABLE #Data (
id int,
ssn varchar(12),
ename varchar(40),
details nvarchar(max)
)
INSERT INTO #Data
(id, ssn, ename, details)
VALUES
(1, '000-00-0000', 'stackoverflow1', N'[{"CaptionName":"txtEname","FieldName":null,"FieldType":null,"FieldValue":""}, {"CaptionName":"txtEname","FieldName":null,"FieldType":null,"FieldValue":""}]'),
(2, '000-00-0000', 'stackoverflow2', N'[{"CaptionName":"txtEname","FieldName":null,"FieldType":null,"FieldValue":""}, {"CaptionName":"txtEname","FieldName":null,"FieldType":null,"FieldValue":""}]')
Statement:
SELECT
d.id, d.ssn, d.ename,
CONCAT(N'[', STRING_AGG(JSON_MODIFY(j.[value], '$.FieldValue', ename), ','), N']') AS details
FROM #Data d
CROSS APPLY OPENJSON (d.details) j
WHERE JSON_VALUE(j.[value], '$.CaptionName') = N'txtEname' AND (d.ssn = '000-00-0000')
GROUP BY d.id, d.ssn, d.ename
Output:
id ssn ename details
1 000-00-0000 stackoverflow1 [{"CaptionName":"txtEname","FieldName":null,"FieldType":null,"FieldValue":"stackoverflow1"},{"CaptionName":"txtEname","FieldName":null,"FieldType":null,"FieldValue":"stackoverflow1"}]
2 000-00-0000 stackoverflow2 [{"CaptionName":"txtEname","FieldName":null,"FieldType":null,"FieldValue":"stackoverflow2"},{"CaptionName":"txtEname","FieldName":null,"FieldType":null,"FieldValue":"stackoverflow2"}]
For SQL Server 2016 you may use FOR XML PATH
for string aggregation:
SELECT
d.id, d.ssn, d.ename,
CONCAT(N'[', STUFF(s.details, 1, 1, N''), N']') AS details
FROM #Data d
CROSS APPLY (
SELECT CONCAT(N',', JSON_MODIFY(j.[value], '$.FieldValue', ename))
FROM #Data
CROSS APPLY OPENJSON (details) j
WHERE
(JSON_VALUE(j.[value], '$.CaptionName') = N'txtEname') AND
(ssn = '000-00-0000') AND
(id = d.id) AND (d.ssn = ssn) AND (d.ename = ename)
FOR XML PATH('')
) s(details)
Upvotes: 0
Reputation: 24410
Here's an amended solution which works for scenarios when there are multiple values in the JSON: https://dbfiddle.uk/?rdbms=sqlserver_2017&fiddle=1fde45dfb604b2d5540c56f6c17a822d
update a
set details = JSON_MODIFY(details, '$[' + x.[key] + '].FieldValue', ename)
from dbo.tblUissAssignments a
CROSS APPLY OPENJSON (details, '$') x
CROSS APPLY OPENJSON (x.Value)
WITH (CaptionName NVARCHAR(100),
FieldName NVARCHAR(100),
FieldType NVARCHAR(15),
FieldValue NVARCHAR(50)) AS P
WHERE a.ssn = '000-00-0000'
and p.CaptionName = 'txtEname'
This is similar to my original answer (see below). However:
cross apply
statements. The first is used to split the JSON array into elements, so we get a key (index) and value (JSON object as a string), as documented here: https://learn.microsoft.com/en-us/sql/t-sql/functions/openjson-transact-sql?view=sql-server-2017#path[key]
returned by the first cross apply
to target the item in the array that we wish to update in our JSON_MODIFY
statement.NB: If it's possible for your JSON array to contain multiple objects that need updating, the best solution I can think of is to put the above statement into a loop; since 1 update will only update 1 index on a given JSON. Here's an example: https://dbfiddle.uk/?rdbms=sqlserver_2017&fiddle=120d2ac7dd3a024e5e503a5f64b0089e
declare @doWhileTrueFlag bit = 1
while (@doWhileTrueFlag = 1)
begin
update a
set details = JSON_MODIFY(details, '$[' + x.[key] + '].FieldValue', ename)
from dbo.tblUissAssignments a
CROSS APPLY OPENJSON (details, '$') x
CROSS APPLY OPENJSON (x.Value)
WITH (CaptionName NVARCHAR(100),
FieldName NVARCHAR(100),
FieldType NVARCHAR(15),
FieldValue NVARCHAR(50)) AS P
WHERE a.ssn = '000-00-0000'
and p.CaptionName = 'txtEname'
and p.FieldValue != ename --if it's already got the correct value, don't update it again
set @doWhileTrueFlag = case when @@RowCount > 0 then 1 else 0 end
end
Try this: https://dbfiddle.uk/?rdbms=sqlserver_2017&fiddle=b7b4d075cac6cd46239561ddb992ac90
update a
set details = JSON_MODIFY(details, '$[0].FieldValue', ename)
from dbo.tblUissAssignments a
cross apply
OPENJSON (details)
WITH (CaptionName NVARCHAR(100),
FieldName NVARCHAR(100),
FieldType NVARCHAR(15),
FieldValue NVARCHAR(50)) AS P
where a.ssn = '000-00-0000'
and p.captionname = 'txtEname'
More info on the JSON_MODIFY method here: https://learn.microsoft.com/en-us/sql/t-sql/functions/json-modify-transact-sql?view=sql-server-2017
The subtle bit is that you're updating a json array containing a json object; not a single object. For that you have to include the index on the root element. See this post for some useful info on JsonPath if you're unfamiliar: https://support.smartbear.com/alertsite/docs/monitors/api/endpoint/jsonpath.html
Regarding scenarios where there's multiple items in the array, ideally we'd use a filter expression, such as this:
update a
set details = JSON_MODIFY(details, '$[?(@.CaptionName == ''txtEname'')].FieldValue', ename)
from dbo.tblUissAssignments a
where a.ssn = '000-00-0000'
Sadly MS SQL doesn't yet support these (see this excellent post: https://modern-sql.com/blog/2017-06/whats-new-in-sql-2016)
As such, I think we need to apply a nasty hack. Two such approaches spring to mind:
I'll think on these / whether there's something cleaner, since neither sits comfortably at present...
Upvotes: 4