Query SQL database with JSON Value - json

Here is my JSON:
[{"Key":"schedulerItemType","Value":"schedule"},{"Key":"scheduleId","Value":"82"},{"Key":"scheduleEventId","Value":"-1"},{"Key":"scheduleTypeId","Value":"2"},{"Key":"scheduleName","Value":"Fixed Schedule"},{"Key":"moduleId","Value":"5"}]
I want to query the database by FileMetadata column
I've tried this:
SELECT * FROM FileSystemItems WHERE JSON_VALUE(FileMetadata, '$.Key') = 'scheduleId' and JSON_VALUE(FileMetadata, '$.Value') = '82'
but it doesn't work!
I had it working with just a dictionary key/value pair, but I needed to return the data differently, so I am adding it with key and value into the json now.
What am I doing wrong?

With the sample data given you'd have to supply an array index to query the 1th element (0-based array indexes), e.g.:
select *
from dbo.FileSystemItems
where json_value(FileMetadata, '$[1].Key') = 'scheduleId'
and json_value(FileMetadata, '$[1].Value') = '82'
If the scheduleId key can appear at arbitrary positions in the array then you can restructure the query to use OPENJSON instead, e.g.:
select *
from dbo.FileSystemItems
cross apply openjson(FileMetadata) with (
[Key] nvarchar(50) N'$.Key',
Value nvarchar(50) N'$.Value'
) j
where j.[Key] = N'scheduleId'
and j.Value = N'82'

Related

FOR JSON PATH in CASE statement

When I use a variable (or a field from a table) in a case statement with "FOR JSON PATH", the JSON provided is not well formed.
Ex:
declare #MyValue nvarchar(50)
set #MyValue='1'
select CASE WHEN #MyValue='1' THEN (select 'ROLE_CLIENT_READONLY' as id FOR JSON PATH) end as [Role] FOR JSON PATH
Return
[{"Role":"[{\"id\":\"ROLE_CLIENT_READONLY\"}]"}]
But if I put this, it's works:
select CASE WHEN '1'='1' THEN (select 'ROLE_CLIENT_READONLY' as id FOR JSON PATH) end as [Role] FOR JSON PATH
Return
[{"Role":[{"id":"ROLE_CLIENT_READONLY"}]}]
Any idea on the reason for this behavior?
How can I fix this in the first scenario?
Not sure why it treats one different than the other. I certainly would not expect the difference in JSON between using a variable in the query and a string literal.
Interestingly
CASE WHEN CAST(N'1' AS NVARCHAR(MAX)) = CAST(N'1' AS NVARCHAR(MAX))
also produces the problematic JSON, however
CASE WHEN CAST(N'1' AS NVARCHAR(50)) = CAST(N'1' AS NVARCHAR(50))
does not.
This seems to work as a workaround for using the variable in the query:
WITH ids AS (
SELECT CASE WHEN #MyValue = '1' THEN 'ROLE_CLIENT_READONLY' END id
)
SELECT (SELECT id FROM ids FOR JSON PATH) AS [Role] FOR JSON PATH;

How can I filter array values in a Json in Sql Server

How can I filter an array value without specifying the index in the array
This is my array
{
"level1":{
"level2":[
{
"level3":"test",
}
]
}
}
I want to retrive all rows that contains a level3 a value test.
Something like this
select * from Documents
where Json_Value(DocumentInfo, '$.level1.level2[X].level3') = 'test'
It this not possible in Sql Server?
Guessing of bit here, but use an EXISTS? This also assumes the JSON is valid, as the JSON in your question is not:
SELECT * --Should be a distinct column list
FROM dbo.Documents D
WHERE EXISTS (SELECT 1
FROM OPENJSON(D.DocumentInfo)
WITH (level1 nvarchar(MAX) AS JSON) L1
CROSS APPLY OPENJSON(L1.Level1)
WITH(level2 nvarchar(MAX) AS JSON) L2
CROSS APPLY OPENJSON(L2.Level2)
WITH (level3 varchar(10)) L3
WHERE L3.level3 = 'test');

Update json value from sql column value

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.
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...
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)

