I am trying to call a function within a function using sql on postgres 9.3.
This question is related to another post by me.
I have written the below function. So far I have failed to incorporate any kind of save-output (COPY) statement, so I am trying to work around this by creating a nested function print-out function.
CREATE FUNCTION retrieve_info(TEXT, TEXT) RETURNS SETOF
retrieve_info_tbl AS $$
SELECT tblA.id, tblA.method, tblA.species, tblA.location
FROM tblA
WHERE method=$1 AND species=$2
GROUP BY id, method, species
ORDER BY location
$$ LANGUAGE 'sql';
The above function works.
An attempt to create a nested function.
CREATE FUNCTION print_out(TEXT, TEXT) RETURNS void AS $$
COPY (SELECT * FROM retrieve_info($1, $2)) TO 'myfilepath/test.csv'
WITH CSV HEADER;
$$ LANGUAGE 'sql';
Calling nested function.
SELECT * FROM print_out('mtd1','sp1');
OUTPUT
The above gives this ERROR: column "$1" does not exist SQL state: 42P02 Context: SQL function "print_out" statement 1. However, when substituting the arg1, arg2 in print_out() with 'mtd1','sp1' the correct output is printed to test.csv (as seen below)
id | method | ind | location
----------------------------
1a | mtd1 | sp3 | locA
1d | mtd1 | sp3 | locB
How would I get the arg1, arg2 of retrieve_info() to call arg1, arg2 properly within print_out()?
I am completely stuck. Would appreciate any pointers, thanks
COPY is a bit odd as it sort of treats its query argument as a string even though it isn't written as a string. The result is that the query:
SELECT * FROM retrieve_info($1, $2)
isn't executed in the context of the function, it is executed in the context of COPY itself. Even though you say:
copy (select * from t) ...
it is treated more as though you wrote:
copy 'select * from t' ...
so by the time the query is executed, the function parameters no longer have any meaning, the query argument to COPY may look like it would behave like a closure in other languages but it doesn't, it acts more like a string that gets passed to eval.
You can get around this strangeness by using the usual Kludge of Last Resort: dynamic SQL. You should get better results if you write your function to use string wrangling and EXECUTE:
create or replace function print_out(text, text) returns void as $$
begin
execute 'copy ('
|| 'select * from retrieve_info'
|| '(' || quote_literal($1) || ',' || quote_literal($2) || ')'
|| ') to ''myfilepath/test.csv'' with csv header;';
end;
$$ language plpgsql;
Are x and y quoted intentionally?
COPY (SELECT * FROM retrieve_info('x','y')) TO 'myfilepath/test.csv'
You are not sending the x and y arguments of print_out to retrieve_info - rather, you are sending the strings 'x' and 'y'. Assuming you don't have records with method='x' AND species='y', it's of little wonder you get no results.
Try this instead:
COPY (SELECT * FROM retrieve_info(x,y)) TO 'myfilepath/test.csv'
Related
I was trying to create a Snowflake SQL UDF
Where it computes the Values of the all values and will return the result to the user.
So firstly, i have tried the following approach
# The UDF that Returns the Result.
CREATE OR REPLACE FUNCTION PRODUCT_OF_COL_VAL()
RETURNS FLOAT
LANGUAGE SQL
AS
$$
SELECT EXP(SUM(LN(COL))) AS RESULT FROM SCHEMA.SAMPLE_TABLE
$$
The above code executes perfectly fine....
if you could see above (i have hardcoded the TABLE_NAME and COLUMN_VALUE) which is not i acutally want..
So, i have tried the following approach, by passing the column name dynamically..
create or replace function (COL VARCHAR)
RETURNS FLOAT
LANGUAGE SQL
AS
$$
SELECT EXP(SUM(LN(COL))) AS RESULT from SCHEMA.SAMPLE_TABLE
$$
But it throws the following issue...
Numeric Value 'Col' is not recognized
To elaborate more the Data type of the Column that i am passing is NUMBER(38,6)
and in the background its doing the following work..
EXP(SUM(LN(TO_DOUBLE(COL))))
Does anyone have any idea why this is running fine in Scenario 1 and not in Scenario 2 ?
Hopefully we will be able to have this kind of UDFs one day, in the meantime consider this answer using ARRAY_AGG() and a Python UDF:
Sample usage:
select count(*) how_many, multimy(array_agg(score)) multiplied, tags[0] tag
from stack_questions
where score > 0
group by tag
limit 100
The UDF in Python - which also protects against numbers beyond float's limits:
create or replace function multimy (x array)
returns float
language python
handler = 'x'
runtime_version = '3.8'
as
$$
import math
def x(x):
res = math.prod(x)
return res if math.log10(res)<308 else 'NaN'
$$
;
The parameter you defined in SQL UDF will be evaluated as a literal:
When you call the function like PRODUCT_OF_COL_VAL('Col'), the SQL statement you execute becomes:
SELECT EXP(SUM(LN('Col'))) AS RESULT from SCHEMA.SAMPLE_TABLE
What you want to do is to generate a new SQL based on parameters, and it's only possible using "stored procedures". Check this one:
Dynamic SQL in a Snowflake SQL Stored Procedure
I'm using PostgreSQL 9.2.10
Suppose you have 2 PostgreSQL functions, 'called_function' and 'caller_function', second one is calling the first one. Exactly:
CREATE FUNCTION called_function () RETURNS varchar AS
$BODY$
BEGIN
RETURN 'something';
END;
CREATE FUNCTION caller_function () RETURNS varchar AS
$BODY$
BEGIN
RETURN called_function ();
END;
Now, using SQL and knowing only function name, I would like to find out if 'caller_function' calls some other function. Or if 'called_function' is called by some other function. Is it possible?
I tried to get function's OID (let's say it is '123') and then I looked into pg_depend table:
SELECT * FROM pg_catalog.pg_depend dep WHERE dep.objid = 123 OR dep.objsubid = 123 OR dep.refobjid = 123 OR dep.refobjsubid = 123 OR dep.refobjsubid = 123;
But it finds only pg_language and pg_namespace dependency. Nothing more.
I had same problem to define a function and because of dependency It didn't work. Then I solved my problem with adding this command before the commands
SET check_function_bodies = false;
hope to help someone else
Look at the table pg_proc for example :
select nspname,proname,prosrc from pg_proc join pg_namespace nsp on (pronamespace=nsp.oid) where prosrc like '%called_function%'
Impossible in the general case; but a limited (restricted-domain) solution is perfectly doable --- and might prove adequate for your needs.
(The Most Obvious of the Many) Limitations
Fails (false negative) if name of callee (a function to be invoked) is specified as a quoted identifier.
Fails (false negative) if name of callee is passed as argument.
Fails (false negative) if name of callee is read from a relation at runtime.
Fails (false negative) if name of callee is assembled from tokens.
Fails (false positive) if name of callee is present just as literal.
Fails (false positive) if name of callee is present in a multi-line comment.
Does not account for function overloading.
Does not account for functions invoked via triggers.
Does not account for functions invoked per query-rewrite rules.
Does not account for effects of query rewriting rules.
Knows nothing about functions written in non-interpreted PLs like C.
Sample Output
Your routine... | ...calls these routines:
---------------------------------+-------------------------------------------------
create_silo_indexes | {get_config__f_l__ea_silo,subst_silo_id}
demux__id_creat_thread | {}
grow__sensor_thhourly | {containing_hhour_t_begin}
SQL
WITH routine_names AS (
SELECT DISTINCT(Lower(proname)) AS name --#0
FROM pg_proc
WHERE proowner = To_Regrole(current_role)
)
SELECT
name AS "Your routine...",
Array_Remove( --#8
Array( --#7
SELECT Unnest( --#5
String_To_Array( --#4
Regexp_Replace( --#3
Regexp_Replace( --#2
Lower(PG_Get_Functiondef(To_Regproc(name))) --#1
, '--.*?\n', '', 'g'
)
, '\W+', ' ', 'g'
)
, ' '
)
)
INTERSECT --#6
SELECT name FROM routine_names
ORDER BY 1
)
, name
) AS "...calls these routines:"
FROM
routine_names;
How It Works
#0 Collect names of all the routines which could be callers and/or callees. We cannot handle overloaded functions correctly anyway, so just DISTINCT to save trouble later on; SQL is case-insensitive apart from quoted identifiers which we are not bothering with anyway, so we just Lower() to simplify comparison later.
#1 PG_Get_Functiondef() fetches complete text of the CREATE FUNCTION or CREATE PROCEDURE command. Again, Lower().
#2 Strip single-line comments. Note the lazy (non-greedy) *? quantifier: the usual * quantifier, if used here, would remove the first single-line comment plus all subsequent lines!
#3 Replace all characters other than letters and digits and _, with a space. Note the + quantifier: it ensures that 2+ contiguous removed characters are replaced by just 1 space.
#4 Split by spaces into an array; this array contains bits of SQL syntax, literals, numbers, and identifiers including routine names.
#5 Unnest the array into a rowset.
#6 INTERSECT with routine names; result will consist of routine names only.
#7 Convert rowset into an array.
#8 Since input was complete text of a CREATE FUNCTION f ... command, extracted routine names will obviously contain f itself; so we remove it with Array_Remove().
(SQL tested with PostgreSQL 12.1)
I want to call a function by passing multiple values on single parameter, like this:
SELECT * FROM jobTitle('270,378');
Here is my function.
CREATE OR REPLACE FUNCTION test(int)
RETURNS TABLE (job_id int, job_reference int, job_job_title text
, job_status text) AS
$$
BEGIN
RETURN QUERY
select jobs.id,jobs.reference, jobs.job_title,
ltrim(substring(jobs.status,3,char_length(jobs.status))) as status
FROM jobs ,company c
WHERE jobs."DeleteFlag" = '0'
and c.id= jobs.id and c.DeleteFlag = '0' and c.active = '1'
and (jobs.id = $1 or -1 = $1)
order by jobs.job_title;
END;
$$ LANGUAGE plpgsql;
Can someone help with the syntax? Or even provide sample code?
VARIADIC
Like #mu provided, VARIADIC is your friend. One more important detail:
You can also call a function using a VARIADIC parameter with an array type directly. Add the key word VARIADIC in the function call:
SELECT * FROM f_test(VARIADIC '{1, 2, 3}'::int[]);
is equivalent to:
SELECT * FROM f_test(1, 2, 3);
Other advice
In Postgres 9.1 or later right() with a negative length is faster and simpler to trim leading characters from a string:
right(j.status, -2)
is equivalent to:
substring(j.status, 3, char_length(jobs.status))
You have j."DeleteFlag" as well as j.DeleteFlag (without double quotes) in your query. This is probably incorrect. See:
PostgreSQL Error: Relation already exists
"DeleteFlag" = '0' indicates another problem. Unlike other RDBMS, Postgres properly supports the boolean data type. If the flag holds boolean data (true / false / NULL) use the boolean type. A character type like text would be inappropriate / inefficient.
Proper function
You don't need PL/pgSQL here. You can use a simpler SQL function:
CREATE OR REPLACE FUNCTION f_test(VARIADIC int[])
RETURNS TABLE (id int, reference int, job_title text, status text)
LANGUAGE sql AS
$func$
SELECT j.id, j.reference, j.job_title
, ltrim(right(j.status, -2)) AS status
FROM company c
JOIN job j USING (id)
WHERE c.active
AND NOT c.delete_flag
AND NOT j.delete_flag
AND (j.id = ANY($1) OR '{-1}'::int[] = $1)
ORDER BY j.job_title
$func$;
db<>fiddle here
Old sqlfiddle
Don't do strange and horrible things like converting a list of integers to a CSV string, this:
jobTitle('270,378')
is not what you want. You want to say things like this:
jobTitle(270, 378)
jobTitle(array[270, 378])
If you're going to be calling jobTitle by hand then a variadic function would probably be easiest to work with:
create or replace function jobTitle(variadic int[])
returns table (...) as $$
-- $1 will be an array if integers in here so UNNEST, IN, ANY, ... as needed
Then you can jobTitle(6), jobTitle(6, 11), jobTitle(6, 11, 23, 42), ... as needed.
If you're going to be building the jobTitle arguments in SQL then the explicit-array version would probably be easier to work with:
create or replace function jobTitle(int[])
returns table (...) as $$
-- $1 will be an array if integers in here so UNNEST, IN, ANY, ... as needed
Then you could jobTitle(array[6]), jobTitle(array[6, 11]), ... as needed and you could use all the usual array operators and functions to build argument lists for jobTitle.
I'll leave the function's internals as an exercise for the reader.
When I want to test the behavior of some PostgreSQL function FOO() I'd find it useful to execute a query like SELECT FOO(bar), bar being some data I use as a direct input without having to SELECT from a real table.
I read we can omit the FROM clause in a statement like SELECT 1 but I don't know the correct syntax for multiple inputs. I tried SELECT AVG(1, 2) for instance and it does not work.
How can I do that ?
With PostgreSQL you can use a VALUES expression to generate an inlined table:
VALUES computes a row value or set of row values specified by value expressions. It is most commonly used to generate a "constant table" within a larger command, but it can be used on its own.
Emphasis mine. Then you can apply your aggregate function to that "constant table":
select avg(x)
from (
values (1.0), (2.0)
) as t(x)
Or just select expr if expr is not an aggregate function:
select sin(1);
You could also define your own avg function that operates on an array and hide your FROM inside the function:
create function avg(double precision[]) returns double precision as $$
select avg(x) from unnest($1) as t(x);
$$ language 'sql';
And then:
=> select avg(array[1.0, 2.0, 3.0, 4.0]);
avg
-----
2.5
But that's just getting silly unless you're doing this quite often.
Also, if you're using 8.4+, you can write variadic functions and do away with the array. The internals are the same as the array version, you just add variadic to the argument list:
create function avg(variadic double precision[]) returns double precision as $$
select avg(x) from unnest($1) as t(x);
$$ language 'sql';
And then call it without the array stuff:
=> select avg(1.0, 1.2, 2.18, 11, 3.1415927);
avg
------------
3.70431854
(1 row)
Thanks to depesz for the round-about-through-google pointer to variadic function support in PostgreSQL.
To express a SET in most varieties of SQL, you need to actually express a table..
SELECT
AVG(inlineTable.val)
FROM
(
SELECT 1 AS Val
UNION ALL
SELECT 2 AS Val
)
AS inLineTable
I have the following function, based on the SQL Functions Returning Sets section of the PG docs, which accepts two arrays of equal length, and unpacks them into a set of rows with two columns.
CREATE OR REPLACE FUNCTION unpack_test(
in_int INTEGER[],
in_double DOUBLE PRECISION[],
OUT out_int INTEGER,
OUT out_double DOUBLE PRECISION
) RETURNS SETOF RECORD AS $$
SELECT $1[rowx] AS out_int, $2[rowx] AS out_double
FROM generate_series(1, array_upper($1, 1)) AS rowx;
$$ LANGUAGE SQL STABLE;
I execute the function in PGAdmin3, like this:
SELECT unpack_test(int_col, double_col) FROM test_data
It basically works, but the output looks like this:
|unpack_test|
|record |
|-----------|
|(1, 1) |
|-----------|
|(2, 2) |
|-----------|
...
In other words, the result is a single record, as opposed to two columns. I found this question that seems to provide an answer, but it deals with a function that selects from a table directly, whereas mine accepts the columns as arguments, since it needs to generate the series used to iterate over them. I therefore can't call it using SELECT * FROM function, as suggested in that answer.
First, you'll need to create a type for the return value of your function. Something like this could work:
CREATE TYPE unpack_test_type AS (out_int int, out_double double precision);
Then change your function to return this type instead of record.
Then you can use it like this:
SELECT (unpack_test).out_int, (unpack_test).out_double FROM
(SELECT unpack_test(int_col, double_col) FROM test_data) as test
It doesn't seem possible to take a function returning a generic record type and use it in this manner.