Expand nested JSONB data in SELECT query result - sql

Assuming I have a table with JSONB data:
create table x (
id integer primary key generated always as identity,
name text,
data jsonb
);
Assuming data can have nested data, I would like to display all data inside data to have this kind of result:
id name data.a data.b.0 data.b.1 data.c
1 test 1 foo bar baz
2 test2 789 pim pam boom
Is there a way to do this without specifying all the JSONB properties names?

JSONB_TO_RECORDSET() function might be used within such a Select statement
SELECT a AS "data.a",
(b::JSONB) ->> 0 AS "data.b.0", (b::JSONB) ->> 1 AS "data.b.1",
c AS "data.c"
FROM x,
JSONB_TO_RECORDSET(data) AS j(a INT, b TEXT, c TEXT)
ORDER BY id
Presuming you have such JSONB values in the data column
[ { "a": 1, "b": ["foo","bar"], "c": "baz" }]
[ { "a": 789, "b": ["pim","pam"], "c": "boom" }]
Demo

Related

PostgresSQL: Select rows where a JSONB field contains all items in an array

Given the database table below where tags are arrays stored as JSONB, how can I write an SQL query to only select ids that contain all tags value's in an array?
e.g only records 1 and 2 would be returned for the array {"London", "Sydney"}
id | tags
----+---------------------------------------------------
1 | [{"value": "Sydney"..}, {"value": "London"..}, {"value": "Munich"..}]
2 | [{"value": "Sydney"..}, {"value": "London"..}]
3 | [{"value": "London"..}]
4 | [{"value": "Sydney"..}]
I managed to construct this query however it does not return an absolute match for ALL the items in the array which is what I'm after..
SELECT *
FROM mytable
WHERE EXISTS (
SELECT TRUE
FROM jsonb_array_elements(tags) tag
WHERE tag->>'value' IN ('London', 'Sydney')
)
You can use the contains operator #> with a JSON array:
select *
from the_table
where tags #> '[{"value": "London"}, {"value": "Sydney"}]'
Or you can use a JSON path expression
select *
from the_table
where jsonb_path_query_array(tags, '$[*].value') ?& array['London', 'Sydney']

Is there a way to search for an element inside a JSON array using sql in snowflake?

I have a column in a table which contains, for each row, a JSONarray. I need to extract certain the same elements from it for each row, however, as it is an array, the order of the elements inside the array is not always the same and I can't call these elements by their names.Is there a way for me to do a for loop or something similar that goes through every index of the array and when it doesn't return null it breaks?
An extension to Lukasz's great answer:
With a CTE with a couple of rows of "id, json" we can see how FLATTEN pulls it apart:
WITH fake_data(id, json) as (
SELECT column1, parse_json(column2) FROM VALUES
(1, '[1,2,3]'),
(2, '{"4":4, "5":5}')
)
SELECT t.*
,f.*
FROM fake_data AS t
,LATERAL FLATTEN(INPUT => t.json) f
ID
JSON
SEQ
KEY
PATH
INDEX
VALUE
THIS
1
[ 1, 2, 3 ]
1
[0]
0
1
[ 1, 2, 3 ]
1
[ 1, 2, 3 ]
1
[1]
1
2
[ 1, 2, 3 ]
1
[ 1, 2, 3 ]
1
[2]
2
3
[ 1, 2, 3 ]
2
{ "4": 4, "5": 5 }
2
4
['4']
4
{ "4": 4, "5": 5 }
2
{ "4": 4, "5": 5 }
2
5
['5']
5
{ "4": 4, "5": 5 }
The Flatten gives seq, key, path, index, value and this
Seq : is the row of the input, which is super useful if you are pulling rows apart and want to merge them back together, but not mix up different rows.
Key : is the name of the property if the thing being FLATTEN'ed was an object, which is the case for the second row.
Path : is how that value could be accessed. aka t.json[2] would with you 3
Index : is the step into the object if it's an array
Value: is the value
This: is the thing that getting looped, useful for get things like the next one, etc.
There is no need to know the size of array:
CREATE OR REPLACE TABLE tab_name
AS
SELECT 1 AS id, PARSE_JSON('[1,2,3]') AS col_array
UNION ALL
SELECT 2 AS id, PARSE_JSON('[1]') AS col_array;
Query:
SELECT t.id
,f.INDEX
,f.VALUE
FROM tab_name t
, LATERAL FLATTEN(INPUT => t.col_array) f
-- WHERE f.VALUE::INT = 1;
Output:
Lateral flatten can help extract the fields of a JSON object and is a very good alternative to extracting them one by one using the respective names. However, sometimes the JSON object can be nested and normally extracting those nested objects requires knowing their names.
Here is an article that might help you to DYNAMICALLY EXTRACT THE FIELDS OF A MULTI-LEVEL JSON OBJECT USING LATERAL FLATTEN

