Postgres JSONB values are all strings - postgresql

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';

Related

PostgreSQL open JSONB column with slashes

How is it possible to open the JSONB column where JSONB string with slashes?
"{\"id\":\"c39fe0f5f9b7c89b005bf3491f2a2ce1\",\"token\":\"c39fe0f5f9b7c89b005bf3491f2a2ce1\",\"line_items\":[{\"id\":32968150843480,\"properties\":{},\"quantity\":2,\"variant_id\":32968150843480,\"key\":\"32968150843480:4a6f6b7d19c7aef119af2cd909f429f1\",\"discounted_price\":\"40.00\",\"discounts\":[],\"gift_card\":false,\"grams\":0,\"line_price\":\"80.00\",\"original_line_price\":\"80.00\",\"original_price\":\"40.00\",\"price\":\"40.00\",\"product_id\":4638774493272,\"sku\":\"36457537-mud-yellow-28\",\"taxable\":false,\"title\":\"Knee Length Summer Shorts - Camel / 28\",\"total_discount\":\"0.00\",\"vendor\":\"Other\",\"discounted_price_set\":{\"shop_money\":{\"amount\":\"40.0\",\"currency_code\":\"USD\"},\"presentment_money\":{\"amount\":\"40.0\",\"currency_code\":\"USD\"}},\"line_price_set\":{\"shop_money\":{\"amount\":\"80.0\",\"currency_code\":\"USD\"},\"presentment_money\":{\"amount\":\"80.0\",\"currency_code\":\"USD\"}},\"original_line_price_set\":{\"shop_money\":{\"amount\":\"80.0\",\"currency_code\":\"USD\"},\"presentment_money\":{\"amount\":\"80.0\",\"currency_code\":\"USD\"}},\"price_set\":{\"shop_money\":{\"amount\":\"40.0\",\"currency_code\":\"USD\"},\"presentment_money\":{\"amount\":\"40.0\",\"currency_code\":\"USD\"}},\"total_discount_set\":{\"shop_money\":{\"amount\":\"0.0\",\"currency_code\":\"USD\"},\"presentment_money\":{\"amount\":\"0.0\",\"currency_code\":\"USD\"}}}],\"note\":null,\"updated_at\":\"2022-03-15T13:24:02.787Z\",\"created_at\":\"2022-03-15T13:23:31.912Z\",\"controller\":\"custom_webhooks\",\"action\":\"store_data\",\"custom_webhook\":{\"id\":\"c39fe0f5f9b7c89b005bf3491f2a2ce1\",\"token\":\"c39fe0f5f9b7c89b005bf3491f2a2ce1\",\"line_items\":[{\"id\":32968150843480,\"properties\":{},\"quantity\":2,\"variant_id\":32968150843480,\"key\":\"32968150843480:4a6f6b7d19c7aef119af2cd909f429f1\",\"discounted_price\":\"40.00\",\"discounts\":[],\"gift_card\":false,\"grams\":0,\"line_price\":\"80.00\",\"original_line_price\":\"80.00\",\"original_price\":\"40.00\",\"price\":\"40.00\",\"product_id\":4638774493272,\"sku\":\"36457537-mud-yellow-28\",\"taxable\":false,\"title\":\"Knee Length Summer Shorts - Camel / 28\",\"total_discount\":\"0.00\",\"vendor\":\"Other\",\"discounted_price_set\":{\"shop_money\":{\"amount\":\"40.0\",\"currency_code\":\"USD\"},\"presentment_money\":{\"amount\":\"40.0\",\"currency_code\":\"USD\"}},\"line_price_set\":{\"shop_money\":{\"amount\":\"80.0\",\"currency_code\":\"USD\"},\"presentment_money\":{\"amount\":\"80.0\",\"currency_code\":\"USD\"}},\"original_line_price_set\":{\"shop_money\":{\"amount\":\"80.0\",\"currency_code\":\"USD\"},\"presentment_money\":{\"amount\":\"80.0\",\"currency_code\":\"USD\"}},\"price_set\":{\"shop_money\":{\"amount\":\"40.0\",\"currency_code\":\"USD\"},\"presentment_money\":{\"amount\":\"40.0\",\"currency_code\":\"USD\"}},\"total_discount_set\":{\"shop_money\":{\"amount\":\"0.0\",\"currency_code\":\"USD\"},\"presentment_money\":{\"amount\":\"0.0\",\"currency_code\":\"USD\"}}}],\"note\":null,\"updated_at\":\"2022-03-15T13:24:02.787Z\",\"created_at\":\"2022-03-15T13:23:31.912Z\"}}"
this real JSONB column
I can not find any example of how to deal with this type of JSONB
Whatever is inserting your data is screwing it up. It is taking the string representation of a JSON object and stuffing that into a JSON string scalar. You need to fix that or it will just keep happening.
To fix what is already there, you need to extract the real PostgreSQL string out of the JSON string, then cast that to JSONB. Extracting a JSON string can be done unintuitively with #>>'{}', or even less intuitively with ->>0.
select (data#>>'{}')::jsonb from table_name.
Of course you should fix it permanently, not just do it on the fly all the time, which is both slow and confusing.
update table_name set data=(data#>>'{}')::jsonb;
Of course fixing the tool which screws this up in the first place, and fixing the historical data, need to be done in a coordinated fashion or you will have a glorious mess on your hands.
I think you have wrong formatted string in jsonb field. You can try fix it in next way:
select trim(both '"' from replace(data::varchar, '\"', '"'))::jsonb data from tbl;
PostgreSQL JSONB online

Can't parse JSONB scalar as array in Postgres

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)

Postgres with json column

I have a column in Postgres database which has a json
{"predict":[{"method":"A","val":1.2},{"method":"B","val":1.7}]}
I would like to extract both val as a separate column. Is there a way I could do this from within Postgres?
Postgres introduced JSON types plus functions and operators in 9.2. If your column is a JSON type you can use them to do your extraction.
If the json always has the structure you indicate (i.e. a key "predict" that holds an array with two JSON objects each having a "method" and a "val" key) then the solution is simply:
SELECT ((my_json->'predict')->>0)->'val' AS method_a,
((my_json->'predict')->>1)->'val' AS method_b
FROM my_table;
If the structure can vary then you'd have to tell us more about it to provide you with a solution.

Convert varchar parameter with CSV into column values postgres

I have a postgres query with one input parameter of type varchar.
value of that parameter is used in where clause.
Till now only single value was sent to query but now we need to send multiple values such that they can be used with IN clause.
Earlier
value='abc'.
where data=value.//current usage
now
value='abc,def,ghk'.
where data in (value)//intended usage
I tried many ways i.e. providing value as
value='abc','def','ghk'
Or
value="abc","def","ghk" etc.
But none is working and query is not returning any result though there are some matching data available. If I provide the values directly in IN clause, I am seeing the data.
I think I should somehow split the parameter which is comma separated string into multiple values, but I am not sure how I can do that.
Please note its Postgres DB.
You can try to split input string into an array. Something like that:
where data = ANY(string_to_array('abc,def,ghk',','))

Storing a timestamp as a default value in hstore

I am trying to store the current timestamp as a default value in an hstore. I tried using now() but all that is stored is "last_collected"=>"now()". Here is what I have:
'"level"=>"1", "last_collected"=>now()'::hstore
Is there a correct way to do this or is it even possible? Thanks!
Using one of the hstore constructor functions would probably be easier:
hstore(text[]): construct an hstore from an array, which may be either a key/value array, or a two-dimensional array.
hstore(text[], text[]): construct an hstore from separate key and value arrays.
So you could use one of these:
hstore(array['level', '1', 'last_collected', now()::text])
hstore(array[array['level', '1'], array['last_collected', now()::text]])
hstore(array['level', 'last_collected'], array['1', now()::text])
Keep in mind that hstore uses text for both the keys and values so your timestamp will be a string. You might want to use to_char instead of ::text to convert your timestamp to a string, that will give you more control over the precision and format.
hstore(ARRAY['level', '1', 'last_collected', to_char(CURRENT_TIMESTAMP, 'HH12:MI:SS')]);