How to create SQL function with parameter? - sql

I have an SQL function:
-- Function: insert_into_wgs()
-- DROP FUNCTION insert_into_wgs();
CREATE OR REPLACE FUNCTION insert_into_wgs()
RETURNS void AS
$BODY$
BEGIN
INSERT INTO parcels (num,vid,support_num,name_dispatcher,cadastr,the_geom,status_id)
SELECT num,vid,support_num,name_dispatcher,cadastr,ST_TRANSFORM(the_geom,4326),status_id
FROM parcels_temp
WHERE num NOT IN (SELECT num FROM parcels)AND deleted='no';
end;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
ALTER FUNCTION insert_into_wgs() OWNER TO postgres;
You see that it works only for parcels, parcels_temp tables. How can I create function with parameters insert_into_wgs(table, table_temp);?

As mentioned in the comment, you have to use dynamic SQL if you want to parametrize identifiers. And use EXECUTE in plpgsql.
In the base query better use NOT EXISTS instead of col NOT IN (<subquery>).
Now, if only the table names change and the columns stay the same, this simple demo would do the job:
CREATE OR REPLACE FUNCTION insert_into_tbl(_tbl text, _tbl_tmp text)
RETURNS void AS
$func$
BEGIN
EXECUTE format($x$
INSERT INTO %I (...) -- enter columns
SELECT ... -- enter columns
FROM %I t
WHERE NOT EXISTS (
SELECT 1
FROM %1$I p -- reuse first table name
WHERE p.num = t.num
)
AND deleted = 'no'$x$, _tbl, _tbl_tmp);
END
$func$ LANGUAGE plpgsql;
Check out format() in the manual.
There are quite a few related answers on SO. Try this search.

Related

Dynamic query that uses CTE gets "syntax error at end of input"

