How to update multiple nested keys in a json field in single update query? - sql

I'm trying to write a function that updates a json (not jsonb) field (called settings) in a table (called user).
My json object looks like this (however it might not have some of the keys on any nesting level):
{
"audiences": ["val1", "val2"],
"inviteNumber": "123",
"workExperience": [
{
"company": {
"name": "Ace",
"industry": "Accounting",
"revenues": "1M-10M",
"size": "1-10"
},
"title": "Product",
"startDate": {
"month": 1,
"year": 2018
}
}
],
"notifications": {
"emailNotifications": true
},
"jobFunction": "Research & Development",
"phoneNumber": "2134447777",
"areasOfInterest": {
"Recruiting and Staffing": true
}
}
I need to be able to update the "title" and "name" fields of the 0th element inside "workExperience" array.
What I currently have is this:
create or replace function my_function()
returns trigger language plpgsql as $$
declare
companyName character varying(255);
begin
select company.name into companyName from company where id = new.companyid;
update "user" set
email = new.email,
"settings" = jsonb_set(
"settings"::jsonb,
'{workExperience,0}'::TEXT[],
format(
'{"company": {"name": %s}, "title": %s}',
'"' || companyName || '"', '"' || new.title || '"'
)::jsonb
)
where id = new.userid;
return new;
end $$;
However the above implementation rewrites the while workExperience object removing the keys other than company and title.
I've tried looking through this answer on SO, but still wasn't able to implement the updating correctly.
I see that the problem is with how the jsonb_set works and it does just what I tell it to do: set 0th element of "workExperience" array to the object I define inside format function.
Seems like I need to use multiple jsonb_set one inside another, but I can't figure out the way to do it correctly.
How can I update my json field correctly without removing other keys from the object?

jsonb_set() returns the modified JSON value.
You could nest the calls and change the company name first, and use the result of that as the input to another jsonb_set().
"settings" = jsonb_set(jsonb_set("settings"::jsonb, '{workExperience,0,company,name}', to_jsonb(companyname)),
'{workExperience,0,title}', to_jsonb(new.title)
)

Related

Cannot update document by index in FaunaDB

I'm attempting to update a document using an index in my FaunaDB collection using FQL.
Update(
Match(
Index('users_by_id'),
'user-1'
),
{
data: {
name: 'John'
}
}
)
This query gives me the following error:
Error: [
{
"position": [
"update"
],
"code": "invalid argument",
"description": "Ref expected, Set provided."
}
]
How can I update the document using the index users_by_id?
Match returns a set reference, not a document reference, because there could be zero or more matching documents.
If you are certain that there is a single document that matches, you can use Get. When you call Get with a set reference (instead of a document reference), the first item of the set is retrieved. Since Update requires a document reference, you can then use Select to retrieve the fetched document's reference.
For example:
Update(
Select(
"ref",
Get(Match(Index('users_by_id'), 'user-1'))
),
{
data: {
name: 'John'
}
}
)
If you have more than one match, you should use Paginate to "realize" the set into an array of matching documents, and then Map over the array to perform a bulk update:
Map(
Paginate(
Match(Index('users_by_id'), 'user-1')
),
Lambda(
"ref",
Update(
Var("ref"),
{
data: {
name: "John",
}
}
)
)
)
Note: For this to work, your index has to have an empty values definition, or it must explicitly define the ref field as the one and only value. If your index returns multiple fields, the Lambda function has to be updated to accept the same number of parameters as are defined in your index's values definition.

Postgresql update JSONB object to array

