postgres select from json field as column for each key - json

I have Items (id, data (json)) table. Data column structure is this:
"somethingNotImportant":{
"someName":"Its a name",
"someProduct":"its a product"
},
"anotherNotImportant":{
"installments":null,
"baseRate":"30",
"grossComm":"20",
"totalMileage":null
},
"fleetdetails":{
"4b4bd441-a8eb-4754-9384-6f97d1ee23e3":{
"vehicleType":"43572386-5908-4e46-bf2b-3948df2e0e72",
"usage":"Class 3",
"carBand":"N\/A",
"coverType":"Third-Party Only",
"vehicleNumber":"1",
"modelRate":"222",
"technicalRate":"333",
"annualMileage":"444",
"adftExcess":"555",
"wsExcess":"777",
"annualBasePremium":null,
"usageRate":null
},
"cc12cc77-9346-4cae-8c27-6afc8b457f9b":{
"vehicleType":"fa999a90-b98f-499a-bef6-55b9a208c2fc",
"usage":"Class 1",
"carBand":"N\/A",
"coverType":"Comprehensive",
"vehicleNumber":"1",
"modelRate":"2",
"technicalRate":"3",
"annualMileage":"4",
"adftExcess":"5",
"wsExcess":"6",
"annualBasePremium":null,
"usageRate":null
}
}
}
I would like to select and get fleetdetails results like this:
item.id
vehicleType
usage
carBand
1
43572386-...
Class 3
N/A
1
fa999a90-...
Class 1
N/A
and so on.
How I can achieve this if I don't know fleetdetails json ids?

Combination of JSONB_EACH() and CROSS JOIN LATERAL will do the things for you try below query:
select
t1.id "id",
t2.value->>'vehicleType' "VEHICLETYPE",
t2.value->>'usage' "usage",
t2.value->>'carBand' "carBand",
t2.value->>'coverType' "coverType",
t2.value->>'vehicleNumber' "vehicleNumber",
t2.value->>'modelRate' "modelRate",
t2.value->>'technicalRate' "technicalRate",
t2.value->>'annualMileage' "annualMileage",
t2.value->>'adftExcess' "adftExcess",
t2.value->>'wsExcess' "wsExcess",
t2.value->>'annualBasePremium' "annualBasePremium",
t2.value->>'usageRate' "usageRate"
from items t1 cross join lateral jsonb_each(data->'fleetdetails') t2
DEMO

You can use jsonb_each():
select i.id,
d.detail ->> 'vehicleType' as vehicle_type,
d.detail ->> 'usage' as usage,
d.detail ->> 'carBand' as car_band
from item i
cross join jsonb_each(i.data -> 'fleetdetails') as d(key, detail);
This assumes that the column data is defined as jsonb (which it should be). If it's just a json column you have to use json_each() instead.

Related

Get nested objects values from JSON in Postgres

So here is my JSON column in my Postgres DB:
{
"objekt_art": {
"86": {
"code": "86",
"bezeichnung_de": "Kino",
"bezeichnung_fr": "Cinéma",
"bezeichnung_it": "Cinema",
"bezeichnung_en": null,
"kurz_bezeichnung_de": "Kino",
"relevant_fuer_berechnung_steuerquote": true
},
"27": {
"code": "27",
"bezeichnung_de": "Kiosk",
"bezeichnung_fr": "Kiosque",
"bezeichnung_it": "Chiosco",
"bezeichnung_en": null,
"kurz_bezeichnung_de": "Kiosk",
"relevant_fuer_berechnung_steuerquote": true
}
}
}
I need to be able to query the bezechnung_de for example where code = 86.
The number of code i can pass from another table.
How can i for example make a query with two columns. One with the number and the second with bezeichnung_de.
Like this:
Code Bez
86 Kino
Sample data structure and sample table for join data: dbfiddle
select
je.value -> 'code' as "Code",
je.value -> 'bezeichnung_de' as "Bez"
from
test t
cross join jsonb_each((data::jsonb ->> 'objekt_art')::jsonb) je
-- In table test_join I insert value 86 for join record
inner join test_join tj on je.key::int = tj.json_id
As you know the code, this is fairly easy:
select t.the_column -> 'objekt_art' -> '86' ->> 'code' as code,
t.the_column -> 'objekt_art' -> '86' ->> 'bezeichnung_de' as bez
from the_table t
where ...
The value '86' can be a parameter. The first expression to select the code isn't really needed though, as you could replace it with the constant value (=parameter) directly.
If the "outer" JSON key isn't the same value as the code value, you could use something like this:
select o.value ->> 'code' as code,
o.value ->> 'bezeichnung_de' as bez
from the_table t
cross join jsonb_each(t.the_column -> 'objekt_art') o(key, value)
where o.key = '86'
and ... other conditions ...
If you are using Postgres 13 or later, this can also be written as a JSON path expression:
select a.item ->> 'code' as code,
a.item ->> 'bezeichnung_de' as bez
from (
select jsonb_path_query_first(t.the_column, '$.objekt_art.* ? (#.code == "86")') as item
from the_table t
where ....
) a
All examples assume that the column is defined with the data jsonb which it should be. If it's not you need to cast it: the_column::jsonb

