SQL Server: How to get JSON in one query? - json

I have 2 queries return json
select * from
(
SELECT Person.id, Person.name as label, 'Person' as 'group', Person.id as value
FROM Person, likes, Restaurant
WHERE MATCH (Person-(likes)->Restaurant)
)
t FOR JSON PATH,
ROOT('nodes')
--- edges
SELECT pe.id as 'from',re.id as 'to',
(cast(pe.id as nvarchar(12))+N'=>'+cast(re.id as nvarchar(12))) as label,
'to' as arrows
FROM person pe, likes li, restaurant re
where match(pe-(li)->re)
FOR JSON PATH,ROOT('edges')
They return 2 json results
"nodes": [
{"id":1,"label":"John","group":"Person","value":1},
{"id":2,"label":"Mary","group":"Person","value":2},
{"id":3,"label":"Alice","group":"Person","value":3},
]}
{"edges":[
{"from":1,"to":1,"label":"1=>1","arrows":"to"},
{"from":2,"to":2,"label":"2=2","arrows":"to"},
]}
I'd like to wrap this to a function which returns json.
How to receive the result with additional level like this in one query:
{
"name": "Nodes", --<-- the new one!
"nodes": [
{"id":1,"label":"John","group":"Person","value":1},
{"id":2,"label":"Mary","group":"Person","value":2},
{"id":3,"label":"Alice","group":"Person","value":3},
]}
{"edges":[
{"from":1,"to":1,"label":"1=>1","arrows":"to"},
{"from":2,"to":2,"label":"2=2","arrows":"to"},
]}

Solved:
I have made it via select into nvarchar

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

JSON EXTRACT SUM returning 0 instead of correct value

I'm trying to sum the contents of a json array in a mysql database, below is the JSON format and the query I'm running. Is there something wrong with it?
// Options JSON Format:
[
{
"optionId": 1,
"optionName": "With Meat",
"optionPrice": 2000
},
{
"optionId": 2,
"optionName": "With Veggies",
"optionPrice": 0
}
]
// Query:
SELECT id, SUM(options->'$[*].optionPrice') FROM table_order_items GROUP BY id;
The result is 0, when it should be 2000
While this query:
SELECT id, options->'$[*].optionPrice' FROM table_order_items;
correctly returns [2000,0]
You need the function JSON_TABLE() to extract the prices:
SELECT t.id,
SUM(j.price) AS total
FROM table_order_items t
JOIN JSON_TABLE(
t.options,
'$[*]' COLUMNS(price INT PATH '$.optionPrice')
) j
GROUP BY t.id;
See the demo.

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 explode several list values JSON within JSON with lateral flatten in Snowflake?

I have a variant table with 24 JSONs like the following (one per row):
{
"context": "marketplace",
"metadata": {
"app_version": "1.0.4 (166)",
},
"params": {
"filter": {
"brands": [],
"categories": [
"f28c7c9f-09ae-4218-821a-bec344998289"
],
"manufacturers": [],
"page": 1,
"product_name": "",
},
"page": "product_list",
"results": 3
},
"user_id": "6443a2db-4526-4fc5-8084-290fc78e5336"
}
I want everything exploded in rows. I have managed to have everything but filter as I want with the code:
SELECT data:event::string,
data:user_id::string,
data:metadata.app_version::string,
data:context::string,
data:params.page::string,
data:params.filter.page::string,
data:params.results::string
FROM ods.stg_tanimis_events
The lists brands, categories and manufacturers can be empty but I would need a null value if that is the case. I would like to have a table with the following columns:
event, user_id, app_version, context, param_page, filter_page, results, manufacturer, brand, category
I have tried with several lateral flatten with no success:
select * FROM table
, lateral flatten (data:params:filter:categories) j2;
select * FROM table
, lateral flatten (data:params:filter.brands) j1
select * FROM table
, lateral flatten (data:params:filter:brands) j1
, lateral flatten (data:params:filter:categories) j2;
select user_id, filter, flat.*
from table
, lateral flatten(parse_json(filter)) flat;
WITH j as (
SELECT *
FROM table
, lateral flatten(data:params:filter))
SELECT *
from j,
lateral flatten (j.value) j2;
Either way, I get 8 rows or 0 rows, or errors. How could I make it? Thanks
In your example data, there is no data in "brands", which means any flatten against that will return nothing unless you add , OUTER => TRUE in your lateral flatten statement. Something more like this:
SELECT table.data:event::string,
table.data:user_id::string,
table.data:metadata.app_version::string,
table.data:context::string,
table.data:params.page::string,
table.data:params.filter.page::string,
table.data:params.results::string,
j1.value::string as brands,
j2.value::string as categories
FROM table
, lateral flatten (data:params:filter:brands, OUTER => TRUE) j1
, lateral flatten (data:params:filter:categories, OUTER => TRUE) j2;
This will act like a LEFT JOIN to the base table and return NULL for any flattened arrays that are empty.

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