json extract multiple level value in sql - sql

This is a follow-up question of Extract all values from json in sql table
What if the json value has multiple levels?
For example,
{
"store-1": {
"Apple": {
"category": "fruit",
"price": 100
},
"Orange": {
"category": "fruit",
"price": 80
}
},
"store-2": {
"Orange": {
"category": "fruit",
"price": 90
},
"Potato": {
"category": "vegetable",
"price": 40
}
}
}
In this case, I want to extract the price for all the items. But I get error when I run the below query.
with my_table(items) as (
values (
'{"store-1":{"Apple":{"category":"fruit","price":100},"Orange":{"category":"fruit","price":80}},
"store-2":{"Orange":{"category":"fruit","price":90},"Potato":{"category":"vegetable","price":40}}}'::json
)
)
select key, (value->value->>'price')::numeric as price
from my_table,
json_each(json_each(items))
I get the below error.
ERROR: function json_each(record) does not exist
LINE 10: json_each(json_each(items))
If I remove one json_each(), it throws
ERROR: operator does not exist: json -> json
LINE 8: select key, (value->value->>'price')::numeric as price

You can use lateral join, something like:
with my_table(items) as (
values (
'{"store-1":{"Apple":{"category":"fruit","price":100},"Orange":{"category":"fruit","price":80}},
"store-2":{"Orange":{"category":"fruit","price":90},"Potato":{"category":"vegetable","price":40}}}'::json
)
)
select outer_key, key, value->>'price' from (
select key as outer_key, value as val from my_table
join lateral json_each(items)
on true
)t
join lateral json_each(val)
on true

Related

Accessing a tag in an array element of a postgres table with json column

My Postgres DB Table BookingDetails has columns - id(bigint), info(jsonb)
My Json structure:
"bookingType": “special”,
“travellers”: [
{
"id": 1,
"nationality": "SomeValue",
}
],
“someTag”: “abc”
}
The travellers here is an array (and in this example, there’s only one element)
I want to:
Select records where nationality=‘CertainValue’
Update records set nationality=‘AnotherValue’ where nationality=‘CertainValue’
I could retrieve the array using:
select CAST (info->'travellers' AS TEXT) from BookingDetails
[
{
"id": 1,
"nationality": "SomeValue",
}
]
I could retrieve the array first element using:
select CAST (info->'travellers'->>0 AS TEXT) from BookingDetails
{
"id": 1,
"nationality": "SomeValue"
}
But how to do a select based on nationality=‘CertainValue’?
try this :
SELECT jsonb_set(info, array['travellers', (a.id - 1) :: text, 'nationality'], to_jsonb('AnotherValue' :: text))
FROM BookingDetails
CROSS JOIN LATERAL jsonb_array_elements(info->'travellers') WITH ORDINALITY AS a(content, id)
WHERE a.content->>'nationality' = 'CertainValue'
see the test result in dbfiddle

Update json data in oracle sql

This is my json data in one of the oracle sql columns "jsoncol" in a table named "jsontable"
{
"Company": [
{
"Info": {
"Address": "123"
},
"Name": "ABC",
"Id": 999
},
{
"Info": {
"Address": "456"
},
"Name": "XYZ",
"Id": 888
}
]
}
I am looking for an UPDATE query to update all the value of "Name" with a new value based on a particular "Id" value.
Thanks in advance
From Oracle 19, you can use JSON_MERGEPATCH:
UPDATE jsontable j
SET jsoncol = JSON_MERGEPATCH(
jsoncol,
(
SELECT JSON_OBJECT(
KEY 'Company'
VALUE JSON_ARRAYAGG(
CASE id
WHEN 999
THEN JSON_MERGEPATCH(
json,
'{"Name":"DEF"}'
)
ELSE json
END
FORMAT JSON
RETURNING CLOB
)
FORMAT JSON
RETURNING CLOB
)
FROM jsontable jt
CROSS APPLY JSON_TABLE(
jt.jsoncol,
'$.Company[*]'
COLUMNS(
json VARCHAR2(4000) FORMAT JSON PATH '$',
id NUMBER PATH '$.Id'
)
)
WHERE jt.ROWID = j.ROWID
)
)
Which, for the sample data:
CREATE TABLE jsontable (
jsoncol CLOB CHECK (jsoncol IS JSON)
);
INSERT INTO jsontable (jsoncol)
VALUES ('{
"Company": [
{
"Info": {
"Address": "123"
},
"Name": "ABC",
"Id": 999
},
{
"Info": {
"Address": "456"
},
"Name": "XYZ",
"Id": 888
}
]
}');
Then after the UPDATE, the table contains:
JSONCOL
{"Company":[{"Info":{"Address":"123"},"Name":"DEF","Id":999},{"Info":{"Address":"456"},"Name":"XYZ","Id":888}]}
db<>fiddle here
You can use REPLACE() within JSON_TABLE() function in order to update the value of the Name(from ABC to DEF) for a specific Id value(999) such as
UPDATE jsontable jt0
SET jsoncol = ( SELECT REPLACE(jsoncol,jt.name,'DEF')
FROM jsontable j,
JSON_TABLE(jsoncol,
'$' COLUMNS(NESTED PATH '$.Company[*]'
COLUMNS(
name VARCHAR2 PATH '$.Name',
id INT PATH '$.Id'
)
)
) jt
WHERE jt.id = 999
AND j.id = jt0.id )
for the DB version prior to 19 provided that the identity values(id) of the table and of the JSON values are unique throughout the table and each individual JSON value, respectively.
Demo

