Get JSON array of rows with MySQL - mysql

I want that MySQL will return a result set in a form of JSON array.
A worked query:
SELECT CONCAT(
'[',
GROUP_CONCAT(
JSON_OBJECT(
'ProductId', tblproducts.ProductId,
'ProductName', tblproducts.ProductName
)
),
']') As Products
FROM tblproducts;
This query returns the following JSON array:
[
{
"ProductId": "1",
"ProductName": "Milk"
},
{
"ProductId": "2",
"ProductName": "Bread"
}
]
Although this code works, I have a strong feeling that building a JSON array with GROUP_CONCAT and CONCAT is kind of workaround. If to use a JSON_ARRAY, a result set will consist of JSON arrays for each JSON_OBJECT.
Is there any native way to get a single JSON array with all JSON_OBJECT in the result set?

JSON_ARRAYAGG() could be what you're looking for.
SELECT JSON_ARRAYAGG(
JSON_OBJECT(
'ProductId', tblproducts.ProductId,
'ProductName', tblproducts.ProductName
)
) FROM tblproducts;
Here's an example: https://www.db-fiddle.com/f/uQ9UC7MDZM4gncNjViTsKw/0

Related

How to Convert Oracle to Postgresql [duplicate]

I'm trying to migrate Oracle 12c queries to Postgres11.5.
Here is the json:
{
"cost": [{
"spent": [{
"ID": "HR",
"spentamount": {
"amount": 2000.0,
"country": "US"
}
}]
}],
"time": [{
"spent": [{
"ID": "HR",
"spentamount": {
"amount": 308.91,
"country": "US"
}
}]
}]
}
Here is the query that has to be migrated to Postgres 11.5:
select js.*
from P_P_J r,
json_table(r.P_D_J, '$.*[*]'
COLUMNS(NESTED PATH '$.spent[*]'
COLUMNS(
ID VARCHAR2(100 CHAR) PATH '$.ID',
amount NUMBER(10,4) PATH '$.spentamount.amount',
country VARCHAR2(100 CHAR) PATH '$.spentamount.country'))
) js
The result:
ID, amount, country
HR, 2000.0,US
HR,308.91,US
I have two questions here:
What does $.*[*] mean?
How can we migrate this query in Postgres so that it directly looks at 'spent' instead of navigating 'cost'->'spent' or 'time'->'spent'
There is no direct replacement for json_table in Postgres. You will have to combine several calls to explode the JSON structure.
You didn't show us your expected output, but as far as I can tell, the following should do the same:
select e.item ->> 'ID' as id,
(e.item #>> '{spentamount, amount}')::numeric as amount,
e.item #>> '{spentamount, country}' as country
from p_p_j r
cross join jsonb_each(r.p_d_j) as a(key, val)
cross join lateral (
select *
from jsonb_array_elements(a.val)
where jsonb_typeof(a.val) = 'array'
) as s(element)
cross join jsonb_array_elements(s.element -> 'spent') as e(item)
;
The JSON path expression '$.*[*] means: iterate over all top-level keys, then iterate over all array elements found in there and the nested path '$.spent[*]' then again iterates over all array elements in there. These steps are reflected in the three JSON function calls that are needed to get there.
With Postgres 12, this would be a bit easier as this can be done with a single call to jsonb_path_query() which also use a JSON Path to access the elements using a very similar JSON path expression:
select e.item ->> 'ID' as id,
(e.item #>> '{spentamount, amount}')::numeric as amount,
e.item #>> '{spentamount, country}' as country
from p_p_j r
cross join jsonb_path_query(r.p_d_j, '$.*[*].spent[*]') as e(item)
;
Online example

Complex JSON using JSON_MODIFY without nested arrays or escape characters (WITHOUT_ARRAY_WRAPPER)

I am using JSON_MODIFY to build complex JSON. Moving from MySQL I am struggling with the JSON functions provided by SQL Server. The issue I'm having is that SQL Server seems to construct all JSON objects in an array. There is the WITHOUT_ARRAY_WRAPPER mechanism, which seems like it should do what I want, however; there are two undesirable consequences.
It only returns one result depending on how it is used
The result is a single string with escape characters
I have constructed a simple query which illustrates my needs and the issue.
QUERY 1
SELECT JSON_MODIFY(
JSON_QUERY('{"definitions": {"id": "INT", "name": "VARCHAR(23)"}}'),
'append $.data',
(
SELECT * FROM (
SELECT 1 AS id, '123abc' AS "name" UNION
SELECT 2 AS id, '234bcd' AS "name"
) AS "data"
FOR JSON PATH, WITHOUT_ARRAY_WRAPPER
)
) AS "data";
OUTPUT 1
{
"definitions":{
"id":"INT",
"name":"VARCHAR(23)"
},
"data":[
"{\"id\":1,\"name\":\"123abc\"},{\"id\":2,\"name\":\"234bcd\"}"
]
}
QUERY 2
SELECT JSON_MODIFY(
JSON_QUERY('{"definitions": {"id": "INT", "name": "VARCHAR(23)"}}'),
'append $.data',
(
SELECT * FROM (
SELECT 1 AS id, '123abc' AS "name" UNION
SELECT 2 AS id, '234bcd' AS "name"
) AS "data"
FOR JSON PATH
)
) AS "data";
OUTPUT 2
{
"definitions":{
"id":"INT",
"name":"VARCHAR(23)"
},
"data":[
[
{"id":1, "name":"123abc"},
{"id":2, "name":"234bcd"}
]
]
}
QUERY 1
The data object is an array (which is expected), but the problem is what is in the array... A single string with escape characters.
QUERY 2
The data object is an array, which contains an array. In order to access the actual array of data, I would use something like for each obj in data[0].... The problem this poses is, for anyone consuming the JSON object, I would have to tell them:
"In this particular object the data element is an array of
arrays--You'll want to use the first and only the first
element to access the actual array of data."
I've naively tried many different combinations of JSON_MODIFY, JSON_QUERY, and CONCAT to no avail. How can I properly use JSON_MODIFY to get the following output, without the double array in data?
{
"definitions":{
"id":"INT",
"name":"VARCHAR(23)"
},
"data":[
{"id":1, "name":"123abc"},
{"id":2, "name":"234bcd"}
]
}
You are over-thinking this by trying to JSON_MODIFY an existing object.
Construct the definitions and data properties that you need, inside a subquery if necessary.
Then use FOR JSON a second time to get the outer object.
SELECT
definitions = JSON_QUERY('{"id": "INT", "name": "VARCHAR(23)"}'),
data =
(
SELECT id, name
FROM (VALUES
(1, '123abc'),
(2, '234bcd')
) v(id, name)
FOR JSON PATH
)
FOR JSON PATH;
SQL Fiddle
By trial and error, I found the solution.
Removed the append keyword from the path parameter in the JSON_MODIFY statement
Removed the WITHOUT_ARRAY_WRAPPER parameter from the FOR JSON statement.
Now the results are as expected and I don't need to explain to any consumers to "Just use data[0]"
The Query
SELECT JSON_MODIFY(
JSON_QUERY('{"definitions": {"id": "INT", "name": "VARCHAR(23)"}}'),
'$.data',
(
SELECT * FROM (
SELECT 1 AS id, '123abc' AS "name" UNION
SELECT 2 AS id, '234bcd' AS "name"
) AS "data"
FOR JSON PATH
)
) AS "data";
Produces the following output
{
"definitions":{
"id":"INT",
"name":"VARCHAR(23)"
},
"data":[
{"id":1, "name":"123abc"},
{"id":2, "name":"234bcd"}
]
}

how to maintain order of elements using snowflake object_construct() function instead of sorting by the keys?

Following snowflake query returns the JSON structure but output is sorted by the keys. How not to sort by the keys but retains the order? Is there any parameter setting that needs to be set?
select
object_construct
(
'entity', 'XYZ',
'allowed', 'Yes',
'currency', 'USD',
'statement_month','July, 2020'
)
Output: --it sorts by the keys
{
"allowed": "Yes",
"currency": "USD",
"entity": "XYZ",
"statement_month": "July, 2020"
}
Expected Output: --same order as specified
{
"entity": "XYZ",
"allowed": "Yes",
"currency": "USD",
"statement_month": "July, 2020"
}
JSON is an unordered collection of name and values. Order cannot be guaranteed in JSON.
The constructed object does not necessarily preserve the original order of the key-value pairs.
You can do it like as below
SELECT mytable:entity::string as entity,
mytable:allowed::string as allowed,
mytable:currency::string as currency,
mytable:statement_month::string as statement_month
from
(select
object_construct
(
'entity', 'XYZ',
'allowed', 'Yes',
'currency', 'USD',
'statement_month','July, 2020'
) mytable);
Unfortunately, no
Usage notes:
https://docs.snowflake.com/en/sql-reference/functions/object_construct.html#usage-notes
The constructed object does not necessarily preserve the original order of the key-value pairs.
same for PARSE_JSON Usage notes:
https://docs.snowflake.com/en/sql-reference/functions/parse_json.html#usage-notes
The order of the key-value pairs in the string produced by TO_JSON is not predictable.
The order was found to be maintained when using object_construct(*):
WITH base AS (
SELECT 'XYZ' "entity", 'Yes' "allowed", 'USD' "currency", 'July, 2020' "statement_month")
SELECT object_construct(*) FROM base;

Oracle JSON_TABLE to PostgreSQL - how to search from the second hierarchical key in a JSON column

I'm trying to migrate Oracle 12c queries to Postgres11.5.
Here is the json:
{
"cost": [{
"spent": [{
"ID": "HR",
"spentamount": {
"amount": 2000.0,
"country": "US"
}
}]
}],
"time": [{
"spent": [{
"ID": "HR",
"spentamount": {
"amount": 308.91,
"country": "US"
}
}]
}]
}
Here is the query that has to be migrated to Postgres 11.5:
select js.*
from P_P_J r,
json_table(r.P_D_J, '$.*[*]'
COLUMNS(NESTED PATH '$.spent[*]'
COLUMNS(
ID VARCHAR2(100 CHAR) PATH '$.ID',
amount NUMBER(10,4) PATH '$.spentamount.amount',
country VARCHAR2(100 CHAR) PATH '$.spentamount.country'))
) js
The result:
ID, amount, country
HR, 2000.0,US
HR,308.91,US
I have two questions here:
What does $.*[*] mean?
How can we migrate this query in Postgres so that it directly looks at 'spent' instead of navigating 'cost'->'spent' or 'time'->'spent'
There is no direct replacement for json_table in Postgres. You will have to combine several calls to explode the JSON structure.
You didn't show us your expected output, but as far as I can tell, the following should do the same:
select e.item ->> 'ID' as id,
(e.item #>> '{spentamount, amount}')::numeric as amount,
e.item #>> '{spentamount, country}' as country
from p_p_j r
cross join jsonb_each(r.p_d_j) as a(key, val)
cross join lateral (
select *
from jsonb_array_elements(a.val)
where jsonb_typeof(a.val) = 'array'
) as s(element)
cross join jsonb_array_elements(s.element -> 'spent') as e(item)
;
The JSON path expression '$.*[*] means: iterate over all top-level keys, then iterate over all array elements found in there and the nested path '$.spent[*]' then again iterates over all array elements in there. These steps are reflected in the three JSON function calls that are needed to get there.
With Postgres 12, this would be a bit easier as this can be done with a single call to jsonb_path_query() which also use a JSON Path to access the elements using a very similar JSON path expression:
select e.item ->> 'ID' as id,
(e.item #>> '{spentamount, amount}')::numeric as amount,
e.item #>> '{spentamount, country}' as country
from p_p_j r
cross join jsonb_path_query(r.p_d_j, '$.*[*].spent[*]') as e(item)
;
Online example

Correctly return column as JSON Array in MySQL after using CONCAT, GROUP_CONCAT and JSON_OBJECT

I'm using MySQL in a Node.JS API, so I need to get data from the database as JSON objects/arrays.
I'm trying to get a JSON Array nested within the result JSON as one of the values, so this is my current query:
SELECT
l.id AS id, l.description AS description, l.parent AS parent,
(
SELECT CONCAT(
'[',
GROUP_CONCAT(
JSON_OBJECT(
'id', a.id, 'description', a.description,
'ip', a.ip, 'lastmovementdetected', a.lastmovementdetected
)
),
']'
)
FROM airconditioners AS a WHERE location = l.id
) AS airconditioners
FROM locations as l`
However, this is the query result (actual output is an array of these JSON objects):
{
"id": 1,
"description": "Meu quarto",
"parent": 0,
"airconditioners": "[{\"id\": 1, \"ip\": \"192.168.137.96\", \"description\": \"Ar-condicionado\", \"lastmovementdetected\": null},{\"id\": 2, \"ip\": \"192.168.0.1\", \"description\": \"Ar-condicionado\", \"lastmovementdetected\": null},{\"id\": 3, \"ip\": \"192.168.0.1\", \"description\": \"Ar-condicionado\", \"lastmovementdetected\": null}]"
}
SQL is returning the JSON Array as a String and it's also escaping the double quotes from within the JSON.
This is the expected return:
"id": 1,
"description": "Meu quarto",
"parent": 0,
"airconditioners": [
{
"id":1,
"ip":"192.168.137.96",
"description":"Ar-condicionado",
"lastmovementdetected":null
},
{
"id":2,
"ip":"192.168.0.1",
"description":"Ar-condicionado",
"lastmovementdetected":null
},
{
"id":3,
"ip":"192.168.0.1",
"description":"Ar-condicionado",
"lastmovementdetected":null
}
]
Can this be done using a SQL query only? Or I'll have to treat the result before sending the response on the API?
I've tried surrounding the column with a CAST((SELECT...) AS JSON) AS airconditioners and also putting JSON_UNQUOTE() in many places, with no success whatsoever.
EDIT
I couldn't get to a conclusion whether MySQL is compatible with what I want or not. But, for instance, I'm using the following Javascript code to work around it:
Object.keys(result).forEach(key => {
let airconditioners = result[key].airconditioners;
if(airconditioners == null) {
// If the airconditioner field is null, then we replace it with an empty array
result[key].airconditioners = [];
} else {
result[key].airconditioners = JSON.parse(airconditioners);
}
});
use JSON_EXTRACT then get result as you expect
SELECT
l.id AS id, l.description AS description, l.parent AS parent,
(
SELECT JSON_EXTRACT( IFNULL(
CONCAT(
'[',
GROUP_CONCAT(
JSON_OBJECT(
'id', a.id, 'description', a.description,
'ip', a.ip, 'lastmovementdetected', a.lastmovementdetected
)
),
']'
)
,'[]'),'$')
FROM airconditioners AS a WHERE location = l.id
) AS airconditioners
FROM locations as l`