Fetching records which have either of two values in an array column in postgres - sql

I have an array_agg column in postgresql which has values like these:
"{Metabolic/Endocrinology}"
"{Cardiovascular}"
"{Oncology}"
"{Autoimmune/Inflammation}"
Basically a string variable being array_agg by an id.
Now I want to fetch all records from this table where either of Oncology or Autoimmune/Inflammation is present.
I am doing something like this but I am not sure why it is throwing an error.
select * from Table where id = ANY('{Oncology,Autoimmune/Inflammation}')
It throws the following error.
ERROR: operator does not exist: text[] = text
SQL state: 42883
Hint: No operator matches the given name and argument type(s). You may need to add explicit type casts.
Character: 67
Please note I have also used ::TEXT [] and it still gives an error.

You want to use the array-overlaps operator &&.
See array operators.
e.g.
select * from (
VALUES
(ARRAY['Oncology','Pediatrics']),
(ARRAY['Autoimmune/Inflammation','Oncology']),
(ARRAY['Autoimmune/Inflammation']),
(ARRAY['Pediatrics']),
(ARRAY[]::text[])
) "Table"(id)
where id && ARRAY['Oncology','Autoimmune/Inflammation'];
By the way, I suggest using the SQL-standard ARRAY[...] constructor where possible.
Also, it's almost certainly a terrible idea to have an id column (presumably a primary key, if not, the name is confusing) defined as an array type.

Related

Invalid token error when using jsonb_insert in postgresql

As a little bit of background. I want to fill a column with jsonb values using values from other columns. Initially, I used this query:
UPDATE myTable
SET column_name =
row_to_json(rowset)
FROM (SELECT column1, column2 FROM myTable)rowset
However, this query seems to run for way too long (a few hours before I stopped it) on a dataset with 9 million records. So I looking for a solution with the second FROM clause and found the jsonb_insert function. To test the query I first ran this sample query:
SELECT jsonb_insert('{}','{column1}','500000')
Which gives {'column1':500000} as output. Perfect, so I tried to fill the value using the actual column:
SELECT jsonb_insert('{}':,'{column1}',column1) FROM myTable WHERE id = <test_id>
This gives a syntax error and a suggestion to add argument types, which leads me to the following:
SELECT jsonb_insert('{}':,'{column1}','column1')
FROM myTable WHERE id = <test_id>
SELECT jsonb_insert('{}'::jsonb,'{column1}'::jsonb,column1::numeric(8,0))
FROM myTable WHERE id = <test_id>
Both these queries give invalid input type syntax error, with Token 'column1' is invalid.
I really can not seem to find the correct syntax for these queries using documentation. Does anyone know what the correct syntax would be?
Because jsonb_insert function might need to use jsonb type for the new_value parameter
jsonb_insert(target jsonb, path text[], new_value jsonb [, insert_after boolean])
if we want to get number type of JSON, we can try to cast the column as string type before cast jsonb
if we want to get a string type of JSON, we can try to use concat function with double-quotes sign.
CREATE TABLE myTable (column1 varchar(50),column2 int);
INSERT INTO myTable VALUES('column1',50000);
SELECT jsonb_insert('{}','{column1}',concat('"',column1,'"')::jsonb) as JsonStringType,
jsonb_insert('{}','{column2}',coalesce(column2::TEXT,'null')::jsonb) as JsonNumberType
FROM myTable
sqlfiddle
Note
if our column value might be null we can try to put 'null' for coalesce function as coalesce(column2::TEXT,'null').

Dynamically cast type of array elements to match some expression type in PostgreSQL query

