Syntax error passing SQL result to PostgreSQL function accepting array - sql

I tried to pass the result of a SQL query to a function, but I got a syntax error.
contacts=> SELECT count(*) FROM update_name(contact_ids := select array(select id from contact where name is NULL));
ERROR: syntax error at or near "select"
LINE 1: SELECT count(*) FROM update_name(contact_ids := select array...
The subselect returns BIGINTs, and the function accepts an array of BIGINTs. I verified that running the subselect and turning the result into an array of BIGINTs works.
Switching to positional notation did not make a difference. Using an Array constructor did not change anything, either.
Following an intuition, I wrapped the argument in parens:
SELECT count(*) FROM update_name(contact_ids := (select array(select id from contact where name is NULL)));
And that worked. I don't get why. The docs on expressions state that arguments in a function call are expressions. Function calls and Array constructors are expressions, so at least using the Array constructor should have worked.
Why do I need the parens? Where does the necessity come from, i.e. how could I have known?

The expression form you are using is called a Scalar Subquery. The manual says:
A scalar subquery is an ordinary SELECT query in parentheses that
returns exactly one row with one column ... The SELECT query is executed and
the single returned value is used in the surrounding value expression.
Your subquery returns a single value (which happens to be an array, prepared from the result of another subquery).
As a basic rule of thumb, subqueries are always in parenthesis.

Related

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

How to run SQL like EVAL in BigQuery

In BigQuery, I have a query and its result is like:
myQueryValue
select * from 'some path'
I'd like to use it directly in new query.
SELECT someValue
FROM
(
select * from 'some path' <- How can I replace this to myQueryValue?
)
How can I use the result value of some queries like EVAL?
----------------EDITED AT 14th Oct.----------------
Thanks for all answer but I need to explain more what I want.
If I have a 'queryTable' like
col
'select * from tableA'
The result of 'select * from tableA' is
foo
bar
When I only know about 'queryTable', how can I get the this result?
foo
bar
I'd like to refer 'queryTable', and get the final result of its.
You can use sub queries, its a query inside the from clause.
Here is an example code:
SELECT * FROM (SELECT ID FROM CUSTOMERS WHERE SALARY > 4500)
A Subquery or Inner query or a Nested query is a query within another
SQL query and embedded within the WHERE clause.
A subquery is used to return data that will be used in the main query
as a condition to further restrict the data to be retrieved.
Subqueries can be used with the SELECT, INSERT, UPDATE, and DELETE
statements along with the operators like =, <, >, >=, <=, IN, BETWEEN,
etc.
There are a few rules that subqueries must follow −
Subqueries must be enclosed within parentheses.
A subquery can have only one column in the SELECT clause, unless
multiple > >columns are in the main query for the subquery to compare
its selected > >columns.
An ORDER BY command cannot be used in a subquery, although the main
query >can use an ORDER BY. The GROUP BY command can be used to
perform the same >function as the ORDER BY in a subquery.
Subqueries that return more than one row can only be used with
multiple >value operators such as the IN operator.
The SELECT list cannot include any references to values that evaluate
to a >BLOB, ARRAY, CLOB, or NCLOB.
A subquery cannot be immediately enclosed in a set function.
The BETWEEN operator cannot be used with a subquery. However, the
BETWEEN >operator can be used within the subquery.
click here for more information about sub queries.
Below is example of how easy to achieve this
DECLARE myQueryValue STRING;
SET myQueryValue = "select * from your_table";
EXECUTE IMMEDIATE '''
SELECT someValue
FROM ( ''' || myQueryValue || ''' )''';
As you didn't provide additional information I'm going to elaborate my comment.
In comment I've proposed that you can use Declare with Set. Good differences between those two are presented in this stackoverflow thread.
DECLARE does not initialize the variable. When you declare it you declare the variable name, the type, and a default value, which could be an expression.
SET is for initializing the variable you declared previously, and you cannot SET the variable until you DECLARE it.
One of the examples has been provided in #Mikhail Berlyant answer in this thread.
However, more detailed information with more examples are mentioned in GCP Set Reference.
Sets a variable to have the value of the provided expression, or sets multiple variables at the same time based on the result of multiple expressions.
The SET statement may appear anywhere within the body of a script.
This is the easiest way to achieve this.
Another common way you could do this is to use SubQuery/Nested Query, it's also well described in the GCP BigQuery Reference.
In GCP doc you can also find example which uses Set, Declare and subquery:
DECLARE target_word STRING DEFAULT 'methinks';
DECLARE corpus_count, word_count INT64;
SET (corpus_count, word_count) = (
SELECT AS STRUCT COUNT(DISTINCT corpus), SUM(word_count)
FROM `bigquery-public-data`.samples.shakespeare
WHERE LOWER(word) = target_word
);
SELECT
FORMAT('Found %d occurrences of "%s" across %d Shakespeare works',
word_count, target_word, corpus_count) AS result;
Output:
Found 151 occurrences of "methinks" across 38 Shakespeare works

Function which returns type runs multiple times

This is my first question here so sorry if I'm doing something wrong.
I have a function in PostgreSQL which returns a type and I want to display all fields from that type.
At first I was doing the following SQL:
SELECT (FC_FUNCTION(FIELD_A, FIELD_B, FIELD_C)).*
FROM TABLE
But I noticed that it was running way too slow. After checking it looked like it was running the function again for each field the type had. Changing the SQL to the following not only returned the same results, but was way faster:
SELECT (X).*
FROM (SELECT FC_FUNCTION(FIELD_A, FIELD_B, FIELD_C) AS X FROM TABLE) A
Is this the correct way of doing it? It feels to me more of a work around than a solution. Thanks!
This is documented:
[...] these two queries have the same result:
SELECT (myfunc(x)).* FROM some_table;
SELECT (myfunc(x)).a, (myfunc(x)).b, (myfunc(x)).c FROM some_table;
Tip
PostgreSQL handles column expansion by actually transforming the first form into the second. So, in this example, myfunc() would get invoked three times per row with either syntax. If it's an expensive function you may wish to avoid that, which you can do with a query like:
SELECT m.* FROM some_table, LATERAL myfunc(x) AS m;
Placing the function in a LATERAL FROM item keeps it from being invoked more than once per row. m.* is still expanded into m.a, m.b, m.c, but now those variables are just references to the output of the FROM item. (The LATERAL keyword is optional here, but we show it to clarify that the function is getting x from some_table.)

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

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.

How to write a function only used in the where clause that evaluates to true without a comparison operator?

My question is:
In Oracle regexp_like works alone in the where clause without having to compare to 1 or 0 or a string. The function can only be called when evaluating something in a case statement or the where clause. Since it can't be described (tried searching the data dictionary for it) I'm wondering how to write a function that works the same way.
For example:
function is_prod
returns boolean
is
l_var boolean := false;
begin
if sys_context('userenv','db_unique_name') = '"PROD_SERVER"' then
l_var := true;
end if;
return l_var;
end;
That function compiles, but cannot be used in a SQL statement like the following:
select *
from table t
where is_prod
Because I get the following error: ORA-00920: invalid relational operator.
Comparing it to a number or true doesn't work either.
Where can I find the code base for regexp_like or what do I need to do to make this work like regexp_like?
Note: I've looked around for several hours and found that Oracle's regexp functions are actually java calls, but that means they still need a pl/sql wrapper.
Basically, oracle has a boolean datatype only for PLSQL.
So, as long as you stay in plsql you can use them but not in SQL.
From documentation:
Because SQL has no data type equivalent to BOOLEAN, you cannot:
Assign a BOOLEAN value to a database table column
Select or fetch the value of a database table column into a BOOLEAN
variable
Use a BOOLEAN value in a SQL statement, SQL function, or PL/SQL
function invoked from a SQL statement
If you want to find metadata about built-in functions, then maybe this post can help.
SQL Doesn't work like that. the where statement always looks for a function/column where something is. even if the function works you still have to tell the where statement which value you want True or False
I haven't used Oracle SQL, but looking at what you have there I think that if you write it like this
select *
from table t
where is_prod = True
it will work, if you change the Variable type in your function to something like a Varchar(5) or something similar.
you are actually asking that function to look at several records, so when you have it like you do it acts like a Select Statement and not like a where statement. it will give the value of the function but not filter the where.
it will look like a column with true or false values.
When you use the function in a Where statement like this:
SELECT *
FROM table t
WHERE is_Prod
it's like saying:
SELECT *
FROM table t
WHERE Column1
you have to clarify for the WHERE Statement
SELECT *
FROM table t
WHERE Column1 = 'blue' or is_Prod = 'false'
in C# you can use a String as a boolean, if it is null it returns false
in SQL Server it comes out like this
Column2 IS NULL
you still need an operator
* Separator *
as I don't use Oracle I was unable to test this.
http://docs.oracle.com/cd/B14117_01/server.101/b10759/conditions018.htm
REGEXP_LIKE is a like Statement. so it uses a comparison operator.
you could probably write a regexp in a like statement, although I am sure that it is time consuming and monotonous so they made a function that does it for you.
in other words you still have to use the '= whatever' on the function that you created.