How to retrieve a value in a json object in sqlite when the key is an empty string?

I am processing an sqlite table which contains json objects. These json objects have keys that are empty strings. How can I retrieve the value? For example:
select json_extract('{"foo": "bar", "":"empty"}', '$.foo') as data;
-result: "bar"
How can I retrieve "empty"?
Using your example:
sqlite> SELECT value FROM json_each('{"foo":"bar","":"empty"}') WHERE key = '';
value
----------
empty
As part of a larger query from a table:
SELECT (SELECT j.value FROM json_each(t.your_json_column) AS j WHERE j.key = '') AS data
FROM your_table AS t;

MySQL JSON column add new array as element

I have a table with JSON type column, I want to update a column with new array element in existing JSON.
What needs to be done: add an array in JSON column when employee punch_in and add another array in JSON column when employee punch_out.
{"emp_sheet":[{"rulecode":"PUNCH_IN","result":1,"applytime":"2018-04-12 04:50:39"},{"rulecode":"PUNCH_OUT","result":1,"applytime":"2018-04-12 13:01:39"}]}
What I did, for employee punch_in:
UPDATE table
SET rule_codes = JSON_SET(COALESCE(rule_codes, '{}'), '$.emp_sheet', '{"rulecode":"PUNCH_IN","result":1,"applytime":"2018-04-12 04:50:39"}')
WHERE emp_id = 1
Result in rule_codes column =
{"emp_sheet": "{"rulecode":"PUNCH_IN","result":1,"applytime":"2018-04-12 04:50:39"}"}
Please help me to write update query for employee punch_out.
This would be easiest if you made $.emp_sheet a JSON array on punch in:
UPDATE table3
SET rule_codes = JSON_SET(COALESCE(rule_codes, JSON_OBJECT('emp_sheet', JSON_ARRAY())),
'$.emp_sheet[0]',
'{"rulecode":"PUNCH_IN","result":1,"applytime":"2018-04-12 04:50:39"}')
WHERE emp_id = 1
Then on punch out, you can add another element to the array:
UPDATE table3
SET rule_codes = JSON_SET(COALESCE(rule_codes, JSON_OBJECT('emp_sheet', JSON_ARRAY())),
'$.emp_sheet[1]',
'{"rulecode":"PUNCH_OUT","result":1,"applytime":"2018-04-12 13:01:39"}')
WHERE emp_id = 1;
SELECT rule_codes FROM table3 WHERE emp_id = 1
Output:
{"emp_sheet": [
"{\"rulecode\":\"PUNCH_IN\",\"result\":1,\"applytime\":\"2018-04-12 04:50:39\"}",
"{\"rulecode\":\"PUNCH_OUT\",\"result\":1,\"applytime\":\"2018-04-12 13:01:39\"}"
]}
Note that when you do the SET, the input JSON ('{"rulecode ... }') gets treated as a string, hence the escaped " in the output above. You can remove those with JSON_UNQUOTE when you extract i.e.
SELECT JSON_UNQUOTE(JSON_EXTRACT(rule_codes, '$.emp_sheet[0]')) FROM `table3`
or using the short-cut notation
SELECT rule_codes->>'$.emp_sheet[0]' FROM `table3`
Output:
{"rulecode":"PUNCH_IN","result":1,"applytime":"2018-04-12 04:50:39"}
Try to use JSON_ARRAY_APPEND instead of JSON_SET.
Manual - https://dev.mysql.com/doc/refman/8.0/en/json-modification-functions.html
I think it could be like this
rule_codes = JSON_ARRAY_APPEND(COALESCE(rule_codes, '{"emp_sheet":[]}'), '$.emp_sheet', '{"rulecode":"PUNCH_IN","result":1,"applytime":"2018-04-12 04:50:39"}')
or
rule_codes = IF(rule_codes IS NULL,'
'{"emp_sheet":[{"rulecode":"PUNCH_IN","result":1,"applytime":"2018-04-12 04:50:39"}]}',
JSON_ARRAY_APPEND(rule_codes, '$.emp_sheet', '{"rulecode":"PUNCH_IN","result":1,"applytime":"2018-04-12 04:50:39"}')
)