Postgres JSONB function to key an array of objects, by a field - postgresql

Is there a pure Postgres approach to turning a JSONB array like:
[{"id": 1, "name": "Dom" }, { "id": 2, "name": "Bass" }]
into
{ 1: {"id": 1, "name": "Dom" }, 2: { "id": 2, "name": "Bass" } }
I.e. same as Lodash doing:
_.keyBy(arr, 'id)

Use the function:
create or replace function jsonb_key_by(jsonb, text)
returns jsonb language sql immutable as $$
select jsonb_object_agg(elem->>$2, elem)
from jsonb_array_elements($1) as arr(elem)
$$;
Test it in db<>fiddle.

Related

Using subquery results as a condition of jsonb field indexed search

I have two following tables:
CREATE TABLE elements(
id INT PRIMARY KEY,
element_type INT,
api_name VARCHAR(100) UNIQUE
);
CREATE TABLE entries(
id INT PRIMARY KEY,
elements JSONB
)
entries.elements field stored a jsonb-array of elements in following format:
[
{
"id": elements.id
"type": elements.element_type
"value": <some_value>
},
{
"id": elements.id
"type": elements.element_type
"value": <some_value>
}
...
]
The value field can be one of 3 types:
Literal (i.e. "abc", 12, true)
Array of literals (i.e. ["abc", "cbd"], [12, 21])
Array of objects (i.e. [{"node_id": <uuid>, "value": "abc"}, {"node_id": <uuid>, "value": "cba"}])
And I have index (maybe incorrect)
CREATE INDEX some_idx_name
ON entries
USING GIN (elements jsonb_ops)
For example suppose that I have data in these tables:
INSERT INTO elements(id, element_type, api_name) VALUES
(1, 1, 'el_number_1'),
(2, 2, 'el_datetime'),
(3, 3, 'el_files'),
(4, 1, 'el_number_2');
INSERT INTO entries(id, elements) VALUES
(1, '[{"id": 1, "type": 1, "value": 12}, {"id": 3, "type": 3, "value": [1, 2]}]'::JSONB),
(2, '[{"id": 4, "type": 1, "value": 12}, {"id": 2, "type": 2, "value": [{"date": "20.12.2022", "time": "16:18"}]}]'::JSONB);
So if I want to find entry by elements I need to do something like this:
SELECT id
FROM entries
WHERE elements #? '$[*] ? (#.id == 4 && #.value = 12)'
But how can I find entry by value of elements which was found by api_name?
-- DOES NOT WORK (only for example what I need)
SELECT id
FROM entries
WHERE elements #? '$[*] ? (#.id == (SELECT id FROM elements WHERE api_name = 'el_number_2') && #.value = 12)'
Fiddle link

malformed array literal when convetring jsonb array of jsonb items to postgres array of jsonb by jsonb_array_elements

I have jsonb-array:
element_values := '[
{
"element_id": "a7993f3d-9256-4354-a147-5b9d18d7812b",
"value": true
},
{
"element_id": "ceeb364e-bb88-4f41-9c56-9e5f4d0bc1fb",
"value": None
},
...
]'::JSONB
And I want to convert it into array of jsonb objects: JSONB[]
I tried this method:
<<elements_n_values_relationship_create>>
DECLARE
elements_n_values_relationship JSONB[];
BEGIN
SELECT * FROM jsonb_array_elements(element_values) INTO elements_n_values_relationship;
...
END;
But I got the following error:
ERROR: malformed array literal: "{"value": true, "element_id": "a7993f3d-9256-4354-a147-5b9d18d7812b"}"
DETAIL: Unexpected array element.
Why it does not work?
You have to use null in place of None to make your statement work
EDIT:
Try this in pgadmin or any SQL client, is is working as expected
select jsonb_array_elements('[{
"element_id": "a7993f3d-9256-4354-a147-5b9d18d7812b",
"value": true
},
{
"element_id": "ceeb364e-bb88-4f41-9c56-9e5f4d0bc1fb",
"value": null
}]'::JSONB);
jsonb_array_elements
"{""value"":"{""value"": true, ""element_id"": ""a7993f3d-9256-4354-a147-5b9d18d7812b""}"
{ "value": null, "element_id": "ceeb364e-bb88-4f41-9c56-9e5f4d0bc1fb" }

Removing field from json and casting it to jsonb

I want to remove field from array of jsons and then retreive that array as jsonb object. I am able to remove field. Now I want to have single object to return from function.
The way it is now I get
ERROR: more than one row returned by a subquery used as an expression
BEGIN
RETURN (
WITH records (game_numbers) AS (
SELECT *
FROM jsonb_array_elements($1))
SELECT (game_numbers - 'code')::json
FROM records);
END;
$$;
I have data like:
"game_numbers":[
{
"id":1,
"code":"code1"
},
{
"id":2,
"code": "code2"
}
]
I want to receive:
"game_numbers":[
{
"id":1,
},
{
"id":2,
}
]
If you want the function to return a jsonb, then you need to aggregate the elements back to a jsonb.
CREATE FUNCTION fnRemoveCodeElement(jsonb)
RETURNS jsonb AS $$
SELECT json_agg(value - 'code')::jsonb
FROM jsonb_array_elements($1)
$$ LANGUAGE SQL;
SELECT fnRemoveCodeElement('[
{
"id":1,
"code":"code1"
},
{
"id":2,
"code": "code2"
}
]') js
js
[{"id": 1}, {"id": 2}]
db<>fiddle here