How to explode several list values JSON within JSON with lateral flatten in Snowflake?

I have a variant table with 24 JSONs like the following (one per row):
{
"context": "marketplace",
"metadata": {
"app_version": "1.0.4 (166)",
},
"params": {
"filter": {
"brands": [],
"categories": [
"f28c7c9f-09ae-4218-821a-bec344998289"
],
"manufacturers": [],
"page": 1,
"product_name": "",
},
"page": "product_list",
"results": 3
},
"user_id": "6443a2db-4526-4fc5-8084-290fc78e5336"
}
I want everything exploded in rows. I have managed to have everything but filter as I want with the code:
SELECT data:event::string,
data:user_id::string,
data:metadata.app_version::string,
data:context::string,
data:params.page::string,
data:params.filter.page::string,
data:params.results::string
FROM ods.stg_tanimis_events
The lists brands, categories and manufacturers can be empty but I would need a null value if that is the case. I would like to have a table with the following columns:
event, user_id, app_version, context, param_page, filter_page, results, manufacturer, brand, category
I have tried with several lateral flatten with no success:
select * FROM table
, lateral flatten (data:params:filter:categories) j2;
select * FROM table
, lateral flatten (data:params:filter.brands) j1
select * FROM table
, lateral flatten (data:params:filter:brands) j1
, lateral flatten (data:params:filter:categories) j2;
select user_id, filter, flat.*
from table
, lateral flatten(parse_json(filter)) flat;
WITH j as (
SELECT *
FROM table
, lateral flatten(data:params:filter))
SELECT *
from j,
lateral flatten (j.value) j2;
Either way, I get 8 rows or 0 rows, or errors. How could I make it? Thanks
In your example data, there is no data in "brands", which means any flatten against that will return nothing unless you add , OUTER => TRUE in your lateral flatten statement. Something more like this:
SELECT table.data:event::string,
table.data:user_id::string,
table.data:metadata.app_version::string,
table.data:context::string,
table.data:params.page::string,
table.data:params.filter.page::string,
table.data:params.results::string,
j1.value::string as brands,
j2.value::string as categories
FROM table
, lateral flatten (data:params:filter:brands, OUTER => TRUE) j1
, lateral flatten (data:params:filter:categories, OUTER => TRUE) j2;
This will act like a LEFT JOIN to the base table and return NULL for any flattened arrays that are empty.

How to select JSON object from JSON array field of mysql by some condition

I have a table with JSON field which contains an array of JSON objects. I need to select objects by some condition.
Create and fill a table:
CREATE TABLE test (
id INT AUTO_INCREMENT PRIMARY KEY,
json_list JSON
);
INSERT INTO test(json_list) VALUES
("{""list"": [{""type"": ""color"", ""value"": ""red""}, {""type"": ""shape"", ""value"": ""oval""}, {""type"": ""color"", ""value"": ""green""}]}"),
("{""list"": [{""type"": ""shape"", ""value"": ""rect""}, {""type"": ""color"", ""value"": ""olive""}]}"),
("{""list"": [{""type"": ""color"", ""value"": ""red""}]}")
;
Now I need to select all objects with type = color from all rows.
I want to see this output:
id extracted_value
1 {"type": "color", "value": "red"}
1 {"type": "color", "value": "green"}
2 {"type": "color", "value": "olive"}
3 {"type": "color", "value": "red"}
It would be good to get this too:
id color
1 red
1 green
2 olive
3 red
I can't change the DB or JSON.
I'm using MySQL 5.7
My current solution
My solution is to cross join the table with some index set and then extract all elements of JSON array.
I don't like it as if possible object count in one array is large it is required to have all indexes till the maximum one. It makes the query slow as it won't stop calculation of JSON value when the end of array is reached.
SELECT
test.id,
JSON_EXTRACT(test.json_list, CONCAT('$.list[', ind.ind, ']')),
ind.ind
FROM
test
CROSS JOIN
(SELECT 0 AS ind UNION ALL SELECT 1 AS ind UNION ALL SELECT 2 AS ind) ind
WHERE
JSON_LENGTH(json_list, "$.list") > ind.ind
AND JSON_EXTRACT(json_list, CONCAT('$.list[', ind.ind, '].type')) = "color";
It is easy to get only values by changing JSON_EXTRACT path. But is it there a better way?
Edits
Added a check for json_list.list length. This filtered out 67% of derived table rows in this case.
SELECT JSON_EXTRACT(json_list, '$.list[*]')
FROM `test`
where JSON_CONTAINS(json_list, '{"type":"color"}', '$.list')
So current best solution is mine:
SELECT
test.id,
JSON_EXTRACT(test.json_list, CONCAT('$.list[', ind.ind, ']')),
ind.ind
FROM
test
CROSS JOIN
(SELECT 0 AS ind UNION ALL SELECT 1 AS ind UNION ALL SELECT 2 AS ind) ind
WHERE
JSON_LENGTH(json_list, "$.list") > ind.ind
AND JSON_EXTRACT(json_list, CONCAT('$.list[', ind.ind, '].type')) = "color";

