extracting specific json key/values from postgresql jsonb table - json

I am using postgres 9.5 and i have a table like this:
create table t1 (
id serial,
fkid int not null,
tstamp timestamp with time zone,
data jsonb
)
a typical json is:
{
"WifiStatistic": {
"Interfaces": {
"wlan0": {
"qualityLevel": {
"type": "graph",
"unit": "string",
"value": "75",
"graphDisplayName": "Quality level"
},
"SNR": {
"type": "graph",
"unit": "string",
"value": "75",
"graphDisplayName": "SNR"}
}
}
}
}
What 'id like as a result of a query that extract the quality level is a recordset like:
id | fkid | tstamp | value | graphdisplayName
-------------------------------------------------
1 | 1 | 2017-01-22 | 75 | "Quality Level"
what kind of query might i use?

Thanks to #VaoTsun for his comment, i ended up using this:
select
tstamp, id, fkid,
data->'WifiStatistics'->'Interfaces'->'wlan0'->'qualityLevel'->'value' as value,
data #>'{WifiStatistics, Interfaces, wlan0, qualityLevel}' ->'graphDisplayName' as dname
from data;
i was trying to recover two values with a single json selection, that is what puzzled me.

Related

Update a nested value from a Json field

Consider this table:
DROP TABLE IF EXISTS `example`;
CREATE TABLE `example` (
`id` int NOT NULL AUTO_INCREMENT,
`content` json NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3;
And these rows:
INSERT INTO example(content)
VALUES (
'[ { "date": "1617210148", "name": "John", "status": "0" },
{ "date": "1617210148", "name": "Jack", "status": "0" },
{ "date": "1617210148", "name": "Henry", "status": "0" }]'
);
I'd like to update the value of the key status where name = Jack to 1
The result would be:
{ "date": "1617210148", "name": "Jack", "status": "1" }
How can I do this using JSON_REPLACE() or JSON_SET() in a SQL query (I'm aiming for a partial update since I'm using MySQL 8.0.25)?
This is very awkward, nearly impossible with MySQL's JSON functions.
You can use JSON_REPLACE() or JSON_SET(), but both require that you know the path to the field you want to change. So in this case, we can see that the array element is $[1] but if you didn't know that, you couldn't use this solution.
mysql> select json_pretty(json_replace(content, '$[1].status', '1')) as j
from example\G
*************************** 1. row ***************************
j: [
{
"date": "1617210148",
"name": "John",
"status": "0"
},
{
"date": "1617210148",
"name": "Jack",
"status": "1"
},
{
"date": "1617210148",
"name": "Henry",
"status": "0"
}
]
The question like yours has come up before on Stack Overflow, for example JSON update single value in MySQL table. The solution in that case depends on you knowing which array element your pseudo-record exists in.
You can get the path to a JSON element with JSON_SEARCH(), but you can only search by value, not by a key/value pair. If "Jack" occurs in some other field, that would also be found.
mysql> select json_unquote(json_search(content, 'one', 'Jack')) as path from example;
+-----------+
| path |
+-----------+
| $[1].name |
+-----------+
To search for a key/value pair, you need to use JSON_TABLE() and that requires you upgrade to MySQL 8.0. And that doesn't tell you the path to the element, it only allows you to return a specific row out of the array.
mysql> select j.* from example cross join json_table(content, '$[*]' columns(
date int unsigned path '$.date',
name varchar(10) path '$.name',
status int path '$.status')
) as j where name = 'Jack';
+------------+------+--------+
| date | name | status |
+------------+------+--------+
| 1617210148 | Jack | 0 |
+------------+------+--------+
Here's a trick: You can extract the name field, and that turns into an array of those values:
mysql> select json_extract(content, '$[*].name') as a from example;
+---------------------------+
| a |
+---------------------------+
| ["John", "Jack", "Henry"] |
+---------------------------+
Then you can search that array to get the array position:
mysql> select json_search(json_extract(content, '$[*].name'), 'one', 'Jack') as root from example;
+--------+
| root |
+--------+
| "$[1]" |
+--------+
Some unquoting and adding .status and you can get the full path to the field you want to update:
mysql> select concat(json_unquote(json_search(json_extract(content, '$[*].name'), 'one', 'Jack')), '.status') as path from example;
+-------------+
| path |
+-------------+
| $[1].status |
+-------------+
Now use it in a JSON_SET() call:
mysql> select json_pretty(
json_set(content,
concat(json_unquote(json_search(json_extract(content, '$[*].name'), 'one', 'Jack')), '.status'),
'1')) as newcontent
from example\G
*************************** 1. row ***************************
newcontent: [
{
"date": "1617210148",
"name": "John",
"status": "0"
},
{
"date": "1617210148",
"name": "Jack",
"status": "1"
},
{
"date": "1617210148",
"name": "Henry",
"status": "0"
}
]
Using this in an UPDATE looks like this:
mysql> update example set content = json_set(content, concat(json_unquote(json_search(json_extract(content, '$[*].name'), 'one', 'Jack')), '.status'), '1');
That's a long way to go. Now compare how difficult that is to:
UPDATE content SET status = 1 WHERE name = 'Jack';
Storing data in a JSON document when you eventually want to use SQL expressions to search or update individual fields within the JSON document is a costly mistake. It increases the complexity of any code you write to do it, and the developer who needs to take over maintenance of your code after you have moved on to another project will curse your name.