I have a table that looks like this:
CREATE TABLE label (
hid UUID PRIMARY KEY DEFAULT UUID_GENERATE_V4(),
name TEXT NOT NULL UNIQUE
);
I want to create a function that takes a list of names and inserts multiple rows into the table, ignoring duplicate names, and returns an array of the IDs generated for the rows it inserted.
This works:
CREATE OR REPLACE FUNCTION insert_label(nms TEXT[])
RETURNS UUID[]
AS $$
DECLARE
ids UUID[];
BEGIN
CREATE TEMP TABLE tmp_names(name TEXT);
INSERT INTO tmp_names SELECT UNNEST(nms);
WITH new_names AS (
INSERT INTO label(name)
SELECT tn.name
FROM tmp_names tn
WHERE NOT EXISTS(SELECT 1 FROM label h WHERE h.name = tn.name)
RETURNING hid
)
SELECT ARRAY_AGG(hid) INTO ids
FROM new_names;
DROP TABLE tmp_names;
RETURN ids;
END;
$$ LANGUAGE PLPGSQL;
I have many tables with the exact same columns as the label table, so I would like to have a function that can insert into any of them. I'd like to create a dynamic query to do that. I tried that, but this does not work:
CREATE OR REPLACE FUNCTION insert_label(h_tbl REGCLASS, nms TEXT[])
RETURNS UUID[]
AS $$
DECLARE
ids UUID[];
query_str TEXT;
BEGIN
CREATE TEMP TABLE tmp_names(name TEXT);
INSERT INTO tmp_names SELECT UNNEST(nms);
query_str := FORMAT('WITH new_names AS ( INSERT INTO %1$I(name) SELECT tn.name FROM tmp_names tn WHERE NOT EXISTS(SELECT 1 FROM %1$I h WHERE h.name = tn.name) RETURNING hid)', h_tbl);
EXECUTE query_str;
SELECT ARRAY_AGG(hid) INTO ids FROM new_names;
DROP TABLE tmp_names;
RETURN ids;
END;
$$ LANGUAGE PLPGSQL;
This is the output I get when I run that function:
psql=# select insert_label('label', array['how', 'now', 'brown', 'cow']);
ERROR: syntax error at end of input
LINE 1: ...SELECT 1 FROM label h WHERE h.name = tn.name) RETURNING hid)
^
QUERY: WITH new_names AS ( INSERT INTO label(name) SELECT tn.name FROM tmp_names tn WHERE NOT EXISTS(SELECT 1 FROM label h WHERE h.name = tn.name) RETURNING hid)
CONTEXT: PL/pgSQL function insert_label(regclass,text[]) line 19 at EXECUTE
The query generated by the dynamic SQL looks like it should be exactly the same as the query from static SQL.
I got the function to work by changing the return value from an array of UUIDs to a table of UUIDs and not using CTE:
CREATE OR REPLACE FUNCTION insert_label(h_tbl REGCLASS, nms TEXT[])
RETURNS TABLE (hid UUID)
AS $$
DECLARE
query_str TEXT;
BEGIN
CREATE TEMP TABLE tmp_names(name TEXT);
INSERT INTO tmp_names SELECT UNNEST(nms);
query_str := FORMAT('INSERT INTO %1$I(name) SELECT tn.name FROM tmp_names tn WHERE NOT EXISTS(SELECT 1 FROM %1$I h WHERE h.name = tn.name) RETURNING hid', h_tbl);
RETURN QUERY EXECUTE query_str;
DROP TABLE tmp_names;
RETURN;
END;
$$ LANGUAGE PLPGSQL;
I don't know if one way is better than the other, returning an array of UUIDs or a table of UUIDs, but at least I got it to work one of those ways. Plus, possibly not using a CTE is more efficient, so it may be better to stick with the version that returns a table of UUIDs.
What I would like to know is why the dynamic query did not work when using a CTE. The query it produced looked like it should have worked.
If anyone can let me know what I did wrong, I would appreciate it.
... why the dynamic query did not work when using a CTE. The query it produced looked like it should have worked.
No, it was only the CTE without (required) outer query. (You had SELECT ARRAY_AGG(hid) INTO ids FROM new_names in the static version.)
There are more problems, but just use this query instead:
INSERT INTO label(name)
SELECT unnest(nms)
ON CONFLICT DO NOTHING
RETURNING hid;
label.name is defined UNIQUE NOT NULL, so this simple UPSERT can replace your function insert_label() completely.
It's much simpler and faster. It also defends against possible duplicates from within your input array that you didn't cover, yet. And it's safe under concurrent write load - as opposed to your original, which might run into race conditions. Related:
How to use RETURNING with ON CONFLICT in PostgreSQL?
I would just use the simple query and replace the table name.
But if you still want a dynamic function:
CREATE OR REPLACE FUNCTION insert_label(_tbl regclass, _nms text[])
RETURNS TABLE (hid uuid)
LANGUAGE plpgsql AS
$func$
BEGIN
RETURN QUERY EXECUTE format(
$$
INSERT INTO %s(name)
SELECT unnest($1)
ON CONFLICT DO NOTHING
RETURNING hid
$$, _tbl)
USING _nms;
END
$func$;
If you don't need an array as result, stick with the set (RETURNS TABLE ...). Simpler.
Pass values (_nms) to EXECUTE in a USING clause.
The tablename (_tbl) is type regclass, so the format specifier %I for format() would be wrong. Use %s instead. See:
Table name as a PostgreSQL function parameter

Filter table in a function using a table valued parameter

I have a table-valued function that uses a parameter to filter a table and returns the results:
drop table if exists mytbl;
create table mytbl (i int);
insert into mytbl (i) values (1),(2),(3);
CREATE FUNCTION filterer(
_filter int
)
RETURNS TABLE(i_ret int)
LANGUAGE 'plpgsql'
AS $BODY$
BEGIN
RETURN QUERY SELECT i
FROM mytbl
WHERE i = _filter;
END;
$BODY$;
select * from filterer(1);
it returns:
So far so good. However what I really want to do is filter on a list of values rather than just one. I figured the way to do it is create a table-valued parameter using a type that I define using CREATE TYPE, but I can't get it working. Here's what I have so far:
drop table if exists mytbl;
create table mytbl (i int);
DO $$
BEGIN
IF NOT EXISTS (SELECT * FROM pg_type t WHERE t.typname = 'tp') THEN
CREATE TYPE tp AS (i int);
END IF;
END$$;
insert into mytbl (i) values (1),(2),(3);
DROP FUNCTION public.filterer(tp);
CREATE FUNCTION filterer(
_filter tp
)
RETURNS TABLE(i_ret int)
LANGUAGE 'plpgsql'
AS $BODY$
BEGIN
RETURN QUERY SELECT i
FROM mytbl
WHERE i IN (_filter);
END;
$BODY$;
select * from filterer(1);
When I run that code it fails with:
ERROR: function filterer(integer) does not exist LINE 23: select *
from filterer(1);
How do I declare a variable of type tp and pass it into my function?
working example
Two things here:
WHERE i IN (_filter);
should be replaced with
WHERE row(i) IN (_filter);
and calling:
select * from filterer(row(1));
I assume the question is purely academic, as I can't see any use of such type?..
CREATE TYPE tp AS (i int);

