PostgreSQL fails on empty constructor arrays - postgresql

I have encountered a strange situation with arrays, which looks like a bug in PostgreSQL, unless I'm missing something...
According to PostgreSQL documentation, constructor and string presentations of arrays are interchangeable, i.e. we can either write ARRAY[1,2,3] or '{1,2,3}', which is the same.
However, I have found one case when they are not treated the same.
I am using an automatic SQL generator for multi-row updates that spits out the following:
UPDATE "myTable" AS t SET "data"=v."data"::int[] FROM (VALUES(1, array[]))
AS v("id", "data") WHERE t.id=v.id
The table is as follows:
CREATE TABLE myTable(
id serial PRIMARY KEY,
data int[] NULL
);
Executing that query produces error - cannot determine type of empty array, even though we are clearly casting the column type.
And if I replace array[] with the equivalent '{}', then it suddenly works.
I've never seen this happen before, this is first time, perhaps a unique situation, but from what I see, it goes against PostgreSQL documentation for interchangeable array presentation.
More examples, to explain the issue:
These work:
UPDATE "myTable" AS t SET "data"=v."data"::int[] FROM (VALUES(1, array[1,2,3]))
AS v("id", "data") WHERE t.id=v.id
UPDATE "myTable" AS t SET "data"=v."data"::int[] FROM (VALUES(1, '{1,2,3}'))
AS v("id", "data") WHERE t.id=v.id
UPDATE "myTable" AS t SET "data"=v."data"::int[] FROM (VALUES(1, '{}'))
AS v("id", "data") WHERE t.id=v.id
This one doesn't work:
UPDATE "myTable" AS t SET "data"=v."data"::int[] FROM (VALUES(1, array[]))
AS v("id", "data") WHERE t.id=v.id
So the type casting for array constructor works, for as long as the array isn't empty, just as it is empty, then the type casting stops working.

It is supported with type info:
pokus1=# select array[];
ERROR: cannot determine type of empty array
LINE 1: select array[];
^
HINT: Explicitly cast to the desired type, for example ARRAY[]::integer[].
pokus1=# select array[]::integer[];
array
-------
{}
(1 row)

Related

Updating json columns in psql

I have a table containing a json column. The json values will look something like this:
{'john': 1, 'alex' : 4, 'harry' :2}
If I wanted to add 1 to john, how would I go about doing this?
demo:db<>fiddle
UPDATE mytable -- 6
SET mydata = jsonb_set( -- 4
mydata::jsonb, -- 1
'{john}', -- 2
((mydata ->> 'john')::int + 1)::text::jsonb -- 3
)::json; -- 5
Fetch your data. If it is of type json, cast it into type jsonb
Path to your requested element as text array
Fetch the original value. ->> operator returns type text, so to do an integer operation, you need to cast it into type int. Then add the 1. This result must be reconverted into type jsonb. Unfortunately type int cannot be cast into type jsonb directly, so take the intermediate step via type text
Use jsonb_set() to update the JSON object specified in (1)
If your column is of type json instead of jsonb, cast the result back into type json
Perform the update

Why is my UPDATE query with jsonb popping an error?

I'm trying to update a row in my PostgreSQL database and it's saying it's not finding the x column. the thing is the column pg is trying to find is actually a parameter for the new value in the jsonb_set function, so I'm at my wits end.
It's hard to explain, so I included the query and the error it throws.
Tried adding quotes, double-quotes, brackets, inside and out... didn't work.
UPDATE public.sometable
SET somecolumn = jsonb_set(somecolumn, '{firstKey, secondKey}', someInputString), update_date=NOW(), update_username="someone#somewhere.com"
WHERE id=1
RETURNING *
I'm expecting the value of the row I'm updating to be returned, instead I get:
ERROR: column "someInputString" does not exist
LINE 1: ...n = jsonb_set(somecolumn , '{firstKey, secondKey}', someInputString)...
You have to deliver a valid json value as the third argument of the function:
UPDATE public.sometable
SET
somecolumn = jsonb_set(somecolumn, '{firstKey, secondKey}', '"someInputString"'),
update_date = now(),
update_username = 'someone#somewhere.com'
WHERE id = 1
RETURNING *
Note, I guess update_username is a text, so you should use single quotes for a simple text.
Db<>fiddle.

How to split array in json using json_query?