Querying nested Json object in postgres

I have a jsonb column in my table and data is in this format:
[
{
"id": 1,
"DATA": {
"a": "XXX",
"key": "value1"
}
},
{
"id": 2,
"DATA": {
"a": "XXX",
"key": "value2"
}
}
]
I would like to get the count of rows in which key = value1. I tried some queries like:
select count(t.id)
from my_table t,
jsonb_array_elements(summary->'DATA') elem
where elem->>'key' = 'value1';
It returned 0 rows, though there are rows in db with that key value pair. Thanks in advance,
Use jsonb_array_elements() for the column summary as it is in the form of json array.
select count(distinct t.id)
from my_table t
cross join jsonb_array_elements(summary) elem
where elem->'DATA'->>'key' = 'value1';
Alternatively, you can get rid of the function using #> operator:
select count(t.id)
from my_table t
where summary #> '[{"DATA":{"key":"value1"}}]'
The second solution should be faster.
Db<>fiddle.

Hasura "Running Total" Computed Column

I'm looking to create a computed column in Hasura which returns a set of another table and includes a running total column of the set. For example:
table_a
id product_id
----------- ----------
1 "1"
2 "2"
2 "3"
table_b
id product_id qty created_at
----------- ---------- --- ----------
1 "1" 6 01/01/20
2 "2" 4 01/02/20
3 "3" 2 01/02/20
4 "3" 2 01/02/20
5 "1" 4 01/03/20
6 "2" 6 01/03/20
Desired GQL Response:
{
"data": {
"table_a": [
{
"id": 1,
"product_id": "1",
"computed_table_b_rows": [
{
id: 1,
product_id: "1",
qty: 6,
created_at: 01/01/20,
running_total: 6,
},
{
id: 5,
product_id: "1",
qty: 4,
created_at: 01/03/20,
running_total: 10,
},
]
}
]
}
}
Here's what I have so far which does not work:
CREATE FUNCTION filter_table_b(table_a_row table_a)
RETURNS SETOF table_b AS $$
SELECT *,
SUM (qty) OVER (PARTITION BY product_id ORDER BY created_at) as running_total
FROM table_b
WHERE product_id = table_a_row.product_id
$$ LANGUAGE sql STABLE;
It seems like SETOF is supposed to be a subset of the table, and cannot have new columns. E.g. I tried:
CREATE FUNCTION getfoo(int) RETURNS SETOF table_a AS $$
SELECT *, 'asdf' as extra FROM table_a WHERE id = $1;
$$ LANGUAGE SQL;
resulting in the Hasura-reported error:
SQL Execution Failed
postgres-error: return type mismatch in function declared to return table_a
{
"path": "$.args[0].args",
"error": "query execution failed",
"internal": {
"arguments": [],
"error": {
"status_code": "42P13",
"exec_status": "FatalError",
"message": "return type mismatch in function declared to return table_a",
"description": "Final statement returns too many columns.",
"hint": ""
},
"prepared": false,
"statement": "CREATE FUNCTION getfoo(int) RETURNS SETOF table_a AS $$\n SELECT *, 'asdf' as extra FROM table_a WHERE id = $1;\n$$ LANGUAGE SQL;"
},
"code": "postgres-error"
}
There are a few other options, however:
Add the "running total" column to table_b itself (by changing the function definition. Detailed below.
If this is not performant enough, another option is to create a view over table_b that has the running_total column using sum and partition. And then create a relationship from table_a to table_b_view.
Create a trigger on table_b, that when a new row is added it computes the running_total column and stores it, as that data seems to be static as it's based on historical data.
Option 1 is detailed below as it most closely resembles original implementation (use of a function):
CREATE OR REPLACE FUNCTION public.table_b_running_total(table_b_row table_b)
RETURNS bigint
LANGUAGE sql
STABLE
AS $function$
SELECT SUM (qty)
FROM table_b
WHERE product_id = table_b_row.product_id
AND created_at <= table_b_row.created_at
LIMIT 1
$function$
With that, I was able to get the desired result -- tho the desired graphql response does not exactly match.
query:
query MyQuery {
table_a(where:{ id: { _eq: 1 }}) {
id
product_id
table_bs {
id
product_id
qty
created_at
table_b_running_total
}
}
}
response:
{
"data": {
"table_a": [
{
"id": 1,
"product_id": "1",
"table_bs": [
{
"id": 1,
"product_id": "1",
"qty": 6,
"created_at": "2020-01-01T00:00:00+00:00",
"table_b_running_total": 6
},
{
"id": 5,
"product_id": "1",
"qty": 4,
"created_at": "2020-01-03T00:00:00+00:00",
"table_b_running_total": 10
}
]
}
]
}
}

How to avoid InternalError on joins with subselects

I am getting an internalError when running a query that joins two subselects. The query looks like this:
SELECT data_table.title AS name
FROM
(
SELECT id, title
FROM
[citrius-altrius-fortius:analyze.topic_735_1354233600], [citrius-altrius-fortius:analyze.topic_735_1354320000]
) AS data_table
INNER JOIN
(
SELECT id FROM
[citrius-altrius-fortius:analyze.topic_735_1354233600], [citrius-altrius-fortius:analyze.topic_735_1354320000]
WHERE
UPPER(title) CONTAINS ("BUDGET")
) AS filter_table
ON data_table.id == filter_table.id
this is the reply I get:
{
"status": {
"state": "DONE",
"errors": [
{
"reason": "internalError",
"message": "Unexpected. Please try again."
}
],
"errorResult": {
"reason": "internalError",
"message": "Unexpected. Please try again."
}
},
"kind": "bigquery#job",
"statistics": {
"endTime": "1354849344685",
"startTime": "1354849344148"
},
"jobReference": {
"projectId": "citrius-altrius-fortius",
"jobId": "job_e870b1fe67b94897a157dfa4f60b9725"
},
"etag": "\"OLvSfkwPDZ7M36YVEFvi-PBaFQM/SnvQhZKTutJxTY1bJLqqdphkA00\"",
"configuration": {
"query": {
"createDisposition": "CREATE_IF_NEEDED",
"query": "SELECT data_table.title AS name\nFROM\n ( \n SELECT id, title\n FROM\n [citrius-altrius-fortius:analyze.topic_735_1354233600], [citrius-altrius-fortius:analyze.topic_735_1354320000]\n ) AS data_table\n INNER JOIN\n ( \n SELECT id FROM\n [citrius-altrius-fortius:analyze.topic_735_1354233600], [citrius-altrius-fortius:analyze.topic_735_1354320000]\n WHERE\n UPPER(title) CONTAINS (\"BUDGET\") LIMIT 1000\n ) AS filter_table\nON data_table.id == filter_table.id\n;",
"destinationTable": {
"projectId": "citrius-altrius-fortius",
"tableId": "anonba898655_04dc_4d8d_8c29_673a36ba4c9f",
"datasetId": "_6fff29df40f86299d525686858be44df27c8dfd0"
}
}
},
"id": "citrius-altrius-fortius:job_e870b1fe67b94897a157dfa4f60b9725",
"selfLink": "https://www.googleapis.com/bigquery/v2/projects/citrius-altrius-fortius/jobs/job_e870b1fe67b94897a157dfa4f60b9725"
}
title and id are plain string columns (non-repeating). Both tables are fairly small in size (adding limit 1000 on the right side of the join does not change anything). When however, I alter the query to use just one table:
SELECT data_table.title AS name
FROM
(
SELECT id, title
FROM
[citrius-altrius-fortius:analyze.topic_735_1354233600]
) AS data_table
INNER JOIN
(
SELECT id FROM
[citrius-altrius-fortius:analyze.topic_735_1354233600]
WHERE
UPPER(title) CONTAINS ("BUDGET") LIMIT 1000
) AS filter_table
ON data_table.id == filter_table.id
it works every time.
What would be the proper way of joining two subselects where they (subselects) are selecting from multiple tables?
You're hitting a known bug doing a table union in a join:
BigQuery - UNION on left side of JOIN == Unexpected. Please try again
We're actively working on a fix.