I don't know why, but probably PHP persisted some of my data as object and some of them as array. My table looks something like:
seller_info_address Table:
ID (INT) | address (JSONB) |
------------+--------------------------------------------------|
1 | {"addressLines":{"0":"Technology Park",...},...} |
2 | {"addressLines":["Technology Park",...],...} |
Some addressLines are objects:
{
"addressLines": {
"0": "Technology Park",
"1": "Blanchard Road",
"3": "Dublin",
"4": "2"
},
"companyName": "...",
"emailAddress": [],
"...": "..."
}
Some addressLines are arrays:
{
"addressLines": [
"Technology Park",
"Blanchard Road",
"Dublin",
"2"
],
"companyName": "...",
"emailAddress": [],
"...": "..."
}
I would like to equalize the data with a SQL query, but I'm not sure how to do it. All addressLines persisted as object should be updated to array form.
I am grateful for help, thanks!
You can convert the objects to an array using this:
select id, (select jsonb_agg(e.val order by e.key::int)
from jsonb_each(sia.address -> 'addressLines') as e(key,val))
from seller_info_address sia
where jsonb_typeof(address -> 'addressLines') = 'object'
The where condition makes sure we only do this for addresslines that are not an array.
The aggregation used can also be used inside an UPDATE statement:
update seller_info_address
set address = jsonb_set(address, '{addressLines}',
(select jsonb_agg(e.val order by e.key::int)
from jsonb_each(address -> 'addressLines') as e(key,val))
)
where jsonb_typeof(address -> 'addressLines') = 'object';
Ok, I have now found a solution myself. Definitely not the most eloquent solution. I'm sure there's a nicer and more efficient one, but it works...
DROP FUNCTION update_address_object_to_array(id INTEGER);
CREATE OR REPLACE FUNCTION
update_address_object_to_array(id INTEGER)
RETURNS VOID AS
$UPDATE_ADDRESS_OBJECT_TO_ARRAY$
BEGIN
UPDATE seller_info_address
SET address = jsonb_set(address, '{addressLines}', (
SELECT CASE
WHEN jsonb_agg(addressLines) IS NOT NULL THEN jsonb_agg(addressLines)
ELSE '[]'
END
FROM seller_info_address sia,
jsonb_each(address #> '{addressLines}') as t(key, addressLines)
WHERE jsonb_typeof(sia.address -> 'addressLines') = 'object'
AND seller_info_id = update_address_object_to_array.id
), true)
WHERE seller_info_id = update_address_object_to_array.id
AND jsonb_typeof(address -> 'addressLines') = 'object';
END
$UPDATE_ADDRESS_OBJECT_TO_ARRAY$
LANGUAGE 'plpgsql';
SELECT update_address_object_to_array(sia.seller_info_id)
FROM seller_info_address sia
WHERE jsonb_typeof(address -> 'addressLines') = 'object';
The inner SELECT fetches all lines in the addressLines object using jsonb_each and then aggregates them into an array using jsonb_agg. The conditional expressions is to prevented null cases.
The result is then stored in the UPDATE via jsonb_set to the required position in the json. The WHERES should be self-explanatory.

JSON_VALUE SQL Server function not returning all values

I have this nvarchar field with a JSON like this:
{"BOARD":"QC_Reference_Phone","SERIAL":"LGM700c2eee454","VERSION.INCREMENTAL":"1901817521542","CPU_ABI2":"armeabi","HOST":"CLD-BLD3-VM1-16","TIME":"1547801577000","MODEL":"LG-M700","MANUFACTURER":"LGE","USER":"jenkins","CPU_ABI":"armeabi-v7a","BRAND":"lge","DISPLAY":"OPM1.171019.026","FINGERPRINT":"lge/mh_global_com/mh:8.1.0/OPM1.171019.026/1901817521542:user/release-keys","HARDWARE":"mh","PRODUCT":"mh_global_com","BOOTLOADER":"unknown","VERSION.RELEASE":"8.1.0","ID":"OPM1.171019.026","UNKNOWN":"unknown","TYPE":"user","VERSION.SDK.NUMBER":"27","TAGS":"release-keys"}
And so my syntax is:
select JSON_VALUE(DeviceHardwareData,'$.VERSION.SDK.NUMBER') SDKVersion_nbr
FROM MyTable
It will work with all the other values within the JSON field but for "VERSION.SDK.NUMBER".
It returns a NULL Result for every row in my table.
I can actually get the value with the OPENJSON function, but I would like to know why it's specifically not returning the value for that attribute using JSON_Value
It doesn't work as you expect because in JSON Path syntax, the full stop character means "going one level down to a nested element under the following name". In order to extract the value with your path expression, your JSON structure should resemble the following:
"VERSION": {
"SDK": {
"NUMBER": 14
}
}
However, enclosing the element name in doublequotes in the path expression apparently does the trick:
declare #j nvarchar(max) = N'{
"VERSION.SDK.NUMBER": "27",
"VERSION": {
"SDK": {
"NUMBER": 14
}
}
}';
select json_value(#j, '$."VERSION.SDK.NUMBER"') as [TopValue],
json_value(#j, '$.VERSION.SDK.NUMBER') as [NestedValue];

Strip empty objects on generated json/jsonb on Postgresql

I stip all the nulls using json_strip_nulls easily but it causes to have some empty objects on the results:
{
"id": 1,
"organization_id": 1,
"pairing_id": 1,
"location": {},
"device": {
"tracking_id": 1
},
"events": [
{}
]
}
Is there any simple way to remove the empty objects too? In here location and the empty object in events are have to be removed.
You can find the complete examples with test data here in DB Fiddle.
In your certain example I'd suggest to add a couple of helper functions:
create or replace function json_object_nullif(
_data json
)
returns json
as $$
select nullif(json_strip_nulls(_data)::text, '{}')::json
$$ language sql;
create or replace function json_array_nullif(
_data json
)
returns json
as $$
select nullif(_data::text, '[null]')::json
$$ language sql;
and then adjust your view, so instead of json_build_object(...) you can use json_object_nullif(json_build_object(...)) and the same for json_agg.
db<>fiddle demo

Get value from array in JSON in SQL Server

Let's say we have this json in our database table. I want to select value from tags. I already know how to get an array from this data but I don't know how to access array members. The question would be how do I get the first value from the array? Is there a function for this task?
{
"info": {
"type": 1,
"address": {
"town": "Bristol",
"county": "Avon",
"country": "England"
},
"tags": ["Sport", "Water polo"]
},
"type": "Basic"
}
Query I already have:
SELECT JSON_QUERY(MyTable.Data, '$.info.tags')
FROM MyTable
This returns me:
["Sport", "Water polo"]
How do I get
Sport
JSON_QUERY returns an object or array. You need JSON_VALUE to return a scalar value, eg :
SELECT JSON_VALUE(Data, '$.info.tags[0]')
from MyTable
Check the section Compare JSON_VALUE and JSON_QUERY in the docs for more examples