Postgres JSONB Editing columns

I was going through the Postgres Jsonb documentation but was unable to find a solution for a small issue I'm having.
I've got a table : MY_TABLE
that has the following columns:
User, Name, Data and Purchased
One thing to note is that "Data" is a jsonb and has multiple fields. One of the fields inside of "Data" is "Attribute" but the values it can hold are not in sync. What I mean is, it could be a string, a list of strings, an empty list, or just an empty string. However, I want to change this.
The only values that I want to allow are a list of strings and an empty list. I want to convert all the empty strings to empty lists and regular strings to a list of strings.
I have tried using json_build_array but have not had any luck
So for example, I'd want my final jsonb to look like :
[{
"Id": 1,
"Attributes": ["Test"]
},
{
"Id": 2,
"Attributes": []
},
{
"Id": 3,
"Attributes": []
}]
when converted from
{
"Id": 1,
"Attributes": "Test"
},
{
"Id": 2,
"Attributes": ""
},
{
"Id": 3,
"Attributes": []
}
]
I only care about the "Attributes" field inside of the Json, not any other fields.
I also want to ensure for some Attributes that have an empty string "Attributes": "", they get mapped to an empty list and not a list with an empty string ([] not [""])
I also want to preserve the empty array values ([]) for the Attributes that already hold an empty array value.
This is what I have so far:
jsonb_set(
mycol,
'{Attributes}',
case when js ->> 'Attributes' <> ''
then jsonb_build_array(js ->> 'Attributes')
else '[]'::jsonb
end
)
However, Attributes: [] is getting mapped to ["[]"]
Use jsonb_array_elements() to iterate over the elements of each 'data' cell and jsonb_agg to regroup the transform values together into an array:
WITH test_data(js) AS (
VALUES ($$ [
{
"Id": 1,
"Attributes": "Test"
},
{
"Id": 2,
"Attributes": ""
},
{
"Id": 3,
"Attributes": []
}
]
$$::JSONB)
)
SELECT transformed_elem
FROM test_data
JOIN LATERAL (
SELECT jsonb_agg(jsonb_set(
elem,
'{Attributes}',
CASE
WHEN elem -> 'Attributes' IN ('""', '[]') THEN '[]'::JSONB
WHEN jsonb_typeof(elem -> 'Attributes') = 'string' THEN jsonb_build_array(elem -> 'Attributes')
ELSE elem -> 'Attributes'
END
)) AS transformed_elem
FROM jsonb_array_elements(test_data.js) AS f(elem) -- iterate over every element in the array
) s
ON TRUE
returns
[{"Id": 1, "Attributes": ["Test"]}, {"Id": 2, "Attributes": []}, {"Id": 3, "Attributes": []}]

Is there a magic function with can extract all select keys/nested keys including array from jsonb

Given a jsonb and set of keys how can I get a new jsonb with required keys.
I've tried extracting key-values and assigned to text[] and then using jsonb_object(text[]). It works well, but the problem comes when a key has a array of jsons.
create table my_jsonb_table
(
data_col jsonb
);
insert into my_jsonb_table (data_col) Values ('{
"schemaVersion": "1",
"Id": "20180601550002",
"Domains": [
{
"UID": "29aa2923",
"quantity": 1,
"item": "book",
"DepartmentDomain": {
"type": "paper",
"departId": "10"
},
"PriceDomain": {
"Price": 79.00,
"taxA": 6.500,
"discount": 0
}
},
{
"UID": "bbaa2923",
"quantity": 2,
"item": "pencil",
"DepartmentDomain": {
"type": "wood",
"departId": "11"
},
"PriceDomain": {
"Price": 7.00,
"taxA": 1.5175,
"discount": 1
}
}
],
"finalPrice": {
"totalTax": 13.50,
"total": 85.0
},
"MetaData": {
"shopId": "1405596346",
"locId": "95014",
"countryId": "USA",
"regId": "255",
"Date": "20180601"
}
}
')
This is what I am trying to achieve :
SELECT some_magic_fun(data_col,'Id,Domains.UID,Domains.DepartmentDomain.departId,finalPrice.total')::jsonb FROM my_jsonb_table;
I am trying to create that magic function which extracts the given keys in a jsonb format, as of now I am able to extract scalar items and put them in text[] and use jsonb_object. but don't know how can I extract all elements of array
expected output :
{
"Id": "20180601550002",
"Domains": [
{
"UID": "29aa2923",
"DepartmentDomain": {
"departId": "10"
}
},
{
"UID": "bbaa2923",
"DepartmentDomain": {
"departId": "11"
}
}
],
"finalPrice": {
"total": 85.0
}
}
I don't know of any magic. You have to rebuild it yourself.
select jsonb_build_object(
-- Straight forward
'Id', data_col->'Id',
'Domains', (
-- Aggregate all the "rows" back together into an array.
select jsonb_agg(
-- Turn each array element into a new object
jsonb_build_object(
'UID', domain->'UID',
'DepartmentDomain', jsonb_build_object(
'departId', domain#>'{DepartmentDomain,departId}'
)
)
)
-- Turn each element of the Domains array into a row
from jsonb_array_elements( data_col->'Domains' ) d(domain)
),
-- Also pretty straightforward
'finalPrice', jsonb_build_object(
'total', data_col#>'{finalPrice,total}'
)
) from my_jsonb_table;
This probably is not a good use of a JSON column. Your data is relational and would better fit traditional relational tables.