We are exploring the JSON feature in SQL Sever and for one of the scenarios we want to come up with a SQL which can return a JSON like below
[
{
"field": {
"uuid": "uuid-field-1"
},
"value": {
"uuid": "uuid-value" //value is an object
}
},
{
"field": {
"uuid": "uuid-field-2"
},
"value": "1". //value is simple integer
}
... more rows
]
The value field can be a simple integer/string or a nested object.
We are able to come up with a table which looks like below:
field.uuid | value.uuid | value|
------------|---------- | -----|
uuid-field-1| value-uuid | null |
uuid-field-2| null | 1 |
... more rows
But as soon as we apply for json path, it fails saying
Property 'value' cannot be generated in JSON output due to a conflict with another column name or alias. Use different names and aliases for each column in SELECT list.
Is it possible to do it somehow generate this? The value will either be in the value.uuid or value not both?
Note: We are open to possibility of if we can convert each row to individual JSON and add all of them in an array.
select
json_query((select v.[field.uuid] as 'uuid' for json path, without_array_wrapper)) as 'field',
value as 'value',
json_query((select v.[value.uuid] as 'uuid' where v.[value.uuid] is not null for json path, without_array_wrapper)) as 'value'
from
(
values
('uuid-field-1', 'value-uuid1', null),
('uuid-field-2', null, 2),
('uuid-field-3', 'value-uuid3', null),
('uuid-field-4', null, 4)
) as v([field.uuid], [value.uuid], value)
for json auto;--, without_array_wrapper;
The reason for this error is that (as is mentioned in the documentation) ... FOR JSON PATH clause uses the column alias or column name to determine the key name in the JSON output. If an alias contains dots, the PATH option creates nested objects. In your case value.uuid and value both generate a key with name value.
I can suggest an approach (probably not the best one), which uses JSON_MODIFY() to generate the expected JSON from an empty JSON array:
Table:
CREATE TABLE Data (
[field.uuid] varchar(100),
[value.uuid] varchar(100),
[value] int
)
INSERT INTO Data
([field.uuid], [value.uuid], [value])
VALUES
('uuid-field-1', 'value-uuid', NULL),
('uuid-field-2', NULL, 1),
('uuid-field-3', NULL, 3),
('uuid-field-4', NULL, 4)
Statement:
DECLARE #json nvarchar(max) = N'[]'
SELECT #json = JSON_MODIFY(
#json,
'append $',
JSON_QUERY(
CASE
WHEN [value.uuid] IS NOT NULL THEN (SELECT d.[field.uuid], [value.uuid] FOR JSON PATH, WITHOUT_ARRAY_WRAPPER)
WHEN [value] IS NOT NULL THEN (SELECT d.[field.uuid], [value] FOR JSON PATH, WITHOUT_ARRAY_WRAPPER)
END
)
)
FROM Data d
SELECT #json
Result:
[
{
"field":{
"uuid":"uuid-field-1"
},
"value":{
"uuid":"value-uuid"
}
},
{
"field":{
"uuid":"uuid-field-2"
},
"value":1
},
{
"field":{
"uuid":"uuid-field-3"
},
"value":3
},
{
"field":{
"uuid":"uuid-field-4"
},
"value":4
}
]
Related
I am trying to parse xml in my oracle DB to return individual json object. I have different xpath. I would like to extract data from oracle xml in a nested json format. I have multiple applicants in my xml data, I would like to extract the data as a nested json format to know what data belong to an applicant
Result format example
{
"loanApplication": {
"applicantGroup": [
{
"applicant": {
"birthDate": "1-1-1",
"maritalStatusDd": "3",
"languagePreferenceDd": "0",
"assetTypeDd": [1, 6],
"asset": [1500, 60000],
"Liabilities": [500, 600, 400],
"sumOfAsset": 61500,
"sumOfliabilities": 1500
}
},
{
"applicant": {
"birthDate": "2-2-2",
"maritalStatusDd": "0",
"languagePreferenceDd": "0",
"assetTypeDd": [2, 6, 9],
"asset": [5000, 20000, 100],
"Liabilities": [500, 600, 400],
"sumOfAsset": 25100,
"sumOfliabilities": 1500
}
}
]
}
}
sample data from xml
WITH t( xml ) AS
(
SELECT XMLType('<loanApplication xmlns="http://www.abcdef.com/Schema/FCX/1"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<applicantGroup>
<applicantGroupTypeDd>0</applicantGroupTypeDd>
<applicant>
<asset>
<assetDescription>neweg</assetDescription>
<assetTypeDd>1</assetTypeDd>
<assetValue>1500.0</assetValue>
</asset>
<asset>
<assetDescription>RayM</assetDescription>
<assetTypeDd>6</assetTypeDd>
<assetValue>60000</assetValue>
</asset>
<liability>
<broker>
<liabilityAmount>9999999.8</liabilityAmount>
<liabilityDescription>CI</liabilityDescription>
<liabilityTypeDd>2</liabilityTypeDd>
</broker>
</liability>
<employmentHistory>
<income>
<annualIncomeAmount>150000.0</annualIncomeAmount>
<incomeAmount>150000.0</incomeAmount>
<incomeDescription>income description</incomeDescription>
<incomePeriodDd>0</incomePeriodDd>
<incomeTypeDd>6</incomeTypeDd>
</income>
</employmentHistory>
</applicant>
</applicantGroup>
<applicantGroup>
<applicantGroupTypeDd>1</applicantGroupTypeDd>
<applicant>
<asset>
<assetDescription>neweg</assetDescription>
<assetTypeDd>2</assetTypeDd>
<assetValue>5000.0</assetValue>
</asset>
<asset>
<assetDescription>Bay</assetDescription>
<assetTypeDd>6</assetTypeDd>
<assetValue>20000</assetValue>
</asset>
<asset>
<assetDescription>TDC</assetDescription>
<assetTypeDd>9</assetTypeDd>
<assetValue>100</assetValue>
</asset>
<liability>
<broker>
<liabilityAmount>9999999.8</liabilityAmount>
<liabilityDescription>CI</liabilityDescription>
<liabilityTypeDd>2</liabilityTypeDd>
</broker>
</liability>
<liability>
<broker>
<liabilityAmount>9999999.8</liabilityAmount>
<liabilityDescription>CI</liabilityDescription>
<liabilityTypeDd>2</liabilityTypeDd>
</broker>
</liability>
<employmentHistory>
<income>
<annualIncomeAmount>150000.0</annualIncomeAmount>
<incomeAmount>150000.0</incomeAmount>
<incomeDescription>income description</incomeDescription>
<incomePeriodDd>0</incomePeriodDd>
<incomeTypeDd>6</incomeTypeDd>
</income>
</employmentHistory>
</applicant>
</applicantGroup>
</loanApplication>')
FROM dual
)
SELECT JSON_OBJECT (
KEY 'Assets' value y.Assets
,KEY 'assetType' VALUE (SELECT JSON_ARRAYAGG( val) FROM
XMLTABLE(XMLNAMESPACES(DEFAULT 'http://www.abcdef.com/Schema/FCX/1'),'/loanApplication/applicantGroup/applicant/asset/assetTypeDd'
PASSING (EXTRACT(xml, '/loanApplication', 'xmlns="http://www.abcdef.com/Schema/FCX/1"') ) COLUMNS val INT PATH './text()') )
,KEY 'liability' VALUE (SELECT JSON_ARRAYAGG( val) FROM
XMLTABLE(XMLNAMESPACES(DEFAULT 'http://www.abcdef.com/Schema/FCX/1'),'/loanApplication/applicantGroup/applicant/liability/broker/BorrowerLiabilityType'
PASSING (EXTRACT(xml, '/loanApplication', 'xmlns="http://www.abcdef.com/Schema/FCX/1"') ) COLUMNS val INT PATH './text()') )
) applicant
FROM t,
XMLTABLE(XMLNAMESPACES(DEFAULT 'http://www.abcdef.com/Schema/FCX/1'), '/loanApplication/applicantGroup/applicant/asset'
PASSING xml
COLUMNS
Assets INT PATH 'assetValue') y
You can start with grouping by applicantGroupTypeDd in order to seperate innermost objects(applicant), and go on with suitable function(JSON_OBJECT or JSON_ARRAYAGG) to the topmost level such as
WITH t(xml) AS
(SELECT XMLType(<yourXMLvalue>)
FROM dual)
SELECT JSON_OBJECT(KEY 'loanApplication' VALUE
JSON_OBJECT(KEY 'applicantGroup' VALUE
JSON_ARRAYAGG(JSON_OBJECT(KEY 'applicant'
VALUE JSON_OBJECT(KEY 'assetTypeDd' VALUE JSON_ARRAYAGG(assetTypeDd),
KEY 'asset' VALUE JSON_ARRAYAGG(assetValue),
KEY 'sumOfAsset' VALUE SUM(assetValue))))))
FROM t,
XMLTABLE(XMLNAMESPACES(DEFAULT 'http://www.abcdef.com/Schema/FCX/1'),
'/loanApplication/applicantGroup' PASSING t.xml COLUMNS
applicantGroupTypeDd INT PATH 'applicantGroupTypeDd',
asset XMLTYPE PATH 'applicant/asset') t1,
XMLTABLE(XMLNAMESPACES(DEFAULT 'http://www.abcdef.com/Schema/FCX/1'),
'asset' PASSING t1.asset COLUMNS
assetTypeDd INT PATH 'assetTypeDd',
assetValue INT PATH 'assetValue') t2
GROUP BY applicantGroupTypeDd
Demo
I have table people and it's maintain Four column which is Name ,TagName ,Value , Location.
I want to convert the tagname and value in json with name and location column as rootnode (Name & location same for multiple records)
Need output as :
{
"{"Name":"EMP1","Location":"mumbai"}": [
{
"TagName": "1",
"Value": "844.17769999999996"
},
{
"TagName": "abc",
"Value": "837.43679999999995"
},
{
"TagName": "pqr",
"Value": "0"
},
{
"TagName": "XYZ",
"Value": "1049.2429999999999"
}
]
}
please check the below query, In which I am trying to create json string using json path but stuck in root node.
SELECT TagName
,Value
FROM dbo.people
FOR JSON PATH, ROOT('')---
when I convert the above json into tabular format, required output as :
Name | Location |TagName| Value
EMP1 | Mumbai |1 | 844.17769999999996|
EMP1 | Mumbai |abc | 837.43679999999995|
.....
Your expected output is not a valid JSON, but you are probably looking for something like this:
Table:
CREATE TABLE People (
[Name] varchar(10),
[Location] varchar(50),
[TagName] varchar(3),
[Value] numeric(20, 14)
)
INSERT INTO People ([Name], [Location], [TagName], [Value])
VALUES
('EMP1', 'Mumbai', '1', 844.17769999999996),
('EMP1', 'Mumbai', 'abc', 837.43679999999995),
('EMP2', 'Mumbai', 'abc', 837.43679999999995)
Statement:
SELECT DISTINCT p.[Name], p.[Location], c.Items
FROM People p
CROSS APPLY (
SELECT [TagName], [Value]
FROM People
WHERE [Name] = p.[Name] AND [Location] = p.[Location]
FOR JSON AUTO
) c (Items)
FOR JSON PATH
Result:
[
{
"Name":"EMP1",
"Location":"Mumbai",
"Items":[
{
"TagName":"1",
"Value":844.17769999999996
},
{
"TagName":"abc",
"Value":837.43679999999995
}
]
},
{
"Name":"EMP2",
"Location":"Mumbai",
"Items":[
{
"TagName":"abc",
"Value":837.43679999999995
}
]
}
]
If you want to parse the generated JSON, you need to use OPENJSON() twice:
Generated JSON:
DECLARE #json varchar(max) = N'[
{
"Name":"EMP1",
"Location":"Mumbai",
"Items":[
{
"TagName":"1",
"Value":844.17769999999996
},
{
"TagName":"abc",
"Value":837.43679999999995
}
]
},
{
"Name":"EMP2",
"Location":"Mumbai",
"Items":[
{
"TagName":"abc",
"Value":837.43679999999995
}
]
}
]'
Statement:
SELECT j1.Name, j1.Location, j2.TagName, j2.Value
FROM OPENJSON(#json) WITH (
[Name] varchar(10) '$.Name',
[Location] varchar(50) '$.Location',
[Items] nvarchar(max) '$.Items' AS JSON
) j1
OUTER APPLY OPENJSON(j1.Items) WITH (
[TagName] varchar(3) '$.TagName',
[Value] numeric(20, 14) '$.Value'
) j2
I am using mssql and one column is having json data, I want to update that part of that json which is an array, by passing the id.
{
"customerName":"mohan",
"custId":"e35273d0-c002-11e9-8188-a1525f580dfd",
"feeds":[
{
"feedId":"57f221d0-c310-11e9-8af7-cf1cf42fc72e",
"feedName":"ccsdcdscsdc",
"format":"Excel",
"sources":[
{
"sourceId":69042417,
"name":"TV 2 Livsstil"
},
{
"sourceId":69042419,
"name":"Turk Max"
}
]
},
{
"feedId":"59bbd360-c312-11e9-8af7-cf1cf42fc72e",
"feedName":"dfgdfgdfgdfgsdfg",
"format":"XmlTV",
"sources":[
{
"sourceId":69042417,
"name":"TV 2 Livsstil"
},
{
"sourceId":69042419,
"name":"Turk Max"
}
]
}
]
}
suppose if I am going to pass customerId and feedId, it should update the whole feed with the feed which I have passed.
I tried with below query, but no help.
UPDATE
ExtractsConfiguration.dbo.Customers
SET
configJSON = JSON_MODIFY(configJSON,'$.feeds[]',{"feedName":"ccsdcdscsdc"})
WHERE
CustomerId = '9ee07040-c001-11e9-b29a-55eb3439cd7c'
AND json_query(configJSON,'$.feeds[].feedId'='57f221d0-c310-11e9-8af7-cf1cf42fc72e');
This, #mohan, is a tricky one and I took it on as a challenge to myself. There is a way to update a nested JSON object's value like you're asking, however, it's not as straight forward as it seems.
Because you're working within an array, you need the array's index in order to update a nested value. In your case you don't know the index within the array, however, you do have a key-value you can reference, in this case, your feedName.
In order to update your value, you first need to "unpack" your JSON so that you can filter for a specific feedName, "ccsdcdscsdc" in your example.
Here is an example that you can run in SSMS that will get you moving in the right direction.
The first thing I created was #Customers TABLE variable to mimic the data structure you showed in your example and inserted your sample data.
DECLARE #Customers TABLE ( CustomerId VARCHAR(50), configJSON VARCHAR(MAX) );
INSERT INTO #Customers ( CustomerID, configJSON ) VALUES ( '9ee07040-c001-11e9-b29a-55eb3439cd7c', '{"customerName":"mohan","custId":"e35273d0-c002-11e9-8188-a1525f580dfd","feeds":[{"feedId":"57f221d0-c310-11e9-8af7-cf1cf42fc72e","feedName":"ccsdcdscsdc","format":"Excel","sources":[{"sourceId":69042417,"name":"TV 2 Livsstil"},{"sourceId":69042419,"name":"Turk Max"}]},{"feedId":"59bbd360-c312-11e9-8af7-cf1cf42fc72e","feedName":"dfgdfgdfgdfgsdfg","format":"XmlTV","sources":[{"sourceId":69042417,"name":"TV 2 Livsstil"},{"sourceId":69042419,"name":"Turk Max"}]}]}' );
Running a SELECT against #Customers returns the following:
+--------------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| CustomerId | configJSON |
+--------------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| 9ee07040-c001-11e9-b29a-55eb3439cd7c | {"customerName":"mohan","custId":"e35273d0-c002-11e9-8188-a1525f580dfd","feeds":[{"feedId":"57f221d0-c310-11e9-8af7-cf1cf42fc72e","feedName":"ccsdcdscsdc","format":"Excel","sources":[{"sourceId":69042417,"name":"TV 2 Livsstil"},{"sourceId":69042419,"name":"Turk Max"}]},{"feedId":"59bbd360-c312-11e9-8af7-cf1cf42fc72e","feedName":"dfgdfgdfgdfgsdfg","format":"XmlTV","sources":[{"sourceId":69042417,"name":"TV 2 Livsstil"},{"sourceId":69042419,"name":"Turk Max"}]}]} |
+--------------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
Next, I matched your rules for the update: Update a nested JSON value that is restricted to a specific CustomerId (9ee07040-c001-11e9-b29a-55eb3439cd7c) and a feedName (ccsdcdscsdc).
Like I mentioned, we need to "unpack" the JSON first because we don't know the specific key (index) value that should be updated. The easiest way to accomplish both tasks (unpack/update) is to use a Common Table Expression (CTE).
So, here's how I did that:
;WITH Config_CTE AS (
SELECT * FROM #Customers AS Customer
CROSS APPLY OPENJSON( configJSON, '$.feeds' ) AS Config
WHERE
Customer.CustomerId = '9ee07040-c001-11e9-b29a-55eb3439cd7c'
AND JSON_VALUE( Config.value, '$.feedName' ) = 'ccsdcdscsdc'
)
UPDATE Config_CTE
SET configJSON = JSON_MODIFY( configJSON, '$.feeds[' + Config_CTE.[key] + '].format', 'MS Excel' );
The CTE allows us to "unpack" (I made this word up as it seemed fitting) the JSON contained in configJSON, which then allows us to apply a filter against the feedName.
AND JSON_VALUE( Config.value, '$.feedName' ) = 'ccsdcdscsdc'
You'll also note that we included the CustomerId rule:
Customer.CustomerId = '9ee07040-c001-11e9-b29a-55eb3439cd7c'
Both the CustomerId and feedName could easily be SQL variables.
So, what did this do? If we were to look at Configs_CTE resultset ( by changing the UPDATE... to SELECT * FROM Config_CTE ) we would see:
+--------------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-----+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+------+
| CustomerId | configJSON | key | value | type |
+--------------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-----+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+------+
| 9ee07040-c001-11e9-b29a-55eb3439cd7c | {"customerName":"mohan","custId":"e35273d0-c002-11e9-8188-a1525f580dfd","feeds":[{"feedId":"57f221d0-c310-11e9-8af7-cf1cf42fc72e","feedName":"ccsdcdscsdc","format":"Excel","sources":[{"sourceId":69042417,"name":"TV 2 Livsstil"},{"sourceId":69042419,"name":"Turk Max"}]},{"feedId":"59bbd360-c312-11e9-8af7-cf1cf42fc72e","feedName":"dfgdfgdfgdfgsdfg","format":"XmlTV","sources":[{"sourceId":69042417,"name":"TV 2 Livsstil"},{"sourceId":69042419,"name":"Turk Max"}]}]} | 0 | {"feedId":"57f221d0-c310-11e9-8af7-cf1cf42fc72e","feedName":"ccsdcdscsdc","format":"Excel","sources":[{"sourceId":69042417,"name":"TV 2 Livsstil"},{"sourceId":69042419,"name":"Turk Max"}]} | 5 |
+--------------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-----+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+------+
There is a bunch of information here, but what we really care about is the "key" column as this contains the feed index ( in this case 0 ) that we want to update.
With that, was able to complete the request and UPDATE format from "Excel" to "MS Excel" for the "feed" with the feedName of "ccsdcdscsdc".
This guy ( note the use of Config_CTE.[key] ):
UPDATE Config_CTE
SET configJSON = JSON_MODIFY( configJSON, '$.feeds[' + Config_CTE.[key] + '].format', 'MS Excel' );
Did it work? Let's look at the updated table's data.
+--------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| CustomerId | configJSON |
+--------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| 9ee07040-c001-11e9-b29a-55eb3439cd7c | {"customerName":"mohan","custId":"e35273d0-c002-11e9-8188-a1525f580dfd","feeds":[{"feedId":"57f221d0-c310-11e9-8af7-cf1cf42fc72e","feedName":"ccsdcdscsdc","format":"MS Excel","sources":[{"sourceId":69042417,"name":"TV 2 Livsstil"},{"sourceId":69042419,"name":"Turk Max"}]},{"feedId":"59bbd360-c312-11e9-8af7-cf1cf42fc72e","feedName":"dfgdfgdfgdfgsdfg","format":"XmlTV","sources":[{"sourceId":69042417,"name":"TV 2 Livsstil"},{"sourceId":69042419,"name":"Turk Max"}]}]} |
+--------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
Here's the updated JSON "beautified" (pretty sure I didn't make up that one).
{
"customerName": "mohan",
"custId": "e35273d0-c002-11e9-8188-a1525f580dfd",
"feeds": [{
"feedId": "57f221d0-c310-11e9-8af7-cf1cf42fc72e",
"feedName": "ccsdcdscsdc",
"format": "MS Excel",
"sources": [{
"sourceId": 69042417,
"name": "TV 2 Livsstil"
}, {
"sourceId": 69042419,
"name": "Turk Max"
}]
}, {
"feedId": "59bbd360-c312-11e9-8af7-cf1cf42fc72e",
"feedName": "dfgdfgdfgdfgsdfg",
"format": "XmlTV",
"sources": [{
"sourceId": 69042417,
"name": "TV 2 Livsstil"
}, {
"sourceId": 69042419,
"name": "Turk Max"
}]
}]
}
Well, there you have it, format for feedName "ccsdcdscsdc" has been updated from "Excel" to "MS Excel". I was not clear on what you were trying to update, so I used format for my testing/example.
I hope this gets you moving in the right direction with your task. Happy coding!
Here's the complete example that can be run in SSMS:
-- CREATE A CUSTOMERS TABLE TO MIMIC SCHEMA --
DECLARE #Customers TABLE ( CustomerId VARCHAR(50), configJSON VARCHAR(MAX) );
INSERT INTO #Customers ( CustomerID, configJSON ) VALUES ( '9ee07040-c001-11e9-b29a-55eb3439cd7c', '{"customerName":"mohan","custId":"e35273d0-c002-11e9-8188-a1525f580dfd","feeds":[{"feedId":"57f221d0-c310-11e9-8af7-cf1cf42fc72e","feedName":"ccsdcdscsdc","format":"Excel","sources":[{"sourceId":69042417,"name":"TV 2 Livsstil"},{"sourceId":69042419,"name":"Turk Max"}]},{"feedId":"59bbd360-c312-11e9-8af7-cf1cf42fc72e","feedName":"dfgdfgdfgdfgsdfg","format":"XmlTV","sources":[{"sourceId":69042417,"name":"TV 2 Livsstil"},{"sourceId":69042419,"name":"Turk Max"}]}]}' );
-- SHOW CURRENT DATA --
SELECT * FROM #Customers;
-- UPDATE "format" FROM "Excel" to "MS Excel" FOR feedName: ccsdcdscsdc --
WITH Config_CTE AS (
SELECT * FROM #Customers AS Customer
CROSS APPLY OPENJSON( configJSON, '$.feeds' ) AS Config
WHERE
Customer.CustomerId = '9ee07040-c001-11e9-b29a-55eb3439cd7c'
AND JSON_VALUE( Config.value, '$.feedName' ) = 'ccsdcdscsdc'
)
UPDATE Config_CTE
SET configJSON = JSON_MODIFY( configJSON, '$.feeds[' + Config_CTE.[key] + '].format', 'MS Excel' );
-- SHOW UPDATED DATA --
SELECT * FROM #Customers;
EDIT:
i wanted to update the feed with the given feedId with the whole new
feed
To replace one "feed" with an entirely new feed, you may do the following:
-- REPLACE AN ENTIRE JSON ARRAY OBJECT --
DECLARE #MyNewJson NVARCHAR(MAX) = '{"feedId": "this_is_an_entirely_new_node","feedName": "ccsdcdscsdc","format": "NewFormat","sources": [{"sourceId": 1,"name": "New Source 1"},{"sourceId": 2,"name": "New Source 2"}]}';
WITH Config_CTE AS (
SELECT * FROM #Customers AS Customer
CROSS APPLY OPENJSON( configJSON, '$.feeds' ) AS Config
WHERE
Customer.CustomerId = '9ee07040-c001-11e9-b29a-55eb3439cd7c'
AND JSON_VALUE( Config.value, '$.feedName' ) = 'ccsdcdscsdc'
)
UPDATE Config_CTE
SET configJSON = JSON_MODIFY( configJSON, '$.feeds[' + Config_CTE.[key] + ']', JSON_QUERY( #MyNewJson ) );
After running this, the feeds now appear as:
{
"customerName": "mohan",
"custId": "e35273d0-c002-11e9-8188-a1525f580dfd",
"feeds": [
{
"feedId": "this_is_an_entirely_new_node",
"feedName": "ccsdcdscsdc",
"format": "NewFormat",
"sources": [
{
"sourceId": 1,
"name": "New Source 1"
},
{
"sourceId": 2,
"name": "New Source 2"
}
]
},
{
"feedId": "59bbd360-c312-11e9-8af7-cf1cf42fc72e",
"feedName": "dfgdfgdfgdfgsdfg",
"format": "XmlTV",
"sources": [
{
"sourceId": 69042417,
"name": "TV 2 Livsstil"
},
{
"sourceId": 69042419,
"name": "Turk Max"
}
]
}
]
}
Note the use of JSON_QUERY( #MyNewJson ) in the UPDATE. This is important.
From Microsoft's Docs:
JSON_QUERY without its optional second parameter returns only the
first argument as a result. Since JSON_QUERY always returns valid
JSON, FOR JSON knows that this result does not have to be escaped.
If you were to pass #MyNewJson without the JSON_QUERY your new json would be escaped ( e.g., "customerName" becomes \"customerName\" ) as if it were being stored as plain text. JSON_QUERY will return unescaped, valid JSON which is necessary in your case.
Also note that the only change I made to replace the entire feed vs. a single item value was switching
'$.feeds[' + Config_CTE.[key] + '].format'
to
'$.feeds[' + Config_CTE.[key] + ']'.
In my postgres database I have json that looks similar to this:
{
"myArray": [
{
"myValue": 1
},
{
"myValue": 2
},
{
"myValue": 3
}
]
}
Now I want to rename myValue to otherValue. I can't be sure about the length of the array! Preferably I would like to use something like set_jsonb with a wildcard as the array index, but that does not seem to be supported. So what is the nicest solution?
You have to decompose a whole jsonb object, modify individual elements and build the object back.
The custom function will be helpful:
create or replace function jsonb_change_keys_in_array(arr jsonb, old_key text, new_key text)
returns jsonb language sql as $$
select jsonb_agg(case
when value->old_key is null then value
else value- old_key || jsonb_build_object(new_key, value->old_key)
end)
from jsonb_array_elements(arr)
$$;
Use:
with my_table (id, data) as (
values(1,
'{
"myArray": [
{
"myValue": 1
},
{
"myValue": 2
},
{
"myValue": 3
}
]
}'::jsonb)
)
select
id,
jsonb_build_object(
'myArray',
jsonb_change_keys_in_array(data->'myArray', 'myValue', 'otherValue')
)
from my_table;
id | jsonb_build_object
----+------------------------------------------------------------------------
1 | {"myArray": [{"otherValue": 1}, {"otherValue": 2}, {"otherValue": 3}]}
(1 row)
Using json functions are definitely the most elegant, but you can get by on using character replacement. Cast the json(b) as text, perform the replace, then change it back to json(b). In this example I included the quotes and colon to help the text replace target the json keys without conflict with values.
CREATE TABLE mytable ( id INT, data JSONB );
INSERT INTO mytable VALUES (1, '{"myArray": [{"myValue": 1},{"myValue": 2},{"myValue": 3}]}');
INSERT INTO mytable VALUES (2, '{"myArray": [{"myValue": 4},{"myValue": 5},{"myValue": 6}]}');
SELECT * FROM mytable;
UPDATE mytable
SET data = REPLACE(data :: TEXT, '"myValue":', '"otherValue":') :: JSONB;
SELECT * FROM mytable;
http://sqlfiddle.com/#!17/1c28a/9/4
I'm using PostgreSQL 9.4.5. I'd like to update a jsonb column.
My table is structured this way:
CREATE TABLE my_table (
gid serial PRIMARY KEY,
"data" jsonb
);
JSON strings are like this:
{"files": [], "ident": {"id": 1, "country": null, "type ": "20"}}
The following SQL doesn't do the job (syntax error - SQL state = 42601):
UPDATE my_table SET "data" -> 'ident' -> 'country' = 'Belgium';
Is there a way to achieve that?
Ok there are two functions:
create or replace function set_jsonb_value(p_j jsonb, p_key text, p_value jsonb) returns jsonb as $$
select jsonb_object_agg(t.key, t.value) from (
select
key,
case
when jsonb_typeof(value) = 'object' then set_jsonb_value(value, p_key, p_value)
when key = p_key then p_value
else value
end as value from jsonb_each(p_j)) as t;
$$ language sql immutable;
First one just changes the value of the existing key regardless of the key path:
postgres=# select set_jsonb_value(
'{"files": [], "country": null, "ident": {"id": 1, "country": null, "type ": "20"}}',
'country',
'"foo"');
set_jsonb_value
--------------------------------------------------------------------------------------
{"files": [], "ident": {"id": 1, "type ": "20", "country": "foo"}, "country": "foo"}
(1 row)
create or replace function set_jsonb_value(p_j jsonb, p_path text[], p_value jsonb) returns jsonb as $$
select jsonb_object_agg(t.key, t.value) from (
select
key,
case
when jsonb_typeof(value) = 'object' then set_jsonb_value(value, p_path[2:1000], p_value)
when key = p_path[1] then p_value
else value
end as value from jsonb_each(p_j)
union all
select
p_path[1],
case
when array_length(p_path,1) = 1 then p_value
else set_jsonb_value('{}', p_path[2:1000], p_value) end
where not p_j ? p_path[1]) as t;
$$ language sql immutable;
Second one changes the value of the existing key using the path specified or creates it if the path does not exists:
postgres=# select set_jsonb_value(
'{"files": [], "country": null, "ident": {"id": 1, "type ": "20"}}',
'{ident,country}'::text[],
'"foo"');
set_jsonb_value
-------------------------------------------------------------------------------------
{"files": [], "ident": {"id": 1, "type ": "20", "country": "foo"}, "country": null}
(1 row)
postgres=# select set_jsonb_value(
'{"files": [], "country": null, "ident": {"id": 1, "type ": "20"}}',
'{ident,foo,bar,country}'::text[],
'"foo"');
set_jsonb_value
-------------------------------------------------------------------------------------------------------
{"files": [], "ident": {"id": 1, "foo": {"bar": {"country": "foo"}}, "type ": "20"}, "country": null}
(1 row)
Hope it will help to someone who uses the PostgreSQL < 9.5
Disclaimer: Tested on PostgreSQL 9.5
In PG 9.4 you are out of luck with "easy" solutions like jsonb_set() (9.5). Your only option is to unpack the JSON object, make the changes and re-build the object. That sounds very cumbersome and it is indeed: JSON is horrible to manipulate, no matter how advanced or elaborate the built-in functions.
CREATE TYPE data_ident AS (id integer, country text, "type" integer);
UPDATE my_table
SET "data" = json_build_object('files', "data"->'files', 'ident', ident.j)::jsonb
FROM (
SELECT gid, json_build_object('id', j.id, 'country', 'Belgium', 'type', j."type") AS j
FROM my_table
JOIN LATERAL jsonb_populate_record(null::data_ident, "data"->'ident') j ON true) ident
WHERE my_table.gid = ident.gid;
In the SELECT clause "data"->'ident' is unpacked into a record (for which you need to CREATE TYPE a structure). Then it is built right back into a JSON object with the new country name. In the UPDATE that "ident" object is re-joined with the "files" object and the whole thing cast to a jsonb.
A pure thing of beauty -- just so long as speed is not your thing...
My previous solution relied on 9.5 functionality.
I would recommend instead either going with abelisto's solutions below or using pl/perlu, plpythonu, or plv8js to write json mutators in a language that has better support for them.