How to query expanded json content in db2

Since version 11, DB2 supports json functions and json value query. I was trying to follow the documentation but could not figure out how to expand a json object into multiple rows. Would like to know if it's possible at all in DB2.
Example:
The JSON object looks like this:
{
"content":{
"key_1": {
"text": "sample",
"label": "l1"
},
"key_2": {
"text": "sample2",
"label": "l2"
},
...
}
}
and the table looks like this:
|id|json|
|--|----|
|1 | '{...}'|
|2 | '{...}'|
I want to query this table:
|id|text|label|
|--|----|-----|
|1 |sample|l1|
|1 |sample2|l2|
...
I tried
SELECT JSON_VALUE(json, 'strict $.content.*.text' RETURNING CHAR(10)) AS text,
JSON_VALUE(json, 'strict $.content.*.label' RETURNING CHAR(10)) AS label
FROM table
but I got NULL for all the rows.
Any insights are appreciated. Thank you!
I belive you need to do something like this
SELECT I, T.*
FROM
TABLE(VALUES('{
"content":{
"key_1": {
"text": "sample",
"label": "l1"
},
"key_2": {
"text": "sample2",
"label": "l2"
}
}
}')) AS D(JSON)
, TABLE(VALUES(1),(2),(3)) AS A(I)
, JSON_TABLE(D.JSON, 'strict $'
COLUMNS(
TEXT VARCHAR(80) PATH '$.content.key_' || i || '.text'
, LABEL VARCHAR(80) PATH '$.content.key_' || i || '.label'
)
ERROR ON ERROR) AS T
WHERE T.TEXT is NOT NULL
which will give you
I|TEXT |LABEL
-|-------|------
1|sample |l1
2|sample2|l2
The A table in the above will need as many rows as you have key_ entries in the JSON

Cassandra - Nested Json Insert

DSE 6.7
CentOS 6.7
We have data in JSON with the below format -
{ "Event_Type": "Booked", "Car": { "RegId": "0961dbac-297d-424c-96ce-9f37f1707cd9" } }
{ "Event_Type": "Complete", "Car": { "RegId": "0961dbac-297d-424c-96ce-9f37f1707cd9" } }
{ "Event_Type": "Booked", "Car": { "RegId": "b32f7b0b-d077-44c4-a454-519a3f82c759" } }
{ "Event_Type": "Complete", "Car": {"RegId": "b32f7b0b-d077-44c4-a454-519a3f82c759"} }
{ "Event_Type": "Booked", "Car": { "RegId": "6fa0b439-0782-49e8-9ac3-78c275470516" } }
{ "Event_Type": "Complete", "Car": { "RegId": "6fa0b439-0782-49e8-9ac3-78c275470516"} }
we need to insert into cassandra with the below table structure
CREATE TABLE IF NOT EXISTS "car"."car_appt" (
"event_type" text,
"RegId" uuid,
"timetamp" timestamp
);
Requried Output
Event_Type RegId
Booked 0961dbac-297d-424c-96ce-9f37f1707cd9
Complete 0961dbac-297d-424c-96ce-9f37f1707cd9
Booked b32f7b0b-d077-44c4-a454-519a3f82c759
Complete b32f7b0b-d077-44c4-a454-519a3f82c759
I am unable to insert the above json, since it is inside a nested json.
Let me know if I need to provide more information.
Cassandra tries to put some structure around a non-structured row that you've supplied. You didn't specify "car", which is really what your reqId "belongs to", so it bombs. The best option is to create a type (so it can be used with the PK definition) and then create the table in the following way:
create type cartype (
regId text
);
create table car_appt (
event_type text,
car frozen<cartype>,
timestamp timestamp,
primary key (event_type, car)
);
Once that's done, you're all set.
insert into car_appt JSON '{ "Event_Type": "Booked", "Car": { "RegId": "6fa0b439-0782-49e8-9ac3-78c275470516" } }';
select * from car_appt;
event_type | car
------------+-------------------------------------------------
Booked | {regid: '6fa0b439-0782-49e8-9ac3-78c275470516'}
Also
select car.regId from car_appt;
car.regid
--------------------------------------
6fa0b439-0782-49e8-9ac3-78c275470516
Like I said, this creates some structure around your data, where-as json itself is unstructured. This means you can't have some "rows" that have different fields in the nested document as it will fail. You need to have "car" and you need to have "regId" for all cassandra rows or it will fail. If this doesn't seem to work well for you, you may want to consider a document database instead (i.e. Mongo).
From your update, here is the output of the 6 inserts you provided. Other than the order, and the regid column heading, it looks the same.
select event_type, car.regId from car_appt;
event_type | car.regid
------------+--------------------------------------
Booked | 0961dbac-297d-424c-96ce-9f37f1707cd9
Booked | 6fa0b439-0782-49e8-9ac3-78c275470516
Booked | b32f7b0b-d077-44c4-a454-519a3f82c759
Complete | 0961dbac-297d-424c-96ce-9f37f1707cd9
Complete | 6fa0b439-0782-49e8-9ac3-78c275470516
Complete | b32f7b0b-d077-44c4-a454-519a3f82c759

PostgreSQL return JSON objects as key-value pairs

A PostgreSQL instance stores data in JSONB format:
CREATE TABLE myschema.mytable (
id BIGSERIAL PRIMARY KEY,
data JSONB NOT NULL
)
The data array might contain objects like:
{
"observations": {
"temperature": {
"type": "float",
"unit": "C",
"value": "23.1"
},
"pressure": {
"type": "float",
"unit": "mbar",
"value": "1011.3"
}
}
}
A selected row should be returned as key-value pairs in a format like:
temperature,type,float,value,23.1,unit,C,pressure,type,float,value,1011.3,unit,mbar
The following query returns at least each object, while still JSON:
SELECT id, value FROM mytable JOIN jsonb_each_text(mytable.data->'observations') ON true;
1 | {"type": "float", "unit": "mbar", "value": 1140.5}
1 | {"type": "float", "unit": "C", "value": -0.9}
5 | {"type": "float", "unit": "mbar", "value": "1011.3"}
5 | {"type": "float", "unit": "C", "value": "23.1"}
But the results are splitted and not in text format.
How can I return key-value pairs of all objects in data?
This will flatten the json structure and effectively just concatenate the values, along with the top-level key names (e.g. temperature and pressure), for the expected "depth" level. See if this is what you had in mind.
SELECT
id,
(
SELECT STRING_AGG(conc, ',')
FROM (
SELECT CONCAT_WS(',', key, STRING_AGG(value, ',')) AS conc
FROM (
SELECT key, (jsonb_each_text(value)).value
FROM jsonb_each(data->'observations')
) AS x
GROUP BY key
) AS csv
) AS csv
FROM mytable
Result:
| id | csv |
| --- | --------------------------------------------------- |
| 1 | pressure,float,mbar,1011.3,temperature,float,C,23.1 |
| 2 | pressure,bigint,unk,455,temperature,int,F,45 |
https://www.db-fiddle.com/f/ada5mtMgYn5acshi3WLR7S/0

How can I use MariaDB to pull values from multiple objects from a JSON array?

In MariaDB 10.2.19, I have a table named forms with a column template which always contains a JSON array of objects. Some of these objects will have properties I want to return: name (should always be present), rule, and parameters. How can I return just these three properties from the entire array, but only for objects on which rule is present?
A sample array (formatted for easier viewing):
[{
"label": "Employed?",
"class": "select",
"name": "employed",
"parameters": "Yes",
"rule": "in"
},
{
"label": "Breed of dog?",
"class": "select",
"name": "breed",
"parameters": "spaniel, collie, mix",
"rule": "in"
},
{
"label": "Number",
"class": "text",
"name": "breed"
}]
If you are using MySQL 8.0.4 or later one way is using JSON_TABLE:
mysql> SELECT * FROM foo;
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| data |
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| [{"name": "employed", "rule": "in", "class": "select", "label": "Employed?", "parameters": "Yes"}, {"name": "breed", "rule": "in", "class": "select", "label": "Breed of dog?", "parameters": "spaniel, collie, mix"}, {"name": "breed", "class": "text", "label": "Number"}] |
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0,00 sec)
mysql> SELECT name, parameters
FROM foo,
JSON_TABLE (
foo.data,
"$[*]" COLUMNS (
name VARCHAR(100) PATH "$.name",
rule VARCHAR(100) PATH "$.rule",
parameters VARCHAR(100) PATH "$.parameters")
) AS t
WHERE rule IS NOT NULL;
+----------+----------------------+
| name | parameters |
+----------+----------------------+
| employed | Yes |
| breed | spaniel, collie, mix |
+----------+----------------------+
2 rows in set (0,00 sec)
https://dev.mysql.com/doc/refman/8.0/en/json-table-functions.html
You can accomplish this by using a dedicated numbers_table table:
SELECT
JSON_VALUE(f.template,CONCAT('$[',n.number,'].name')) AS `name`,
JSON_VALUE(f.template,CONCAT('$[',n.number,'].rule')) AS `rule`,
JSON_VALUE(f.template,CONCAT('$[',n.number,'].parameters')) AS `parameters`
FROM forms AS f
JOIN numbers_table AS n
WHERE
n.number < JSON_LENGTH(f.template) AND
JSON_VALUE(f.template,CONCAT('$[',n.number,'].rule')) IS NOT NULL;
The numbers_table table contains a single column called number which starts at 0 and can be as long as your use cases require (I have 1000 values 0 to 999). It is very useful for extracting each element of a JSON_ARRAY field into its own row.
The first WHERE condition makes sure we only use as many numbers as there are elements in the JSON_ARRAY (template in your case).
The second WHERE condition eliminates the ones that don't have a rule as per your use case.