Postgres search JSON by dynamic value - sql

In Postgres 14, I'm trying to query a JSON array element:
{
"haystack": [
{ "search": "findthis" },
{ "search": "someothervalue" }
]
}
This works:
SELECT 1
FROM test
WHERE data->'haystack' #> '[{"search":"findthis"}]';
However, when "findthis" comes from a function: getText() or some other dynamic value, I get 0 results:
SELECT 1
FROM test
WHERE data->'haystack' #> to_jsonb('[{"search":"' || getText()::text || '"}]');
(I am expecting to return 1)
My test:
https://dbfiddle.uk/?rdbms=postgres_14&fiddle=ed2615fe9be7a5b0284065f49fddd36f
What am I missing?

Use jsonb, not to_jsonb
select to_jsonb('[{"search":"' || 'findthis' || '"}]'), jsonb('[{"search":"' || 'findthis' || '"}]')
and your query with type cast
SELECT 1
FROM test
WHERE data->'haystack' #> ('[{"search":"' || 'findthis' || '"}]')::jsonb;

Related

How to map object/json array in Snowflake SQL / DBT Macro?

id
some_attribute
json_array
1
"abc"
[ { attr: 'apple'}, { attr: 'banana' } ]
How to get the get rid of attr in json_array so that the table results into something like table below?
id
some_attribute
string_array
1
"abc"
[ 'apple', 'banana' ]
Use case is during the cleaning stage of the data to make further processing and analysis simpler in later stages of the pipeline.
Thx for the help!
One option is to FLATTEN the json array, then construct the string array from the values.
For example
WITH data AS(
SELECT 1 id, 'abc' as some_attribute
, [{ 'attr': 'apple'}, { 'attr': 'banana' } ] as json_array
)
SELECT
id
, some_attribute
, ARRAY_AGG(value:attr::string) WITHIN GROUP( ORDER BY index) as string_array
FROM
data
, TABLE(FLATTEN(input => json_array))
GROUP BY
id
, some_attribute
which returns
ID|SOME_ATTRIBUTE|STRING_ARRAY |
--+--------------+------------------+
1|abc |["apple","banana"]|
Another option is to create a JavaScript UDF. For example
CREATE OR REPLACE FUNCTION ARRAY_JSON_VALUES("a" ARRAY, "attr" STRING)
RETURNS ARRAY
LANGUAGE JAVASCRIPT RETURNS NULL ON NULL INPUT IMMUTABLE
AS
$$
return a.map(e => e[attr]);
$$
then
WITH data AS(
SELECT 1 id, 'abc' as some_attribute, [{ 'attr': 'apple'}, { 'attr': 'banana' } ] as json_array
)
SELECT
id
, some_attribute
, ARRAY_JSON_VALUES(json_array,'attr') as string_array
FROM
data
again returns
ID|SOME_ATTRIBUTE|STRING_ARRAY |
--+--------------+------------------+
1|abc |["apple","banana"]|

PostgreSQL regexp_replace square brackets to other format

I have this column text in a table which contains following string
{
"person": {
"id": "b01d9bf1-998f-4fa8-879a-0f8d0de4b626",
"creationDate": [
2022,
1,
22
],
"modificationDate": [
2022,
1,
27
]
}
}
I have the following regexp_matches query:
select regexp_matches('"creationDate": [2022,1,22], "modificationDate": [2022,1,27],', '\[(.[^)]+)\]', 'g')
but I need to replace
"creationDate": [2022,1,22], "modificationDate": [2022,1,27],
to
"creationDate": "2022-01-22", "modificationDate": "2022-01-27",
I'm not very good working with regular expressions. Also the difficulty is in adding a leading zero to the month as you can see.
Regex-based
A nested regex replacement does the trick:
select regexp_replace(
regexp_replace(
'"creationDate": [2022,1,22], "modificationDate": [2022,1,27],'
, '\[(\d+),(\d+),(\d+)\]'
, '"\1-\2-\3"'
, 'g'
)
, '-(\d)-'
, '-0\1-'
, 'g');
The outer replacement only fires if the month is represented by a single digit.
JSON-based
Dwelling on the comment by #a_horse_with_no_name, the following query operates uses json operators:
select x.key
, (x.value ->> 0) || '-' || LPAD(x.value ->> 1, 2, '0') || '-' || LPAD(x.value ->> 2, 2, '0') mydate
from json_each ( '{"creationDate": [2022,1,22], "modificationDate": [2022,1,27] }'::json ) x
;
The query builds a set of records from a JSON object consisting of a key (the JSON property name) and a value of the native JSON datatype, which happens to be an array. The array elements are extracted, padded with leading zeros where appropriate and concatenated.
See the Postgresql docs for JSON operators and functions for more info.
Full-fledged example
Query to produce a recordset of persons containing their id plus the creation and modification date based on a json array of objects as given in the question.
select id
, ("creationDate" ->> 0) || '-' || LPAD("creationDate" ->> 1, 2, '0') || '-' || LPAD("creationDate" ->> 2, 2, '0') creation_date
, ("modificationDate" ->> 0) || '-' || LPAD("modificationDate" ->> 1, 2, '0') || '-' || LPAD("modificationDate" ->> 2, 2, '0') modification_date
from jsonb_to_recordset (
(
select jsonb_path_query_array ( orig.j, '$.person' ) part
from (
select '[
{ "person": { "id": "b01d9bf1-998f-4fa8-879a-0f8d0de4b626", "creationDate": [2022,1,22], "modificationDate": [2022,1,27] } }
, { "person": { "id": "deadcafe-998f-4fa8-879a-0f8d0de4b626", "creationDate": [2000,1,1], "modificationDate": [2000,12,31] } }
]'::jsonb j
) orig
)
) as x( id varchar(50), "creationDate" json, "modificationDate" json )
;
Available live here (dbfiddle.co.uk).