I want to use array_position function in PostgreSQL (which takes array of some type and expression or value of the same type) for constructing query that returns rows in some arbitrary order (additional context: I want to enhance Ruby on Rails in_order_of feature which is currently implemented via unreadable CASE statement):
SELECT id, title, type
FROM posts
ORDER BY
array_position(ARRAY['SuperSpecial','Special','Ordinary']::varchar[], type),
published_at DESC;
The problem here is that requirement to do explicit type casting from type inferred by PostgreSQL from array literal (ARRAY['doh'] is text[]) to type of expression (type is varchar here). While varchar and text are coercible to each other, PostgreSQL requires explicit type cast, otherwise if omit it (like in array_position(ARRAY['doh'], type)) PostgreSQL will throw error (see this answer for details):
ERROR: function array_position(text[], character varying) does not exist
HINT: No function matches the given name and argument types. You might need to add explicit type casts.
While it is not a problem to specify explicit type cast in some static queries, it is problem in autogenerated queries when type of expression is unknown beforehand: array_position(ARRAY[1,2,3], id * 2) (what type has id * 2?)
I thought that pg_typeof() could help me, but it seems that it can't be used neither in :: operator nor in CAST operator (I've seen information that both forms aren't function forms, but syntax constructs, see this question for details):
SELECT id, title, type
FROM posts
ORDER BY array_position(CAST(ARRAY['SpecialPost','Post','Whatever'] AS pg_typeof(type)), type), id;
ERROR: type "pg_typeof" does not exist
LINE 1: ...on(CAST(ARRAY['SpecialPost','Post','Whatever'] AS pg_typeof(...
Question:
How to do dynamic typecast to expression type (say, to type of "posts"."id" * 2) in the same SQL query?
I would prefer to avoid extra roundtrip to database server (like executing SELECT pg_typeof("id" * 2) FROM "posts" LIMIT 1 and then using its result in generating of a new query) or writing some custom functions. Is it possible?
Better query
I want to enhance Ruby on Rails in_order_of feature which is currently implemented via unreadable CASE statement:
For starters, neither the awkward CASE construct nor array_position() are ideal solutions.
SELECT id, title, type
FROM posts
ORDER BY
array_position(ARRAY['SuperSpecial','Special','Ordinary']::varchar[], type),
published_at DESC;
There is a superior solution in Postgres:
SELECT id, title, type
FROM posts
LEFT JOIN unnest(ARRAY['SuperSpecial','Special','Ordinary']::varchar[]) WITH ORDINALITY o(type, ord) USING (type)
ORDER BY o.ord, published_at DESC;
This avoids calling the function array_position() for every row and is cheaper.
Equivalent short syntax with array literal and implicit column name:
SELECT id, title, type
FROM posts
LEFT JOIN unnest('{SuperSpecial,Special,Ordinary}'::varchar[]) WITH ORDINALITY type USING (type)
ORDER BY ordinality, published_at DESC;
db<>fiddle here
Added "benefit": it works with type-mismatch in Postgres 13 - as long as array type and column type are compatible.
The only possible caveat I can think of: If the passed array has duplicate elements, joined rows are duplicated accordingly. That wouldn't happen with array_position(). But duplicates would be nonsense for the expressed purpose in any case. Make sure to pass unique array elements.
See:
ORDER BY the IN value list
PostgreSQL unnest() with element number
Improved functionality in Postgres 14
The error you report is going away with Postgres 14:
ERROR: function array_position(text[], character varying) does not exist
HINT: No function matches the given name and argument types. You might need to add explicit type casts.
Quoting the release notes:
Allow some array functions to operate on a mix of compatible data
types (Tom Lane)
The functions array_append(), array_prepend(), array_cat(),
array_position(), array_positions(), array_remove(),
array_replace(), and width_bucket() now take anycompatiblearray
instead of anyarray arguments. This makes them less fussy about
exact matches of argument types.
And the manual on anycompatiblearray:
Indicates that a function accepts any array data type, with automatic promotion of multiple arguments to a common data type
So, while this raises the above error msg in Postgres 13:
SELECT array_position(ARRAY['a','b','c']::text[], 'd'::varchar);
.. the same just works in Postgres 14.
(Your query and error msg show flipped positions for text and varchar, but all the same.)
To be clear, calls with compatible types now just work, incompatible types still raise an exception:
SELECT array_position('{1,2,3}'::text[], 3);
(The numeric literal 3 defaults to type integer, which is incompatible with text.)
Answer to actual question
.. which may be irrelevant by now. But as proof of concept:
CREATE OR REPLACE FUNCTION posts_order_by(_order anyarray)
RETURNS SETOF posts
LANGUAGE plpgsql AS
$func$
BEGIN
RETURN QUERY EXECUTE format (
$$
SELECT p.*
FROM posts p
LEFT JOIN unnest($1) WITH ORDINALITY o(type, ord) ON (o.type::%s = p.type)
ORDER BY o.ord, published_at DESC
$$
, (SELECT atttypid::regtype
FROM pg_attribute
WHERE attrelid = 'posts'::regclass
AND attname = 'type')
)
USING _order;
END
$func$;
db<>fiddle here
Doesn't make a whole lot of sense, as the type of posts.id should be well-known at the time of writing the function, but there may be special cases ...
Now both of these calls work:
SELECT * FROM posts_order_by('{SuperSpecial,Special,Ordinary}'::varchar[]);
SELECT * FROM posts_order_by('{1,2,3}'::int[]);
Though the second typically doesn't make sense.
Related, with links to more:
Executing queries dynamically in PL/pgSQL

Avoid PostgreSQL type evaluation in CASE statements

I want to select a column key value if this column is a jsonb.
If it's not a jsonb, I want to simply select the column.
Here is the code:
SELECT
CASE WHEN (pg_typeof(mycolumn)::text LIKE 'jsonb')
THEN mycolumn->>'mykey'
ELSE mycolumn
END
FROM mytable;
It does not work, because somehow the mycolumn->>'mykey' is still evaluated for columns that are not of type JSONB.
ERROR: operator does not exist: [...] ->> unknown
HINT: No operator matches the given name and argument type(s). You might need to add explicit type casts.
Is there a way to make PostgreSQL type check accept this statement, or is there any other way to do this?
In general you can do this by casting the query result to text:
SELECT
CASE WHEN (pg_typeof(mycolumn)::text LIKE 'jsonb')
THEN (mycolumn::text::jsonb)->>'mykey'
ELSE mycolumn::text
END
FROM mytable;

Finding a single element in postgres json array

I have a table that has a Json typed column and I am trying query that table based of values that are contained in that json column.
Here is the relevant DDL:
create table user
(
roles json
);
Here is what a roles value would look like:
[70,254]
I want to be able to perform a query like this:
select * from user where roles in(254);
So I've tried this:
SELECT * from apptree.atf_app_user where roles #> 254;
And it is giving me this error:
[2017-12-01 14:58:16] [42883] ERROR: operator does not exist: json #> integer
[2017-12-01 14:58:16] Hint: No operator matches the given name and argument type(s). You might need to add explicit type casts.
[2017-12-01 14:58:16] Position: 48
The operator #> works on jsonb arguments. Cast the column to jsonb:
select *
from apptree.atf_app_user
where roles::jsonb #> '254';
Update. There is no operator to search in a json array for any of multiple values. You can use the function json_array_elements_text(), e.g.:
select t.*
from apptree.atf_app_user t
cross join json_array_elements_text(roles)
where value in ('70', '71');

Postgresql function returns composite - how do I access composite values as separate columns?

I have a Postgresql function which returns a composite type defined as (location TEXT, id INT). When I run "SELECT myfunc()", My output is a single column of type text, formatted as:
("locationdata", myid)
This is pretty awful. Is there a way to select my composite so that I get 2 columns back - a TEXT column, and an INT column?
Use:
SELECT *
FROM myfunc()
You can read more about the functionality in this article.
Answer has already been accepted, but I thought I'd throw this in:
It may help to think about the type of the data and where those types fit into an overall query. SQL queries can return essentially three types:
A single scalar value
A list of values
A table of values
(Of course, a list is just a one-column table, and a scalar is just a one-value list.)
When you look at the types, you see that an SQL SELECT query has the following template:
SELECT scalar(s)
FROM table
WHERE boolean-scalar
If your function or subquery is returning a table, it belongs in the FROM clause. If it returns a list, it could go in the FROM clause or it could be used with the IN operator as part of the WHERE clause. If it returns a scalar, it can go in the SELECT clause, the FROM clause, or in a boolean predicate in the WHERE clause.
That's an incomplete view of SELECT queries, but I've found it helps to figure out where my subqueries should go.