3

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.

2
  • I want to copy the value from column ename say "stackoverflow" and update the json array fieldvalue key. output would be [{"CaptionName":"txtEname","FieldName":null,"FieldType":null,"FieldValue":"stackoverflow"}] Commented Jul 11, 2019 at 21:25
  • 1
    What is your SQL Server version? Commented Jul 11, 2019 at 21:39

2 Answers 2

4

UPDATE 2019-07-11

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:

  • We now have 2 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
  • The second does what your original CROSS APPLY did, only acting on the single array element.
  • We use the [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

ORIGINAL ANSWER

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:

  • Implement a loop to iterate through all matches
  • Convert from JSON to some other type, then convert back to JSON afterwards

I'll think on these / whether there's something cleaner, since neither sits comfortably at present...

Sign up to request clarification or add additional context in comments.

4 Comments

This worked well but updated the first index, what if in this array does not sit in the first index all the time.[ {"CaptionName":"txtCname","FieldName":null,"FieldType":null,"FieldValue":""}, {"CaptionName":"txtEname","FieldName":null,"FieldType":null,"FieldValue":""} ]
@ElNottoWorry I'd hoped you didn't have that requirement given your sample data - I don't have an easy answer; see extended answer for my current thoughts...
Woo! Solved it in a nice way: dbfiddle.uk/…
even original answer works well, if my json was one array thank you
0

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)   

2 Comments

We are on SQL 2016, 'STRING_AGG' is not a recognized built-in function name.
@ElNottoWorry Please, check updated answer. Thanks,

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.