Only extract json if field not null - sql

I want to extract a key value from a (nullable) JSONB field. If the field is NULL, I want the record still present in my result set, but with a null field.
customer table:
id, name, phone_num, address
1, "john", 983, [ {"street":"23, johnson ave", "city":"Los Angeles", "state":"California", "current":true}, {"street":"12, marigold drive", "city":"Davis", "state":"California", "current":false}]
2, "jane", 9389, null
3, "sally", 352, [ "street":"90, park ave", "city":"Los Angeles", "state":"California", "current":true} ]
Current PostgreSQL query:
select id, name, phone_num, items.city
from customer,
jsonb_to_recordset(customer) as items(city str, current bool)
where items.current=true
It returns:
id, name, phone_num, city
1, "john", 983, "Los Angeles"
3, "sally", 352, "Los Angeles"
Required Output:
id, name, phone_num, city
1, "john", 983, "Los Angeles"
2, "jane", 9389, null
3, "sally", 352, "Los Angeles"
How do I achieve the above output?

Use a left join lateral instead of an implicit lateral join:
select c.id, c.name, c.phone_num, i.city
from customer c
left join lateral jsonb_to_recordset(c.address) as i(city str, current bool)
on i.current=true

Related

Concatenate column by condition (oracle)

source_id
source_groupid
source_nm
category_id
level_id
12345
34
ABC
7
2
67549
GI
5
1
24751
BL
6
Result
{"id": 12345, "groupid": 34, "name": ABC, "category_id": 7, "level_id": 2}
{"id": 67549, "groupid": , "name": GI, "category_id": 5, "level_id": 1}
SELECT CONCAT ('{','"id": ', source_id,', ', '"groupid": ', source_groupid,', ','"name": ',source_nm,', ','"category_id": ',category_id,', ', '"level_id": ', level_id, '}') as full_info
FROM table
I need to do column concatenation according to the following pattern. If, for example, there is no entry in the group_id or category_id, then how to write the code so that the template changes and looks for lines 2 and 3 as follows.
{"id": 12345, "groupid": 34, "name": ABC, "category_id": 7, "level_id": 2}
{"id": 67549, "name": GI, "category_id": 5, "level_id": 1}
{"id": 24751, "name": BL, "level_id": 6}
Well, in Oracle (which tag you've used), CONCAT function accepts only two arguments, and - therefore - that code won't work.
Instead, use a double pipe || operator. As of your main problem, CASE it is.
SQL> with test (source_id, source_groupid, source_nm, category_id, level_id) as
2 (select 12345, 34, 'ABC', 7, 2 from dual union all
3 select 67549, null, 'GI' , 5, 1 from dual union all
4 select 24751, null, 'BL' , null, 6 from dual
5 )
6 select '{' || '"id": ' || source_id ||
7 case when source_groupid is not null then ', "groupid": ' || source_groupid end ||
8 case when source_nm is not null then ', "name": ' || source_nm end ||
9 case when category_id is not null then ', "category_id": ' || category_id end ||
10 case when level_id is not null then ', "level_id": ' || level_id end || '}'
11 as result
12 from test;
RESULT
--------------------------------------------------------------------------------
{"id": 12345, "groupid": 34, "name": ABC, "category_id": 7, "level_id": 2}
{"id": 67549, "name": GI, "category_id": 5, "level_id": 1}
{"id": 24751, "name": BL, "level_id": 6}
SQL>
From Oracle 12, don't build JSON by hand; use the JSON_OBJECT function and then you can use ABSENT ON NULL:
SELECT JSON_OBJECT(
KEY 'id' VALUE source_id,
KEY 'groupid' VALUE source_groupid,
KEY 'name' VALUE source_nm,
KEY 'category_id' VALUE category_id,
KEY 'level_id' VALUE level_id
ABSENT ON NULL
) As json
FROM table_name;
Which, for the sample data:
CREATE TABLE table_name (source_id, source_groupid, source_nm, category_id, level_id) AS
SELECT 12345, 34, 'ABC', 7, 2 FROM DUAL UNION ALL
SELECT 67549, NULL, 'GI', 5, 1 FROM DUAL UNION ALL
SELECT 24751, NULL, 'BL', NULL, 6 FROM DUAL;
Outputs:
JSON
{"id":12345,"groupid":34,"name":"ABC","category_id":7,"level_id":2}
{"id":67549,"name":"GI","category_id":5,"level_id":1}
{"id":24751,"name":"BL","level_id":6}
db<>fiddle here

Is there a way to filter rows in BigQuery by the contents of an array?

I have data in a BigQuery table that looks like this:
[
{ "id": 1, "labels": [{"key": "a", "value": 1}, {"key": "b", "value": 2}] },
{ "id": 2, "labels": [{"key": "a", "value": 1}, {"key": "b", "value": 3}] },
// a lot more rows
]
My question is, how can I find all rows where "key" = "a", "value" = 1, but also "key" = "b" and "value" = 3?
I've tried various forms of using UNNEST but I haven't been able to get it right. The CROSS JOIN leaves me with one row for every object in the labels array, leaving me unable to query by both of them.
Try this:
select *
from mytable
where exists (select 1 from unnest(labels) where key = "a" and value=1)
and exists (select 1 from unnest(labels) where key = "b" and value=3)
Assuming there is no duplicate entries in labels array - you can use below
select *
from `project.dataset.table` t
where 2 = (
select count(1)
from t.labels kv
where kv in (('a', 1), ('b', 3))
)
You can try parse JSON and then you can apply different filter conditions on it as per your requirements, in following query I have tried to identified which all records have Key=a then I tried to identify which record has value=30 then joined them through id :-
WITH data1 AS (
SELECT '{ "id": 1, "labels": [{"key": "a", "value": 11}, {"key": "b", "value": 22}] }' as c1
union all
SELECT '{ "id": 2, "labels": [{"key": "a", "value": 10}, {"key": "b", "value": 30}] }' AS C1
)
select T1.id, T1.C1, T1.key, T2.value from
(SELECT JSON_EXTRACT_SCALAR(c1 , "$.id") AS id,
json_extract_scalar(curSection, '$.value') as value, json_extract_scalar(curSection, '$.key') as key,
c1
FROM data1 tbl LEFT JOIN unnest(json_extract_array(tbl.c1, '$.labels')
) curSection
where json_extract_scalar(curSection, '$.key')='a') T1,
(
SELECT JSON_EXTRACT_SCALAR(c1 , "$.id") AS id,
json_extract_scalar(curSection, '$.value') as value, json_extract_scalar(curSection, '$.key') as key,
c1
FROM data1 tbl LEFT JOIN unnest(json_extract_array(tbl.c1, '$.labels')
) curSection
where json_extract_scalar(curSection, '$.value')='30') T2
where T1.id = T2.id
Hopefully it may work for you.

How to select a field of a JSON object coming from the WHERE condition

I have this table
id name json
1 alex {"type": "user", "items": [ {"name": "banana", "color": "yellow"}, {"name": "apple", "color": "red"} ] }
2 peter {"type": "user", "items": [ {"name": "watermelon", "color": "green"}, {"name": "pepper", "color": "red"} ] }
3 john {"type": "user", "items": [ {"name": "tomato", "color": "red"} ] }
4 carl {"type": "user", "items": [ {"name": "orange", "color": "orange"}, {"name": "nut", "color": "brown"} ] }
Important, each json object can have different number of "items", but what I need is the "product name" of JUST the object that matched in the WHERE condition.
My desired output would be the two first columns and just the name of the item, WHERE the color is like %red%:
id name fruit
1 alex apple
2 peter pepper
3 john tomato
select id, name, ***** (this is what I don't know) FROM table
where JSON_EXTRACT(json, "$.items[*].color") like '%red%'
I would recommend json_table(), if you are running MySQL 8.0:
select t.id, t.name, x.name as fruit
from mytable t
cross join json_table(
t.js,
'$.items[*]' columns (name varchar(50) path '$.name', color varchar(50) path '$.color')
) x
where x.color = 'red'
This function is not implemented in MariaDB. We can unnest manually with the help of a numbers table:
select t.id, t.name,
json_unquote(json_extract(t.js, concat('$.items[', x.num, '].name'))) as fruit
from mytable t
inner join (select 0 as num union all select 1 union all select 2 ...) x(num)
on x.num < json_length(t.js, '$.items')
where json_unquote(json_extract(t.js, concat('$.items[', x.num, '].color'))) = 'red'
You can use JSON_EXTRACT() function along with Recursive Common Table Expression in order to generate rows dynamically such as
WITH RECURSIVE cte AS
(
SELECT 1 AS n
UNION ALL
SELECT n + 1
FROM cte
WHERE cte.n < (SELECT MAX(JSON_LENGTH(json)) FROM t )
)
SELECT id, name,
JSON_UNQUOTE(JSON_EXTRACT(json,CONCAT('$.items[',n-1,'].name'))) AS fruit
FROM cte
JOIN t
WHERE JSON_EXTRACT(json,CONCAT('$.items[',n-1,'].color')) = "red"
Demo

In PostgreSQL, what's the best way to select an object from a JSONB array?

Right now, I have an an array that I'm able to select off a table.
[{"_id": 1, "count: 3},{"_id": 2, "count: 14},{"_id": 3, "count: 5}]
From this, I only need the count for a particular _id. For example, I need the count for
_id: 3
I've read the documentation but I haven't been able to figure out the correct way to get the object.
WITH test_array(data) AS ( VALUES
('[
{"_id": 1, "count": 3},
{"_id": 2, "count": 14},
{"_id": 3, "count": 5}
]'::JSONB)
)
SELECT val->>'count' AS result
FROM
test_array ta,
jsonb_array_elements(ta.data) val
WHERE val #> '{"_id":3}'::JSONB;
Result:
result
--------
5
(1 row)

How do I return a jsonb array and array of objects from my data?

I have the following table:
CREATE TABLE mytable (
id serial PRIMARY KEY
, employee text UNIQUE NOT NULL
, data jsonb
);
With the following data:
INSERT INTO mytable (employee, data)
VALUES
('Jim', '{"sales_tv": [{"value": 10, "yr": "2010", "loc": "us"}, {"value": 5, "yr": "2011", "loc": "europe"}, {"value": 40, "yr": "2012", "loc": "asia"}], "sales_radio": [{"value": 11, "yr": "2010", "loc": "us"}, {"value": 8, "yr": "2011", "loc": "china"}, {"value": 76, "yr": "2012", "loc": "us"}], "another_key": "another value"}'),
('Rob', '{"sales_radio": [{"value": 7, "yr": "2014", "loc": "japan"}, {"value": 3, "yr": "2009", "loc": "us"}, {"value": 37, "yr": "2011", "loc": "us"}], "sales_tv": [{"value": 4, "yr": "2010", "loc": "us"}, {"value": 18, "yr": "2011", "loc": "europe"}, {"value": 28, "yr": "2012", "loc": "asia"}], "another_key": "another value"}')
Notice that there are other keys in there besides just "sales_tv" and "sales_radio". For the queries below I just need to focus on "sales_tv" and "sales_radio".
I need to find all sales for Jim for 2012. Anything that starts with "sales_" and then put that in an object (just need the what the product sold is and the value). e.g.:
employee | sales_
Jim | {"sales_tv": 40, "sales_radio": 76}
I've got:
SELECT * FROM mytable,
(SELECT l.key, l.value FROM mytable, lateral jsonb_each_text(data) AS l
WHERE key LIKE 'sales_%') AS a,
jsonb_to_recordset(a.value::jsonb) AS d(yr text, value float)
WHERE mytable.employee = 'Jim'
AND d.yr = '2012'
But I can't seem to even get just Jim's data. Instead I get:
employee | key | value
-------- |------ | -----
Jim | sales_tv | [{"yr": "2010", "loc": "us", "value": 4}, {"yr": "2011", "loc": "europe", "value": 18}, {"yr": "2012", "loc": "asia", "value": 28}]
Jim | sales_tv | [{"yr": "2010", "loc": "us", "value": 10}, {"yr": "2011", "loc": "europe", "value": 5}, {"yr": "2012", "loc": "asia", "value": 40}]
Jim | sales_radio | [{"yr": "2010", "loc": "us", "value": 11}, {"yr": "2011", "loc": "china", "value": 8}, {"yr": "2012", "loc": "us", "value": 76}]
You treat the result of the first join as JSON, not as text string, so use jsonb_each() instead of jsonb_each_text():
SELECT t.employee, json_object_agg(a.k, d.value) AS sales
FROM mytable t
JOIN LATERAL jsonb_each(t.data) a(k,v) ON a.k LIKE 'sales_%'
JOIN LATERAL jsonb_to_recordset(a.v) d(yr text, value float) ON d.yr = '2012'
WHERE t.employee = 'Jim' -- works because employee is unique
GROUP BY 1;
GROUP BY 1 is shorthand for GROUP BY t.employee.
Result:
employee | sales
---------+--------
Jim | '{ "sales_tv" : 40, "sales_radio" : 76 }'
I also untangled and simplified your query.
json_object_agg() is instrumental in aggregating name/value pairs as JSON object. Optionally cast to jsonb if you need that - or use jsonb_object_agg() in Postgres 9.5 or later.
Using explicit JOIN syntax to attach conditions in their most obvious place.
The same without explicit JOIN syntax:
SELECT t.employee, json_object_agg(a.k, d.value) AS sales
FROM mytable t
, jsonb_each(t.data) a(k,v)
, jsonb_to_recordset(a.v) d(yr text, value float)
WHERE t.employee = 'Jim'
AND a.k LIKE 'sales_%'
AND d.yr = '2012'
GROUP BY 1;
Your first query can be solved like this (shooting from the hip, no access to PG 9.4 here):
SELECT employee, json_object_agg(key, sales)::jsonb AS sales_
FROM (
SELECT t.employee, j.key, sum((e->>'value')::int) AS sales
FROM mytable t,
jsonb_each(t.data) j,
jsonb_array_elements(j.value) e
WHERE t.employee = 'Jim'
AND j.key like 'sales_%'
AND e->>'yr' = '2012'
GROUP BY t.employee, j.key) sub
GROUP BY employee;
The trick here is that you use LATERAL joins to "peel away" outer layers of the jsonb object to get at data deeper down. This query is assuming that Jim may have sales in multiple locations.
(Working on your query 2)