How to select query from json in Postgres

I have JSON data in field hotel_data like this:
{
"title":"foo",
"description":[
{
"locale":"pt",
"content":"pt text"
},
{
"locale":"fr",
"content":"fr text"
}
]
}
I would like to select description only fr description. It is possible using Postgres and how?
I was trying use ->> but it is not working...
SELECT
hotel_data->'description'->>'locale' = 'fr' AS description
FROM hotel LIMIT 1;
Note:
I don't want to use SELECT *...
Excepted output: {description: "fr text"}
You can use a lateral join and json_to_recordset to expand the json array as a set of records. Then, you can filter on column locale in the generated records, and finally recompose a new json object with your expected result:
select json_build_object('description', d.content) hotel_data_descr_fr
from
mytable,
json_to_recordset(hotel_data->'description') as d("locale" text, "content" text)
where d.locale = 'fr'
Demo on DB Fiddle:
with mytable as (
select '{
"title":"foo",
"description":[
{
"locale":"pt",
"content":"pt text"
},
{
"locale":"fr",
"content":"fr text"
}
]
}'::json hotel_data
)
select json_build_object('description', d.content) hotel_data_descr_fr
from
mytable,
json_to_recordset(hotel_data->'description') as d("locale" text, "content" text)
where d.locale = 'fr'
| hotel_data_descr_fr |
| :------------------------- |
| {"description": "fr text"} |
The filtering can be done using the #> operator which can use a GIN index on the hotel_data column. This is typically faster than expanding the array.
select ...
from hotel
where hotel_data #> '{"description": [{"locale":"fr"}] }';
This can also be extended to include more properties:
select ...
from hotel
where hotel_data #> '{"description": [{"locale":"fr", "headline": "nice view'}] }';
But you can only express equality conditions on the key/value pairs with that. Using LIKE is not possible. You will have to expand the array if you want to do that and apply the condition in the WHERE clause - see GMB's answer.
To extract that description, I would use a scalar sub-query:
select (select jsonb_build_object('description', t.descr ->> 'content')
from jsonb_array_elements(h.hotel_data -> 'description') as t(descr)
where t.descr ->> 'locale' = 'fr'
limit 1)
from hotel h
where h.hotel_data #> '{"description": [{"locale":"fr"}] }';
That way you don't need to expand the array for filtering which I expect to be faster if only a few hotels qualify for that condition. But it has the drawback that you need to repeat the condition on the locale in the sub-select.
The limit 1 is only a safety net in case you have more than one french description. If you never have that, it doesn't hurt either
With Postgres 12 this is easier:
select jsonb_build_object(
'description',
jsonb_path_query_first(hotel_data, '$.description ? (#.locale == "fr")') -> 'content'
)
from hotel
where hotel_data #> '{"description": [{"locale":"fr"}] }'
All of the above assumes hotel_data is a jsonb column, if it's not (which it should be) you need to cast it: hotel_data::jsonb

List arbitrary number of elements from a json field (postgres)

I have a json field in postgres db that contains data like
[{
"value": "+1 968 730 5680",
"label": "mobile",
"primary": true
},{
"value": "+1 909 169 4444",
"label": "mobile",
"primary": false
}]
I want to query this field to produce
+1 968 730 5680; +1 909 169 4444
Given that the number of elemets can vary.
I had enough brains to get the first element with
json_field -> 0 ->> 'value'
but am totally stuck at producing a string with all elements.
Please help.
UPDATE
Following advice below, I get an error with statement
select d ->> 'value' as val
from analyst.person
cross join jsonb_array_elements(phone) as x(d)
ERROR: column "phone" does not exist
LINE 3: cross join jsonb_array_elements(phone) as x(d)
another variant I tried
select d ->> 'value' as val
from analyst.person as person
cross join jsonb_array_elements(person.phone) as x(d)
gives error
ERROR: invalid reference to FROM-clause entry for table "person"
LINE 3: cross join jsonb_array_elements(person.phone) as x(d)
^
HINT: There is an entry for table "person", but it cannot be referenced from this part of the query.
You need to unnest your json array, extract the value and aggregate it back into a string:
select string_agg(val, '; ')
from (
select d ->> 'value' as val
from the_table
cross join lateral jsonb_array_elements(the_json_colum) as x(d)
) t
Online example: https://rextester.com/VFSRY99127