How to PARSE_JSON from Snowflake Field?

I have a table that looks like:
ID|FIELD1
1|[ { "list": [ {} ] } ]
2|[ { "list": [ { "item": "" } ] } ]
3|[ { "list": [ { "item": "Tag1" }, { "item": "Tag2" } ] } ]
And I want to get all the tags associated to this specific query such that I can just get a list:
Tag1,Tag2
I've tried
SELECT PARSE_JSON(FIELD1[0]['list'][0]['item']) FROM MY_TABLE
WHERE PARSE_JSON(FIELD1[0]['list'][0]) != '{}'
But I get
JSON: garbage in the numeric literal: 65-310 , pos 7
How can I properly unpack these values in SQL?
UPDATE: Clumsy Solution
SELECT LISTAGG(CODES,'\',\'') AS PROMO_CODES
FROM
(SELECT DISTINCT FIELD1[0]['list'][0]['item'] AS CODES FROM MY_TABLE
WHERE FIELD1[0]['list'][0] IS NOT NULL
AND FIELD1[0]['list'][0] != '{}'
AND FIELD1[0]['list'][0]['item'] != ''
)
Please have a look into below knowledge article, if this helps in your case:
https://community.snowflake.com/s/article/Dynamically-extracting-JSON-using-LATERAL-FLATTEN
As I see, the Clumsy Solution does not provide the correct result. It shows only Tag1. So here's my solution:
select LISTAGG( v.VALUE:item, ',' ) from MY_TABLE,
lateral flatten (parse_json(FIELD1[0]):list) v
WHERE v.VALUE:item <> '';
I would recommend to add DISTINCT to prevent duplicate tags in the output:
select LISTAGG( DISTINCT v.VALUE:item, ',' ) from MY_TABLE,
lateral flatten (parse_json(FIELD1[0]):list) v
WHERE v.VALUE:item <> '';
If there are more items in the FIELD1 array (ie 0,1,2), you may use this one:
select LISTAGG( DISTINCT v.VALUE:item, ',' ) from MY_TABLE,
lateral flatten(FIELD1) f,
lateral flatten (parse_json(f.VALUE):list) v
WHERE v.VALUE:item <> '';

Can't add new item to array with value from prev array's item

column data has type jsonb
example:
"availability": [
{
"qty": 31,
"is_available": false,
"store": {
"name": "test_value
}
},
It's contain array availability with one item.
I want to add new item to this array. And get value of node qty from first item. And then add this value to second (new) array's item.
As result availability will contain 2 items. And nodes qty must be equals in both items.
I try this:
WITH subquery AS (
SELECT
id,
data #>>'{availability,0,qty}' as qty
from copy_product
)
UPDATE copy_product
SET
data = (
jsonb_set(data, '{availability}', data -> 'availability' || '{
"qty": subquery.qty,
"is_available": false,
"store": {
"address": null
}}')
)
But I get error:
ERROR: invalid input syntax for type json
LINE 10: ...onb_set(data, '{availability}', data -> 'availability' || '{
^
DETAIL: Token "subquery" is invalid.
CONTEXT: JSON data, line 1: {
"qty": subquery...
SQL state: 22P02
Character: 190
No need for a sub-query (or even a CTE), you can reference the existing value directly as part of the expression passed to jsonb_set()
UPDATE copy_product
SET data = jsonb_set(data,
'{availability}',
data -> 'availability'
|| ('{"qty": '||(data #>>'{availability,0,qty}')||', "is_available": false, "store": {"address": null}}')::jsonb)
Alternatively using jsonb_build_object()
UPDATE copy_product
SET data = jsonb_set(data,
'{availability}',
data -> 'availability' ||
jsonb_build_object(
'qty', data #>>'{availability,0,qty}',
'is_available', false,
'store', jsonb_build_object('address', null)
)

How to pretty format JSON in Oracle?

I wanted to know if there is any way to format a JSON in Oracle (as does this web site example)
In XML I used:
SELECT XMLSERIALIZE(Document XMLTYPE(V_RESPONSE) AS CLOB INDENT SIZE = 2)
INTO V_RESPONSE
FROM DUAL;
And it works very well.
With Oracle 12c, you can use the JSON_QUERY() function with the RETURNING ... PRETTY clause :
PRETTY : Specify PRETTY to pretty-print the return character string by inserting newline characters and indenting
Expression :
JSON_QUERY(js_value, '$' RETURNING VARCHAR2(4000) PRETTY)
Demo on DB Fiddle :
with t as (select '{"a":1, "b": [{"b1":2}, {"b2": "z"}]}' js from dual)
select json_query(js, '$' returning varchar2(4000) pretty) pretty_js, js from t;
Yields :
PRETTY_JS | JS
--------------------------|----------------------------------------
{ | {"a":1, "b": [{"b1":2}, {"b2": "z"}]}
"a" : 1, |
"b" : |
[ |
{ |
"b1" : 2 |
}, |
{ |
"b2" : "z" |
} |
] |
} |
When you're lucky enough to get to Oracle Database 19c, there's another option for pretty printing: JSON_serialize.
This allows you to convert JSON between VARCHAR2/CLOB/BLOB. And includes a PRETTY clause:
with t as (
select '{"a":1, "b": [{"b1":2}, {"b2": "z"}]}' js
from dual
)
select json_serialize (
js returning varchar2 pretty
) pretty_js,
js
from t;
PRETTY_JS JS
{ {"a":1, "b": [{"b1":2}, {"b2": "z"}]}
"a" : 1,
"b" :
[
{
"b1" : 2
},
{
"b2" : "z"
}
]
}