Can't parse JSONB scalar as array in Postgres - postgresql

I'm on PostgreSQL 13.2. I have a table with a JSONB column, and it stores JSONs that are a list of objects:
"[{\"MyKey\":\"ValueXYZ\",\"Counter\":0}, {\"MyKey\":\"ValueABC\",\"Counter\":3}]"
When I test this column for a type with jsonb_typeof() like so:
select jsonb_typeof(i.my_column) as col_type
from items i
where i.id = 342
I get string. Which tells me this value is a scalar, and I'm wondering if maybe it wasn't inserted properly.
The error that is bothering me is I am trying to parse the column with something like this:
select jsonb_array_elements(i.my_column)
from items i
and I see the error:
SQL Error [22023]: ERROR: cannot extract elements from a scalar
What is going on? Is there a way to fix this?

Yes, it got inserted wrong. It contains a scalar, which happens to be holding the string representation of a JSON array. The string representation of a JSON array is a different thing than an actual JSON array.
You can extract the string value from the scalar, then cast that string into a jsonb. #>>'{}' will extract the string out of a scalar.
select jsonb_array_elements((i.my_column#>>'{}')::jsonb)
from items i ;
Although you should fundamentally fix the problem, by re-storing the values correctly.
update items set my_column = (my_column#>>'{}')::jsonb where jsonb_typeof(my_column)='string';
But of course you should fix whatever is doing the incorrect insertions first.

Looks like a quoting issue. Consider:
test=> SELECT jsonb_typeof(jsonb '[{"MyKey":"ValueXYZ","Counter":0}, {"MyKey":"ValueABC","Counter":3}]') AS col_type;
col_type
----------
array
(1 row)

Related

Postgres JSONB values are all strings

Somehow populating a database with a JSONB column ended up with every value in the column being a JSONB string instead of an object.
=> select specifications from checklist_item;
specifications
---------------------
"{}"
"{}"
"{\"x\": 2, \"y\": \"z\"}"
Is it possible to update, in a single statement, each of these values to JSONB objects as opposed to strings?
I tried to_jsonb(specifications) but that did not parse as expected. I've gone over documentation but all the examples seem to show ways to manipulate data that is already a jsonb array or a jsonb object but not a plain string.
I can write a script and do the parsing in Python, but there certainly must be a nice way to do with in a single update command with a json function that I simply cannot find at the moment. Is there such a json function or operator that will "parse" my bad data?
to_jsonb(specifications) does to_jsonb(specifications::text), which just gets the JSON text with the string literal as text. What you need is to get the value of the JSON string literal, then cast that to jsonb:
UPDATE checklist_item
SET specifications = (specifications #>> '{}')::jsonb
-- or … = to_jsonb(specifications #>> '{}')
WHERE jsonb_typeof(specifications) = 'string';

Postgres Update Number Values in a JSONB field to be text

I have a table with a JSON column and some of the values in it are numbers but I want all the values to be text. For example, I have {"budget": 500}, but I want it to be {"budget":"500"}. I have tried using the JSONB_SET function but even after postgres returns N rows updated, when I go to retrieve the records, they are still numbers. I was hoping that somebody may have encountered this issue. Here's what I've tried that isn't working.
UPDATE my_table
SET data = JSONB_SET(data, '{budget}', data->'budget'::text)
WHERE data ? 'budget' = true;
Since this is a very large table, hardcoding values is not feasible. If anybody knows why this isn't working or if there is something that does work, please let me know, thank you!
You can enforce converting a JSONB number to text with the function quote_ident():
UPDATE my_table
SET data = jsonb_set(data, '{budget}', quote_ident(data->>'budget')::jsonb)
WHERE data ? 'budget'
-- you can add this condition to avoid updating non-numbers
-- AND jsonb_typeof(data->'budget') = 'number'
Note that data->'budget'::text does nothing as the cast refers to 'budget', not a JSON object and the expression is equivalent to data->'budget'.

PostgreSql Queries treats Int as string datatypes

I store the following rows in my table ('DataScreen') under a JSONB column ('Results')
{"Id":11,"Product":"Google Chrome","Handle":3091,"Description":"Google Chrome"}
{"Id":111,"Product":"Microsoft Sql","Handle":3092,"Description":"Microsoft Sql"}
{"Id":22,"Product":"Microsoft OneNote","Handle":3093,"Description":"Microsoft OneNote"}
{"Id":222,"Product":"Microsoft OneDrive","Handle":3094,"Description":"Microsoft OneDrive"}
Here, In this JSON objects "Id" amd "Handle" are integer properties and other being string properties.
When I query my table like below
Select Results->>'Id' From DataScreen
order by Results->>'Id' ASC
I get the improper results because PostgreSql treats everything as a text column and hence does the ordering according to the text, and not as integer.
Hence it gives the result as
11,111,22,222
instead of
11,22,111,222.
I don't want to use explicit casting to retrieve like below
Select Results->>'Id' From DataScreen order by CAST(Results->>'Id' AS INT) ASC
because I will not be sure of the datatype of the column due to the fact that JSON structure will be dynamic and the keys and values may change next time. and Hence could happen the same with another JSON that has Integer and string keys.
I want something so that Integers in Json structure of JSONB column are treated as integers only and not as texts (string).
How do I write my query so that Id And Handle are retrieved as Integer Values and not as strings , without explicit casting?
I think your assumtions about the id field don't make sense. You said,
(a) Either id contains integers only or
(b) it contains strings and integers.
I'd say,
If (a) then numerical ordering is correct.
If (b) then lexical ordering is correct.
But if (a) for some time and then (b) then the correct order changes, too. And that doesn't make sense. Imagine:
For the current database you expect the order 11,22,111,222. Then you add a row
{"Id":"aa","Product":"Microsoft OneDrive","Handle":3095,"Description":"Microsoft OneDrive"}
and suddenly the correct order of the other rows changes to 11,111,22,222,aa. That sudden change is what bothers me.
So I would either expect a lexical ordering ab intio, or restrict my id field to integers and use explicit casting.
Every other option I can think of is just not practical. You could, for example, create a custom < and > implementation for your id field which results in 11,111,22,222,aa. ("Order all integers by numerical value and all strings by lexical order and put all integers before the strings").
But that is a lot of work (it involves a custom data type, a custom cast function and a custom operator function) and yields some counterintuitive results, e.g. 11,111,22,222,0a,1a,2a,aa (note the position of 0a and so on. They come after 222).
Hope, that helps ;)
If Id always integer you can cast it in select part and just use ORDER BY 1:
select (Results->>'Id')::int From DataScreen order by 1 ASC

Temp Tables Calculating Fields

I am joining two tables and outputting to a csv file. This has worked ok,
But I would like to create a calculated field (an integer field multiplied by a decimal field) and output that as one of the columns.
I am struggling at the moment to calculate the field and store it.
CREATE TEMP-TABLE tth2.
tth2:CREATE-LIKE(buf-woins-hndl).
tth2:ADD-LIKE-FIELD("ttqtyhrs","work_order.est_ltime").
tth2:TEMP-TABLE-PREPARE("ordx2").
bh2 = tth2:DEFAULT-BUFFER-HANDLE.
FOR EACH wo_instr NO-LOCK:
bh2:BUFFER-CREATE.
bh2:BUFFER-COPY(buf-woins-hndl).
ASSIGN bh2:BUFFER-VALUE("ttqtyhrs") = bh2:BUFFER-VALUE ("craft_nbr") *
bh2:BUFFER-VALUE("std_hrs").
END.
I am trying store the result of the calculation in temp table field ttqtyhrs
I get an error message
Invalid datatype for argument to method 'BUFFER-VALUE'. Expecting 'integer' (5442)
when I try to compile.
I would be grateful for any pointers
Andy
Most likely you want to something like this:
ASSIGN
bh2:BUFFER-FIELD("ttqtyhrs"):BUFFER-VALUE() = bh2:BUFFER-FIELD("craft_nbr"):BUFFER-VALUE() * bh2:BUFFER-FIELD("std_hrs"):BUFFER-VALUE().
BUFFER-VALUE takes an integer representing the index if the field is an extent/array. You need to pinpoint the BUFFER-FIELD!

How to find an arbitrary key within a postgres jsonb object?

There are several operators in postgres for getting elements at a certain path in jsonb.
But how could I retrieve all the values that have a key of 'foo', if I don't know where in the whole object structure they will appear?
I saw there is a regex matches function which would return me matching regexes, but the object keyed off foo could be arbitrarily complex, so tough to come up with a regex that would pull the whole object out neatly?
Thanks for your help
SELECT jsonb_column->'foo'
FROM table
[WHERE jsonb_column ? 'foo'] -- ignore values without key "foo"