<column> is ambiguous in column comparison between two tables

I want this postgres function to work :
CREATE OR REPLACE FUNCTION difference_of_match_ids_in_match_history_and_match_results()
returns table(match_id BIGINT)
as
$$
BEGIN
return QUERY
SELECT *
FROM sports.match_history
WHERE match_id NOT IN (SELECT match_id
FROM sports.match_results);
END $$
LANGUAGE 'plpgsql';
This stand alone query works just fine:
SELECT *
FROM sports.match_history
WHERE match_id NOT IN (SELECT match_id FROM sports.match_results);
But when I put it into this function and try to run it like this:
select *
from difference_of_match_ids_in_match_history_and_match_results();
I get this:
SQL Error [42702]: ERROR: column reference "match_id" is ambiguous
Detail: It could refer to either a PL/pgSQL variable or a table
column. Where: PL/pgSQL function
difference_of_match_ids_in_match_history_and_match_results() line 3 at
RETURN QUERY
I've seen other questions with this same error, and they suggest naming the sub queries to specify which instance of a column you're referring to, however, those examples use joins and my query works fine outside of the function.
If I do need to name the column, how would I do so with only one sub-query?
If that isn't the issue, then I'm assuming that there's something wrong with the way I'm defining a function.
You query is fine. The ambiguity is on the match_id in returns table(match_id BIGINT) rename it or prefix the columns with the table name in your query
CREATE OR REPLACE FUNCTION difference_of_match_ids_in_match_history_and_match_results()
returns table(new_name BIGINT)
as
$$
BEGIN
return QUERY
SELECT *
FROM sports.match_history
WHERE match_id NOT IN (SELECT match_id
FROM sports.match_results);
END $$
LANGUAGE 'plpgsql';
or
CREATE OR REPLACE FUNCTION difference_of_match_ids_in_match_history_and_match_results()
returns table(match_id BIGINT)
as
$$
BEGIN
return QUERY
SELECT sports.match_history.match_id
FROM sports.match_history
WHERE sports.match_history.match_id NOT IN (SELECT sports.match_results.match_id
FROM sports.match_results);
END $$
LANGUAGE 'plpgsql';
Didn't test the code.
The structure of the result set must match the function result type. If you want to get only match_ids:
CREATE OR REPLACE FUNCTION difference_of_match_ids_in_match_history_and_match_results()
RETURNS TABLE(m_id BIGINT) -- !!
AS
$$
BEGIN
RETURN QUERY
SELECT match_id -- !!
FROM sports.match_history
WHERE match_id NOT IN (SELECT match_id
FROM sports.match_results);
END $$
LANGUAGE 'plpgsql';
If you want to get whole rows as a result:
DROP FUNCTION difference_of_match_ids_in_match_history_and_match_results();
CREATE OR REPLACE FUNCTION difference_of_match_ids_in_match_history_and_match_results()
RETURNS SETOF sports.match_history -- !!
AS
$$
BEGIN
RETURN QUERY
SELECT * -- !!
FROM sports.match_history
WHERE match_id NOT IN (SELECT match_id
FROM sports.match_results);
END $$
LANGUAGE 'plpgsql';
As others have answerd, it's an ambiguity between the result definition and PL/pgSQL variables. The column name in a set returning function is in fact also a variable inside the function.
But you don't need PL/pgSQL for this in the first place. If you use a plain SQL function it will be more efficient and the problem will go away as well:
CREATE OR REPLACE FUNCTION difference_of_match_ids_in_match_history_and_match_results()
returns table(match_id BIGINT)
as
$$
SELECT match_id --<< do not return * - only return one column
FROM sports.match_history
WHERE match_id NOT IN (SELECT match_id
FROM sports.match_results);
$$
LANGUAGE sql;
Note that the language name is an identifier and should not be quoted at all.
The naming conflict between column names and plpgsql OUT parameters has been addressed. More details here:
Postgresql - INSERT RETURNING INTO ambiguous column reference
I would also use a different query style. NOT IN (SELECT ...) is typically slowest and carries traps with NULL values. Use NOT EXISTS instead:
SELECT match_id
FROM sports.match_history h
WHERE NOT EXISTS (
SELECT match_id
FROM sports.match_results
WHERE match_id = h.match_id
);
More:
Select rows which are not present in other table

