Have a table with json column, this json is quite big, so I want to filter/select only speicific nested keys.
Json Example:
{
"title":{
"nested_1":"This is nested key 1",
"nested_2":"This is nested key 2",
"nested_3":"This is nested key 3",
"nested_4":"This is nested key 4",
"nested_5":"This is nested key 5"
},
"description":{
"nested_1":"This is nested key 1",
"nested_2":"This is nested key 2",
"nested_3":"This is nested key 3",
"nested_4":"This is nested key 4",
"nested_5":"This is nested key 5"
},
"meta":{
"nested_1":"This is nested key 1",
"nested_2":"This is nested key 2",
"nested_3":"This is nested key 3",
"nested_4":"This is nested key 4",
"nested_5":"This is nested key 5"
}
}
In example I want to select only nested_3 and nested_5 (But keep the json structure):
{
"title":{
"nested_3":"This is nested key 3",
"nested_5":"This is nested key 5"
},
"description":{
"nested_3":"This is nested key 3",
"nested_5":"This is nested key 5"
},
"meta":{
"nested_3":"This is nested key 3",
"nested_5":"This is nested key 5"
}
}
What I tried so far:
select
id,
(select json_object_agg(key, val) from (
values
('nested_3', (select json_object_agg(t.token, t.content->'nested_3') from json_each(json_col) as t(token, content))),
('nested_5', (select json_object_agg(t.token, t.content->'nested_5') from json_each(json_col) as t(token, content)))
) as foo(key, val)) as json_col
from my_table
This is working but gives the opposite result (logical, based on above query):
{
"nested_3": {
"title": "This is nested key 3",
"description": "This is nested key 3",
"meta": "This is nested key 3"
},
"nested_5": {
"title": "This is nested key 5",
"description": "This is nested key 5",
"meta": "This is nested key 5"
}
}
I've also tried to nest:
(select json_object_agg(t.token, json_object_agg(t.content->'nested_3', t.languages->'nested_5')) from json_each(json_col) as t(token, content))
But this gives me an error: aggregate function calls cannot be nested
There is a way to select only specific nested keys but preserve the json structure?
Postgres version: 12
With a declared instead of hard-coded list of keys, in ('nested_3', 'nested_5').
select id, (
select jsonb_object_agg(key, (
select jsonb_object_agg(key, value)
from jsonb_each(t.value)
where key in ('nested_3', 'nested_5')
)) json_filtered
from jsonb_each(jsonfield) t
) from the_table;
SQL Fiddle
Found a way to do this: by using json_build_object
So what happens in json_each on each loop we create new object with only needed keys/value, and after push it to our output using json_object_agg if the key already exists will push to previous object otherwise will create new key.
select
id,
(
select
json_object_agg(t.token, json_build_object('nested_3', t.content->'nested_3', 'nested_5', t.content->'nested_5'))
from json_each(json_col) as t(token, content)
) as json_col
from my_table
If someone has better solution or maybe better description explanation, please post your answer.
Related
I have a Nested JSON like
{
"Col1" : Val1,
"Col2" : [
"NestedCol1" : "Nested Value 1",
"NestedCol2" : "Nested Value 2",
"NestedCol3" : "Nested Value 3",
]
"Col3" : [
"NestedCol1" : "Nested Value 1",
"NestedCol2" : "Nested Value 2",
"NestedCol3" : "Nested Value 3",
]
}
Both Nested Columns will have same column names,
i want to select based on the parameter either NestedCol2 values or NestedCol3 Values.
want to create more like genric pl/sql fucntions where user can pass the column names , as in future more nested columns with same structure can exists too.
I have a working query that flattens a nested JSON object into rows of data. What I would like to do, however, is preserve the original order of one array of objects which is nested several layers in.
I have tried to use ROW_NUMBER with an ORDER BY NULL and an ORDER BY (SELECT NULL) and neither seem to preserve the order.
Any ideas on how to accomplish that? Examples below. I chose to mask the real data, but the important parts of the structure are the same. The data in JSON format comes through with no rank-identifying information, but I used numbers as examples here to show the strange results.
Original structure (masked):
{
"topNode: {
"childNode": {
"list": [
{
"title": "example title 1",
},
{
"title": "example title 2",
},
{
"title": "example title 3",
},
{
"title": "example title 4",
},
{
"title": "example title 5",
}
]
}
}
}
Example query (masked):
SELECT
A.VALUE:"title"::VARCHAR AS "TITLE",
ROW_NUMBER() OVER(ORDER BY NULL) AS RANK
FROM
DB.SCHEMA.TABLE as A,
lateral flatten(input=>A.JSON:topNode.childNode.list) "list_flatten"
Example output:
TITLE RANK
"example title 3" 1
"example title 5" 2
"example title 2" 3
"example title 1" 4
"example title 4" 5
It is possible with INDEX, which returns index of element in array:
SELECT A.VALUE:"title"::VARCHAR AS "TITLE",
"list_flatten".index AS "RANK"
FROM DB.SCHEMA.TABLE as A,
lateral flatten(input=>A.JSON:topNode.childNode.list) "list_flatten"
I'm using following schema for the JSONB column of my table (named fields). There are several of these field entries.
{
"FIELD_NAME": {
"value" : "FIELD_VALUE",
"meta": {
"indexable": true
}
}
}
I need to find all the fields that contain this object
"meta": {
"indexable": true
}
Here is a naive attempt at having json_object_keys in where clause, which doesn't work, but illustrates what I'm trying to do.
with entry(fields) as (values('{
"login": {
"value": "fred",
"meta": {
"indexable": true
}
},
"password_hash": {
"value": "88a3d1c7463d428f0c44fb22e2d9dc06732d1a4517abb57e2b8f734ce4ef2010",
"meta": {
"indexable": false
}
}
}'::jsonb))
select * from entry where fields->jsonb_object_keys(fields) #> '{"meta": {"indexable": "true"}}'::jsonb;
How can I query on the value of nested object? Can I somehow join the result of json_object_keys with the table iself?
demo:db<>fiddle
First way: using jsonb_each()
SELECT
jsonb_build_object(elem.key, elem.value) -- 3
FROM
entry,
jsonb_each(fields) as elem -- 1
WHERE
elem.value #> '{"meta": {"indexable": true}}' -- 2
Expand all subobjects into one row per "field". This creates 2 columns: the key and the value (in your case login and {"meta": {"indexable": true}, "value": "fred"})
Filter the records by checking the value column for containing the meta object using the #> as you already mentioned
Recreate the JSON object (combining the key/value columns)
Second way: Using jsonb_object_keys()
SELECT
jsonb_build_object(keys, fields -> keys) -- 3
FROM
entry,
jsonb_object_keys(fields) as keys -- 1
WHERE
fields -> keys #> '{"meta": {"indexable": true}}' -- 2
Finding all keys as you did
and 3. are very similar to the first way
I'm trying to merge some nested JSON arrays without looking at the id. Currently I'm getting this when I make a GET request to /surveyresponses:
{
"surveys": [
{
"id": 1,
"name": "survey 1",
"isGuest": true,
"house_id": 1
},
{
"id": 2,
"name": "survey 2",
"isGuest": false,
"house_id": 1
},
{
"id": 3,
"name": "survey 3",
"isGuest": true,
"house_id": 2
}
],
"responses": [
{
"question": "what is this anyways?",
"answer": "test 1"
},
{
"question": "why?",
"answer": "test 2"
},
{
"question": "testy?",
"answer": "test 3"
}
]
}
But I would like to get it where each survey has its own question and answers so something like this:
{
"surveys": [
{
"id": 1,
"name": "survey 1",
"isGuest": true,
"house_id": 1
"question": "what is this anyways?",
"answer": "test 1"
}
]
}
Because I'm not going to a specific id I'm not sure how to make the relationship work. This is the current query I have that's producing those results.
export function getSurveyResponse(id: number): QueryBuilder {
return db('surveys')
.join('questions', 'questions.survey_id', '=', 'surveys.id')
.join('questionAnswers', 'questionAnswers.question_id', '=', 'questions.id')
.select('surveys.name', 'questions.question', 'questions.question', 'questionAnswers.answer')
.where({ survey_id: id, question_id: id })
}
Assuming jsonb in current Postgres 10 or 11, this query does the job:
SELECT t.data, to_jsonb(s) AS new_data
FROM t
LEFT JOIN LATERAL (
SELECT jsonb_agg(s || r) AS surveys
FROM (
SELECT jsonb_array_elements(t.data->'surveys') s
, jsonb_array_elements(t.data->'responses') r
) sub
) s ON true;
db<>fiddle here
I unnest both nested JSON arrays in parallel to get the desired behavior of "zipping" both directly. The number of elements in both nested JSON arrays has to match or you need to do more (else you lose data).
This builds on implementation details of how Postgres deals with multiple set-returning functions in a SELECT list to make it short and fast. See:
What is the expected behaviour for multiple set-returning functions in select clause?
One could be more explicit with a ROWS FROM expression, which works properly since Postgres 9.4:
SELECT t.data
, to_jsonb(s) AS new_data
FROM tbl t
LEFT JOIN LATERAL (
SELECT jsonb_agg(s || r) AS surveys
FROM ROWS FROM (jsonb_array_elements(t.data->'surveys')
, jsonb_array_elements(t.data->'responses')) sub(s,r)
) s ON true;
The manual about combining multiple table functions.
Or you could use WITH ORDINALITY to get original order of elements and combine as you wish:
PostgreSQL unnest() with element number
My JSON data looks like this:
[{
"id": 1,
"payload": {
"location": "NY",
"details": [{
"name": "cafe",
"cuisine": "mexican"
},
{
"name": "foody",
"cuisine": "italian"
}
]
}
}, {
"id": 2,
"payload": {
"location": "NY",
"details": [{
"name": "mbar",
"cuisine": "mexican"
},
{
"name": "fdy",
"cuisine": "italian"
}
]
}
}]
given a text "foo" I want to return all the tuples that have this substring. But I cannot figure out how to write the query for the same.
I followed this related answer but cannot figure out how to do LIKE.
This is what I have working right now:
SELECT r.res->>'name' AS feature_name, d.details::text
FROM restaurants r
, LATERAL (SELECT ARRAY (
SELECT * FROM json_populate_recordset(null::foo, r.res#>'{payload,
details}')
)
) AS d(details)
WHERE d.details #> '{cafe}';
Instead of passing the whole text of cafe I want to pass ca and get the results that match that text.
Your solution can be simplified some more:
SELECT r.res->>'name' AS feature_name, d.name AS detail_name
FROM restaurants r
, jsonb_populate_recordset(null::foo, r.res #> '{payload, details}') d
WHERE d.name LIKE '%oh%';
Or simpler, yet, with jsonb_array_elements() since you don't actually need the row type (foo) at all in this example:
SELECT r.res->>'name' AS feature_name, d->>'name' AS detail_name
FROM restaurants r
, jsonb_array_elements(r.res #> '{payload, details}') d
WHERE d->>'name' LIKE '%oh%';
db<>fiddle here
But that's not what you asked exactly:
I want to return all the tuples that have this substring.
You are returning all JSON array elements (0-n per base table row), where one particular key ('{payload,details,*,name}') matches (case-sensitively).
And your original question had a nested JSON array on top of this. You removed the outer array for this solution - I did the same.
Depending on your actual requirements the new text search capability of Postgres 10 might be useful.
I ended up doing this(inspired by this answer - jsonb query with nested objects in an array)
SELECT r.res->>'name' AS feature_name, d.details::text
FROM restaurants r
, LATERAL (
SELECT * FROM json_populate_recordset(null::foo, r.res#>'{payload, details}')
) AS d(details)
WHERE d.details LIKE '%oh%';
Fiddle here - http://sqlfiddle.com/#!15/f2027/5