I've got a column in a table that's a json. It contains only values without keys like
Now I'm trying to split the data from the json and create new table using every index of each array as new entry like
I've already tried
SELECT JSON_QUERY(abc) as 'Type', Id as 'ValueId' from Table FOR JSON AUTO
Is there any way to handle splitting given that some arrays might be empty and look like
[]
?
A fairly simply approach would be to use outer apply with openjson.
First, create and populate sample table (Please save us this step in your future questions):
DECLARE #T AS TABLE
(
Id int,
Value nvarchar(20)
)
INSERT INTO #T VALUES
(1, '[10]'),
(2, '[20, 200]'),
(3, '[]'),
(4, '')
The query:
SELECT Id, JsonValues.Value
FROM #T As t
OUTER APPLY
OPENJSON( Value ) As JsonValues
WHERE ISJSON(t.Value) = 1
Results:
Id Value
1 10
2 20
2 200
3 NULL
Note the ISJSON condition in the where clause will prevent exceptions in case the Value column contains anything other than a valid json (an empty array [] is still considered valid for this purpose).
If you don't want to return a row where the json array is empty, use cross apply instead of outer apply.
Your own code calling for FOR JSON AUTO tries to create JSON out of tabular data. But what you really needs seems to be the opposite direction: You want to transform JSON to a result set, a derived table. This is done by OPENJSON.
Your JSON seems to be a very minimalistic array.
You can try something along this.
DECLARE #json NVARCHAR(MAX) =N'[1,2,3]';
SELECT * FROM OPENJSON(#json);
The result returns the zero-based ordinal position in key, the actual value in value and a (very limited) type-enum.
Hint: If you want to use this against a table's column you must use APPLY, something along
SELECT *
FROM YourTable t
OUTER APPLY OPENJSON(t.TheJsonColumn);

Postgresql - Interpreted type for NULL is wrong

I have the problem with the following CTE expression because prev_count in new_values is being interpreted as text, but the column I'm updating in counts is type integer. I'm getting this error on the marked line:
ERROR: column "prev_count" is of type integer but expression is of type text
LINE 12: prev_count = new_values.prev_count
Here's the query:
WITH
new_values (word,count,txid,prev_count) AS (
VALUES ('cat',1,5,NULL)),
updated AS (
UPDATE
counts t
SET
count = new_values.count,
txid = new_values.txid,
prev_count = new_values.prev_count -- ERROR HERE
FROM
new_values
WHERE (
t.word = new_values.word
)
RETURNING t.*)
INSERT INTO counts(
word,count,txid,prev_count
) SELECT
word,count,txid,prev_count FROM new_values
WHERE NOT EXISTS (
SELECT 1 FROM updated WHERE (updated.word = new_values.word))
My question is, what's an elegant way to fix the error? I would rather specify the type of prev_count in new_values instead of adding an explicit cast, but I don't see anything like that in the docs.
Adding this here as an explicit answer along with a detailed explanation.
The fix is:
WITH
new_values (word,count,txid,prev_count) AS (
VALUES ('cat',1,5,NULL::text)),
As a_horse_with_no_name suggested in the comments.
Why is this necessary? Because the row specification comes from the VALUES section and NULL is unknown. In this case PostgreSQL helpfully casts to text. But that is not what you want so you have to give a type to the NULL.
This often comes up in other cases too, such as UNION statements where a NULL in the first segment in the column list can be given an implicit type which clashes with the type of the column in another segment. So this is a tricky corner worth knowing about.

Postgres query error

I have a query in postgres
insert into c_d (select * from cd where ak = '22019763');
And I get the following error
ERROR: column "region" is of type integer but expression is of type character varying
HINT: You will need to rewrite or cast the expression.
An INSERT INTO table1 SELECT * FROM table2 depends entirely on order of the columns, which is part of the table definition. It will line each column of table1 up with the column of table2 with the same order value, regardless of names.
The problem you have here is whatever column from cd with the same order value as c_d of the table "region" has an incompatible type, and an implicit typecast is not available to clear the confusion.
INSERT INTO SELECT * statements are stylistically bad form unless the two tables are defined, and will forever be defined, exactly the same way. All it takes is for a single extra column to get added to cd, and you'll start getting errors about extraneous extra columns.
If it is at all possible, what I would suggest is explicitly calling out the columns within the SELECT statement. You can call a function to change type within each of the column references (or you could define a new type cast to do this implicitly -- see CREATE CAST), and you can use AS to set the column label to match that of your target column.
If you can't do this for some reason, indicate that in your question.
Check out the PostgreSQL insert documentation. The syntax is:
INSERT INTO table [ ( column [, ...] ) ]
{ DEFAULT VALUES | VALUES ( { expression | DEFAULT } [, ...] ) | query }
which here would look something like:
INSERT INTO c_d (column1, column2...) select * from cd where ak = '22019763'
This is the syntax you want to use when inserting values from one table to another where the column types and order are not exactly the same.