Passing a ResultSet into a Postgresql Function

Is it possible to pass the results of a postgres query as an input into another function?
As a very contrived example, say I have one query like
SELECT id, name
FROM users
LIMIT 50
and I want to create a function my_function that takes the resultset of the first query and returns the minimum id. Is this possible in pl/pgsql?
SELECT my_function(SELECT id, name FROM Users LIMIT 50); --returns 50
You could use a cursor, but that very impractical for computing a minimum.
I would use a temporary table for that purpose, and pass the table name for use in dynamic SQL:
CREATE OR REPLACE FUNCTION f_min_id(_tbl regclass, OUT min_id int) AS
$func$
BEGIN
EXECUTE 'SELECT min(id) FROM ' || _tbl
INTO min_id;
END
$func$ LANGUAGE plpgsql;
Call:
CREATE TEMP TABLE foo ON COMMIT DROP AS
SELECT id, name
FROM users
LIMIT 50;
SELECT f_min_id('foo');
Major points
The first parameter is of type regclass to prevent SQL injection. More info in this related answer on dba.SE.
I made the temp table ON COMMIT DROP to limit its lifetime to the current transaction. May or may not be what you want.
You can extend this example to take more parameters. Search for code examples for dynamic SQL with EXECUTE.
-> SQLfiddle demo
I would take the problem on the other side, calling an aggregate function for each record of the result set. It's not as flexible but can gives you an hint to work on.
As an exemple to follow your sample problem:
CREATE OR REPLACE FUNCTION myMin ( int,int ) RETURNS int AS $$
SELECT CASE WHEN $1 < $2 THEN $1 ELSE $2 END;
$$ LANGUAGE SQL STRICT IMMUTABLE;
CREATE AGGREGATE my_function ( int ) (
SFUNC = myMin, STYPE = int, INITCOND = 2147483647 --maxint
);
SELECT my_function(id) from (SELECT * FROM Users LIMIT 50) x;
It is not possible to pass an array of generic type RECORD to a plpgsql function which is essentially what you are trying to do.
What you can do is pass in an array of a specific user defined TYPE or of a particular table row type. In the example below you could also swap out the argument data type for the table name users[] (though this would obviously mean getting all data in the users table row).
CREATE TYPE trivial {
"ID" integer,
"NAME" text
}
CREATE OR REPLACE FUNCTION trivial_func(data trivial[])
RETURNS integer AS
$BODY$
DECLARE
BEGIN
--Implementation here using data
return 1;
END$BODY$
LANGUAGE 'plpgsql' VOLATILE;
I think there's no way to pass recordset or table into function (but I'd be glad if i'm wrong). Best I could suggest is to pass array:
create or replace function my_function(data int[])
returns int
as
$$
select min(x) from unnest(data) as x
$$
language SQL;
sql fiddle demo

plpgsql - column type auto detect by returning a resultset