PostgreSQL get json as record

I would like to be able to get a json object as a record.
SELECT select row_number() OVER () AS gid, feature->'properties' FROM dataset
The output of this query looks like this:
gid
?column? json
1
{"a": "1", "b":"2", "c": "3"}
2
{"a": "3", "b":"2", "c": "1"}
3
{"a": "1"}
The desired result :
gid
a
b
c
1
1
2
3
2
3
2
1
3
1
null
null
I can't use json_to_record because i don't know the number of fields. However, all my fields are text.
There is no generic way to do that, because the number, type and name of columns have to be known at query parse time. So you would have to do:
SELECT row_number() OVER () AS gid,
CAST(feature #>> '{properties,a}' AS integer) AS a,
CAST(feature #>> '{properties,b}' AS integer) AS b,
CAST(feature #>> '{properties,c}' AS integer) AS c
FROM dataset;
Essentially, you have to know the columns ahead of time and hard code them in the query.

Postgresql add item in array except if null

I try to make a PostgreSQL array by using values from JSONB field.
When all data are in JSONB, result is fine. This query :
SELECT array [
(SELECT ('{"tech_id": 4, "admin_id": 5}'::jsonb->>'admin_id')::int),
(SELECT ('{"tech_id": 4, "admin_id": 5}'::jsonb->>'tech_id')::int)
];
returns me the right result because admin_id and tech_id are in JSONB field :
-[ RECORD 1 ]
array | {5,4}
But, if JSONB contains only one value, the array contains a NULL value.
This query :
SELECT array [
(SELECT ('{"tech_id": 4}'::jsonb->>'admin_id')::int),
(SELECT ('{"tech_id": 4}'::jsonb->>'tech_id')::int)
];
Gives me this result :
-[ RECORD 1 ]---
array | {NULL,4}
But I want an array like {4}, so without NULL value.
Do you know a way to avoid adding NULL value in this case ?
Finnaly, I found solution by wrapping the array to check if values are NULL.
I use the following code :
select array_agg(id) FROM unnest(array [
(SELECT ('{"tech_id": 4}'::jsonb->>'admin_id')::int),
(SELECT ('{"tech_id": 4}'::jsonb->>'tech_id')::int)
]) as id where id IS NOT NULL;
PostgreSQL returns the expected result :
-[ RECORD 1 ]--
array_agg | {4}

Postgres Check for Empty JSONB Fields

I want to be able to ignore or prevent an INSERT from occurring if there is a JSON object with keys but no values from within a Postgres function.Here is a small, contrived example:
DROP TABLE IF EXISTS mytable;
create table mytable(
a text,
b text
);
CREATE OR REPLACE FUNCTION insert_to_table(
somedata JSONB
)
RETURNS VOID AS $$
BEGIN
insert into mytable (a, b) select a,b from jsonb_populate_recordset(null::mytable, $1::jsonb);
END;
$$ LANGUAGE plpgsql;
select insert_to_table('[{"a":1,"b":2},{"a":3,"b":4}, {"a":null, "b": null}, {"a": null, "b": "some data"}]');
This will insert 4 records, with the first row being 1,2 and the next row being 3,4. The third row is "", "", and the forth is "", some data.
In this scenario, rows 1,2, and 4 are valid. I want to ignore 3 and prevent it from being inserted.
I do not want a blank row, and my data/table will be much larger than what is listed (roughly 20 fields in the table, and 20 key/value pairs in the JSON).
Most likely I will need to loop over the array and pick out JSON object where ALL the keys are null and not just 1 or 2.
How can I do that?
In Postgres you can refer to a complete row using the name of the table (alias) in the query and compare that to NULL. A record is considered NULL if all columns are null. So you can do:
create or replace function insert_to_table(somedata jsonb)
returns void as $$
begin
insert into mytable (a, b)
select a, b
from jsonb_populate_recordset(null::mytable, somedata) as t
where not (t is null);
end;
$$ language plpgsql;
Note that where t is not null is something different then where not (t is null). This works regardless of the number of columns or their data types.
To visualize the logic. The following:
select a,b,
not (t is null) as "not (t is null)",
t is null as "t is null",
t is not null as "t is not null"
from jsonb_populate_recordset(null::mytable,
'[{"a":1,"b":2},
{"a":3,"b":4},
{"a":null, "b": null},
{"a": null, "b": "some data"}]'::jsonb) as t(a,b)
returns:
a | b | not (t is null) | t is null | t is not null
--+-----------+-----------------+-----------+--------------
1 | 2 | true | false | true
3 | 4 | true | false | true
| | false | true | false
| some data | true | false | false
Unrelated:
The cast $1::jsonb is useless as you have declared the parameter of that type already.