Is it possible to get a field of a JSON object that is a field of a JSON object without casting twice? - postgresql

Is the second way the only way to get the second result or is something else possible?
1.
select '{"a": [{"b":1}]}'::jsonb -> 'a' ->> 0;
---
{"b": 1}
2.
select ('{"a": [{"b":1}]}'::jsonb -> 'a' ->> 0)::jsonb -> 'b';
---
1
3.
select '{"a": [{"b":1}]}'::jsonb -> 'a' ->> 0 -> 'b';
---
No operator matches the given name and argument types...

casting (back to json) is probably only needed in 2 since you're casting the first element of a to text via ->>. try this instead:
select '{"a": [{"b":1}]}'::jsonb -> 'a' -> 0 -> 'b';
It should work and return 1 (as a json object) which you then might have to cast to int or another appropriate type to reuse in any computation.
alternatively, this should also work, and is shorter as well:
select '{"a": [{"b":1}]}'::jsonb #> '{a,0,b}';
(disclosure: i'm not at a computer with postgresql. i did not test the above queries)

Related

Combine JSONB array of values by consecutive pairs

In postgresql, I have a simple one JSONB column data store:
data
----------------------------
{"foo": [1,2,3,4]}
{"foo": [10,20,30,40,50,60]}
...
I need to convert consequent pairs of values into data points, essentially calling the array variant of ST_MakeLine like this: ST_MakeLine(ARRAY(ST_MakePoint(10,20), ST_MakePoint(30,40), ST_MakePoint(50,60))) for each row of the source data.
Needed result (note that the x,y order of each point might need to be reversed):
data geometry (after decoding)
---------------------------- --------------------------
{"foo": [1,2,3,4]} LINE (1 2, 3 4)
{"foo": [10,20,30,40,50,60]} LINE (10 20, 30 40, 50 60)
...
Partial solution
I can already iterate over individual array values, but it is the pairing that is giving me trouble. Also, I am not certain if I need to introduce any ordering into the query to preserve the original ordering of the array elements.
SELECT ARRAY(
SELECT elem::int
FROM jsonb_array_elements(data -> 'foo') elem
) arr FROM mytable;
You can achieve this by using window functions lead or lag, then picking only every second row:
SELECT (
SELECT array_agg((a, b) ORDER BY o)
FROM (
SELECT elem::int AS a, lead(elem::int) OVER (ORDER BY o) AS b, o
FROM jsonb_array_elements(data -> 'foo') WITH ORDINALITY els(elem, o)
) AS pairs
WHERE o % 2 = 1
) AS arr
FROM example;
(online demo)
And yes, I would recommend to specify the ordering explicitly, making use of WITH ORDINALITY.

PostgresSQL nested jsonb query for multiple key/value pairs

Starting out with JSONB data type and I'm hoping someone can help me out.
I have a table (properties) with two columns (id as primary key and data as jsonb).
The data structure is:
{
"ProductType": "ABC",
"ProductName": "XYZ",
"attributes": [
{
"name": "Color",
"type": "STRING",
"value": "Silver"
},
{
"name": "Case",
"type": "STRING",
"value": "Shells"
},
...
]
}
I would like to get all rows where an attribute has a specific value i.e. return all rows where Case = 'Shells' and/or Color = 'Red'.
I have tried the following but I'm not able to apply two conditions like Case = 'Shells' and Color = 'Silver'.
I can get rows for when a single attributes' name and value matches conditions but I can't figure out how to get it to work for multiple attributes.
EDIT 1:
I'm able to achieve the results using the following query:
WITH properties AS (
select *
from (
values
(1, '{"ProductType": "ABC","ProductName": "XYZ","attributes": [{"name": "Color","type": "STRING","value": "Silver"},{"name": "Case","type": "STRING","value": "Shells"}]}'::jsonb),
(2, '{"ProductType": "ABC","ProductName": "XYZ","attributes": [{"name": "Color","type": "STRING","value": "Red"},{"name": "Case","type": "STRING","value": "Shells"}]}'::jsonb)
) s(id, data)
)
select
*
from (
SELECT
id,
jsonb_object_agg(attr ->> 'name', attr -> 'value') as aggr
FROM properties m,
jsonb_array_elements(data -> 'attributes') as attr
GROUP BY id
) a
where aggr ->> 'Color' = 'Red' and aggr ->> 'Case' LIKE 'Sh%'
I could potentially have millions of these records so I suppose my only concern now is if this is efficient and if not, is there a better way?
step-by-step demo:db<>fiddle
SELECT
id
FROM properties m,
jsonb_array_elements(data -> 'attributes') as attr
GROUP BY id
HAVING jsonb_object_agg(attr ->> 'name', attr -> 'value') #> '{"Color":"Silver", "Case":"Shells"}'::jsonb
The problem is, that jsonb_array_elements() moves both related values into different records. However, this call is necessary to fetch the values. So, you need to reaggregate the values after you were able to read them. This would make it possible to check them in a related manner.
This can be achieved by using the jsonb_object_agg() aggregation function. The trick here is that we create an object with attributes like "name":"value". So, with that, we can easily check if all required attributes are in the JSON object using the #> operator.
Concerning "Edit 1"
demo:db<>fiddle
You can do this:
SELECT
*
FROM (
SELECT
id,
jsonb_object_agg(attr ->> 'name', attr -> 'value') as obj
FROM properties m,
jsonb_array_elements(data -> 'attributes') as attr
GROUP BY id
) s
WHERE obj ->> 'Color' = 'Silver'
AND obj ->> 'Case' LIKE 'Sh%'
Create the new JSON structure as described above for all JSONs
Filter this result afterward.
Alternatively you can use jsonb_object_agg() in the HAVING clause as often as you need it. I guess you need to check which way is more performant in your case:
SELECT
id
FROM properties m,
jsonb_array_elements(data -> 'attributes') as attr
GROUP BY id
HAVING
jsonb_object_agg(attr ->> 'name', attr -> 'value') ->> 'Color' = 'Silver'
AND
jsonb_object_agg(attr ->> 'name', attr -> 'value') ->> 'Case' LIKE 'Sh%'

Select greatest number from a json list of variable length with PostgreSQL

I have a column (let's call it jsn) in my database with json object (actually stored as plain text for reasons). This json object looks like this:
{"a":
{"b":[{"num":123, ...},
{"num":456, ...},
...,
{"num":789, ...}],
...
},
...
}
I'm interested in the biggest "num" inside that list of objects "b" inside the object "a".
If the list if of known length I can do it like this:
SELECT
GREATEST((jsn::json->'a'->'b'->>0)::int,
(jsn::json->'a'->'b'->>1)::int,
... ,
(jsn::json->'a'->'b'->>N)::int))
FROM table
Note that I'm new to PostgreSQL (and database querying in general!) so that may be a rubbish way to do it. In any case it works. What I can't figure out is how to make this work when the list, 'b', is of arbitrary and unknown length.
In case it is relevant, I am using PostgreSQL 10 hosted on AWS RDS and running queries using pgAdmin 4.
You need to unnest the array then you can apply a max() on the result:
select max((n.x -> 'num')::int)
from the_table t
cross join jsonb_array_elements(t.jsn::jsonb -> 'a' -> 'b') as n(x);
you probably want to add a group by, so that you can distinguish rom which row the max value came from. Assuming your table has a column id that is unique:
select id, max((n.x -> 'num')::int)
from the_table t
cross join jsonb_array_elements(t.jsn::jsonb -> 'a' -> 'b') as n(x)
group by id;

Query JSONB Array with Sequelize.js

I'm working on a workflow engine of sorts where an admin can build a set of workflow steps, which have conditionals applied to them, and a user can then start a project and have the appropriate steps pulled up.
The conditional object essentially has these key points:
if conditional.isDefault is true, then it applies to all projects
otherwise, conditional will have fieldName, operand, and value fields for numbers, and just fieldName and value fields for string matches (i.e. a state field defined by a select list of US States)
I've got a straight SQL query (below) that works and illustrates what I'm doing, but I'm curious of 2 things:
1) Is it possible to do this with sequelize instead of using raw sql?
2) Is it more performant to have postgres do these ops, or would it be more performant to just get the entire set of workflow steps into my nodejs app and then iterate through the array in javascript?
Here's the SQl query showing what I'm wanting to do with an array of data contained in a single JSONB field (NOTE: I'm not super skilled w/ SQL and especially new to postgres - so this may be all kinds of wrong!):
WITH steps(projId, step) AS (
SELECT
ppr.id AS projId,
jsonb_array_elements(ppr."workflowSteps") AS step
FROM "provider_project_ruleset" AS ppr
)
SELECT ARRAY(SELECT
jsonb(steps.step)
FROM
steps
WHERE
steps.step -> 'conditional' #> '{"isDefault": true}'
OR (step -> 'conditional' ->> 'fieldName' = 'state' AND step -> 'conditional' ->> 'value' = 'TN')
OR (step -> 'conditional' ->> 'fieldName' = 'amount' AND
(CASE step -> 'conditional' ->> 'operand'
WHEN '>=' THEN CASE WHEN (step -> 'conditional' ->> 'value')::bigint <= 10000000000 THEN 0 ELSE 1 END
WHEN '<=' THEN CASE WHEN (step -> 'conditional' ->> 'value')::bigint >= 10000000000 THEN 0 ELSE 1 END
WHEN '=' THEN CASE WHEN (step -> 'conditional' ->> 'value')::bigint = 10000000000 THEN 0 ELSE 1 END
ELSE 1
END) = 0
)
)

PostgreSQL - jsonb_each

I have just started to play around with jsonb on postgres and finding examples hard to find online as it is a relatively new concept.I am trying to use jsonb_each_text to printout a table of keys and values but get a csv's in a single column.
I have the below json saved as as jsonb and using it to test my queries.
{
"lookup_id": "730fca0c-2984-4d5c-8fab-2a9aa2144534",
"service_type": "XXX",
"metadata": "sampledata2",
"matrix": [
{
"payment_selection": "type",
"offer_currencies": [
{
"currency_code": "EUR",
"value": 1220.42
}
]
}
]
}
I can gain access to offer_currencies array with
SELECT element -> 'offer_currencies' -> 0
FROM test t, jsonb_array_elements(t.json -> 'matrix') AS element
WHERE element ->> 'payment_selection' = 'type'
which gives a result of "{"value": 1220.42, "currency_code": "EUR"}", so if i run the below query I get (I have to change " for ')
select * from jsonb_each_text('{"value": 1220.42, "currency_code": "EUR"}')
Key | Value
---------------|----------
"value" | "1220.42"
"currency_code"| "EUR"
So using the above theory I created this query
SELECT jsonb_each_text(data)
FROM (SELECT element -> 'offer_currencies' -> 0 AS data
FROM test t, jsonb_array_elements(t.json -> 'matrix') AS element
WHERE element ->> 'payment_selection' = 'type') AS dummy;
But this prints csv's in one column
record
---------------------
"(value,1220.42)"
"(currency_code,EUR)"
The primary problem here, is that you select the whole row as a column (PostgreSQL allows that). You can fix that with SELECT (jsonb_each_text(data)).* ....
But: don't SELECT set-returning functions, that can often lead to errors (or unexpected results). Instead, use f.ex. LATERAL joins/sub-queries:
select first_currency.*
from test t
, jsonb_array_elements(t.json -> 'matrix') element
, jsonb_each_text(element -> 'offer_currencies' -> 0) first_currency
where element ->> 'payment_selection' = 'type'
Note: function calls in the FROM clause are implicit LATERAL joins (here: CROSS JOINs).
WITH testa AS(
select jsonb_array_elements
(t.json -> 'matrix') -> 'offer_currencies' -> 0 as jsonbcolumn from test t)
SELECT d.key, d.value FROM testa
join jsonb_each_text(testa.jsonbcolumn) d ON true
ORDER BY 1, 2;
tetsa get the temporal jsonb data. Then using lateral join to transform the jsonb data to table format.