I got Postgresql 8.4
I have a user table
user_id INT,
user_name VARCHAR(255),
user_email VARCHAR(255),
user_salt VARCHAR(255)
and 2 functions:
one with SETOF:
CREATE FUNCTION test ()
RETURNS SETOF "user"
AS
$BODY$
BEGIN
RETURN QUERY SELECT
*
FROM
"user";
END;
$BODY$
LANGUAGE 'plpgsql' VOLATILE;
one with TABLE:
CREATE FUNCTION test ()
RETURNS TABLE (id INT, name VARCHAR, email VARCHAR)
AS
$BODY$
BEGIN
RETURN QUERY SELECT
"user".user_id, "user".user_name, "user".user_email
FROM
"user";
END;
$BODY$
LANGUAGE 'plpgsql' VOLATILE;
By SETOF the column type is filled automatically, but I cannot set the column name and which columns to select in the result. By TABLE I can cut off the user_ prefix and set the exact column names, but I have to set the column types manually.
Is it possible to got the advantages of both?
Type handling with in PostgreSQL (and SQL in general) is strict. Defining the RETURN type of a function can be tricky.
There are simple solutions with plain SQL:
Select and rename all columns:
SELECT * FROM t AS t(id, name, email, salt);
Select and rename some columns:
SELECT user_id AS id, user_name AS name FROM t;
Combine with function
If you need a server side function for some reason, you can still combine these SQL features with a function. Given a table t like defined in your question and this simple function:
CREATE OR REPLACE FUNCTION f_test()
RETURNS SETOF t AS
$func$
SELECT * FROM t -- do stuff here
$func$ LANGUAGE sql STABLE;
To only rename columns (no need to provide types):
SELECT * FROM f_test() t(id, name, email, salt)
Select and rename some columns:
SELECT user_id AS id, user_name AS name FROM f_test() t;
You could possibly combine this with various different functions on different tables:
Refactor a PL/pgSQL function to return the output of various SELECT queries
But I am not entirely sure which problem in particular you want to tackle here.
Asides
Never quote the language name. plpgsql is an identifier. Quoting can lead to unexpected problems.
A function that only selects from tables can be declared STABLE. May be beneficial with repeated calls when nested.
Generally I would advice to upgrade to a current version of Postgres. 8.4 is rather old and will be lose support in less than a year. There have been a number of improvements in this area since 8.4.
I don't think this is possible in pl/pgsql because, it strongly depends on user defined types. Sadly this language is not smart enough for type auto detection... I think my first possible solution I'll use, it solves the problem partially because at least I won't need to refactor every function manually by type change of a table column.
1.) Possible solution with asking column types:
CREATE FUNCTION test ()
RETURNS TABLE (id "user".user_id%TYPE, name "user".user_name%TYPE, email "user".user_email%TYPE)
AS
$BODY$
BEGIN
return QUERY SELECT
"user".user_id, "user".user_name, "user".user_email
FROM
"user";
END;
$BODY$
LANGUAGE 'plpgsql' VOLATILE;
With this at least the type is not redundant. Not the best, but acceptable.
2.) Possible solution with SETOF RECORD:
CREATE FUNCTION test ()
RETURNS SETOF RECORD
AS
$BODY$
BEGIN
RETURN QUERY SELECT
"user".user_id AS id, "user".user_name AS name, "user".user_email AS email
FROM
"user";
END;
$BODY$
LANGUAGE 'plpgsql' VOLATILE;
Without column type definition list I got the following error:
ERROR: a column definition list is required for functions returning
"record"
So I have to use it like this:
SELECT * FROM test() AS (id INT, name VARCHAR, email VARCHAR);
Instead of this:
SELECT * FROM test()
I got every column in string by the php client, so the column type definition is more than useless for me... This solution would be the best without column type definition, but with it not acceptable.
It is possible to use this similar to table:
CREATE FUNCTION test (OUT id "user".user_id%TYPE, OUT name "user".user_name%TYPE, OUT email "user".user_email%TYPE)
RETURNS SETOF RECORD
AS
$BODY$
BEGIN
RETURN QUERY SELECT
"user".user_id, "user".user_name, "user".user_email
FROM
"user";
END;
$BODY$
LANGUAGE 'plpgsql' VOLATILE;
For example I could set everything to TEXT:
CREATE FUNCTION test (OUT id TEXT, OUT name TEXT , OUT email TEXT )
RETURNS SETOF RECORD
AS
$BODY$
BEGIN
RETURN QUERY SELECT
"user".user_id::TEXT , "user".user_name::TEXT , "user".user_email::TEXT
FROM
"user";
END;
$BODY$
LANGUAGE 'plpgsql' VOLATILE;
This works, but this is far from type auto detection, and it would result a lot of redundant text converter code...
3.) Possible solution with refcursors:
CREATE FUNCTION test ()
RETURNS SETOF "user"
AS
$BODY$
DECLARE
refc "user";
BEGIN
FOR refc IN
SELECT
"user".user_id, "user".user_name, "user".user_email
FROM
"user"
LOOP
RETURN NEXT refc;
END LOOP ;
RETURN ;
END;
$BODY$
LANGUAGE 'plpgsql' VOLATILE;
This fills out the lacking columns with null values, I cannot name the columns, and sql loops are very slow... So this is not acceptable.
By refcursors there is another way: to return the refcursor itself, but it is not acceptable because I cannot use it as a normal variable, I have to give a string as cursor name... Btw I did not manage to use the refcursor itself as result in phpstorm. I got jdbc cursor not found error. Maybe I set the name wrong, I don't know, I don't think it's worth more effort.