PostgreSQL 11 - Procedures - postgresql

With the latest update of PostgreSQL supporting procedures. The official blog, quoted that "As opposed to functions, procedures are not required to return a value." (https://blog.2ndquadrant.com/postgresql-11-server-side-procedures-part-1/)
So my question is, is there actually any way for me to return error code or response in a procedure? (Procedures is rather new in Postgres thus there were very little resources online.)
Here is an example of what I meant by returning these "error codes"
create or replace PROCEDURE multislot_Update_v1
(
p_id in varchar2,
p_name in varchar2,
p_enname in varchar2,
results out SYS_REFCURSOR
) AS
rowNumber int;
defaultNumber int;
BEGIN
select count(1) into rowNumber from MULTISLOTSGAME where fid=P_id;
if (rowNumber = 0) then
open results for
select '1' as result from dual;
return;
end if;
update MULTISLOTSGAME set
name = P_name,
enname = P_enname
where fid = P_id ;
commit;
open results for
select '0' as result, t1.* from MULTISLOTSGAME t1 where fid = p_id;
END multislot_Update_v1;
The above script is an Oracle procedure, as u can see if the returned result is "1" it meant that the update wasn't successful.
Is there any way I can write the above script (with error code) as a PostgresSQL Procedure ? Maybe an example of using the "INOUT" argument would be great!

You can have INOUT parameters in a procedure.
You call a procedure with the CALL statement; if there are any INOUT parameters, the statement will return a result row just like SELECT.
Here is an example that uses a procedure that returns a refcursor:
CREATE PROCEDURE testproc(INOUT r refcursor) LANGUAGE plpgsql AS
$$BEGIN
r := 'cur';
OPEN r FOR VALUES (1), (42), (12321);
END;$$;
BEGIN;
CALL testproc(NULL);
r
-----
cur
(1 row)
FETCH ALL FROM cur;
column1
---------
1
42
12321
(3 rows)
COMMIT;

How about using the RAISE statement?
https://www.postgresql.org/docs/10/static/plpgsql-errors-and-messages.html

Related

returning a query result from dynamic query

reading about how to make an SP that returns the results of a query it seems I must do this kind of thing (from tutorial site)
CREATE OR REPLACE FUNCTION show_cities() RETURNS refcursor AS $$
DECLARE
ref refcursor; -- Declare a cursor variable
BEGIN
OPEN ref FOR SELECT city, state FROM cities; -- Open a cursor
RETURN ref; -- Return the cursor to the caller
END;
$$ LANGUAGE plpgsql;
OK, I get this, but I want to pass the SQL in as a paramter so I need to do (I think)
EXECUTE mysql ......
But I dont see how to make EXECUTE return a cursor
EDIT:
OK now I see that I misunderstood what the non dynamic case does. I expected to be able to do select show_cities() and have it do that same thing as SELECT city, state FROM cities, it does not. Of course now that I think about it this isnt surprising. I want to return the actual set of records.
In your case smth like:
t=# CREATE OR REPLACE FUNCTION data_of(_tbl_type anyelement)
RETURNS SETOF anyelement AS
$func$
BEGIN
RETURN QUERY EXECUTE format('
SELECT *
FROM %s -- pg_typeof returns regtype, quoted automatically
WHERE true /*or some filter in additional arguments or so */
ORDER BY true /*or some filter in additional arguments or so */'
, pg_typeof(_tbl_type))
;
END
$func$ LANGUAGE plpgsql;
CREATE FUNCTION
would work:
t=# create table city(i serial, cn text);
CREATE TABLE
t=# insert into city(cn) values ('Moscow'), ('Barcelona'),('Visaginas');
INSERT 0 3
t=# SELECT * FROM data_of(NULL::city);
i | cn
---+-----------
1 | Moscow
2 | Barcelona
3 | Visaginas
(3 rows)
all credits to Erwin with his https://stackoverflow.com/a/11751557/5315974 which is one obligatory reading

Postgresql Common Expression Table (CTE) in Function

I'm trying to use CTE in PostgreSQL function and returning the CTE as table. But I couldn't manage to compile the function as it says ERROR: syntax error at end of input in the select query. Could someone point me what I'm missing here.
CREATE OR REPLACE FUNCTION my_func(name varchar) RETURNS TABLE (hours integer) AS $$
BEGIN
WITH a AS (
SELECT hours FROM name_table tbl where tbl.name= name; <- giving error here
)
RETURN QUERY SELECT hours FROM a;
END;
$$ LANGUAGE plpgsql;
PS: I'm on PostgreSQL 9.6 if that helps.
The CTE expression is part of the query, so it needs to come immediately after the return query clause, not before it. Additionally, to avoid syntax errors later on, you should select a parameter name that ins't ambiguous with the names of the columns, and fully qualify the columns you're querying:
CREATE OR REPLACE FUNCTION my_func(v_name varchar)
RETURNS TABLE (hours integer) AS $$
BEGIN
RETURN QUERY WITH a AS (
SELECT tbl.hours
FROM name_table tbl
WHERE name = v_name
)
SELECT a.hours FROM a;
END;
$$ LANGUAGE plpgsql;

Declare COUNT value as variable (PostgreSQL)

Does anyone know if it possible to declare the COUNT value as a variable to call in queries/functions/triggers?
I would like to use the COUNT value to trigger data transfer from table1 to table2, triggering when the row count of table1 reaches 500.
FIX.....
Defining count function:
CREATE OR REPLACE FUNCTION count_function () RETURNS integer AS $$
BEGIN
RETURN (SELECT COUNT(*) FROM table1);
END $$ LANGUAGE plpgsql;
Calling the variable to trigger an event:
CREATE OR REPLACE FUNCTION save_table2()
RETURNS trigger AS
$$
BEGIN
IF count_function()>=500 THEN
INSERT INTO table2
values ('NEW.column1','NEW.column2');
END IF;
RETURN NEW;
END $$
LANGUAGE plpgsql;
CREATE TRIGGER copy_trigger
AFTER INSERT ON table1
FOR EACH ROW
EXECUTE PROCEDURE save_table2();
Have you tried this (should work on MySQL and SQL Server, maybe PostgreSQL, too)?
SELECT count_function();
On Oracle it would be
SELECT count_function() FROM DUAL;
To store the result in a variable you can do this:
DECLARE result int;
SET result = SELECT count_function();
In your case the trigger can be written as:
CREATE TRIGGER copy_trigger
AFTER INSERT ON table1
FOR EACH STATEMENT
WHEN count_function() >= 500
EXECUTE PROCEDURE save_table2 ();
Notice that >= means greater or equal. While => does not exist (or is not what it looks like).
If nothing else helps, you can do this:
CREATE OR REPLACE FUNCTION save_table2_on_500()
RETURNS VOID AS $$
DECLARE cnt INTEGER;
BEGIN
cnt := (SELECT COUNT(*) FROM table1);
IF cnt >= 500 THEN
EXECUTE PROCEDURE save_table2();
END IF;
END $$ LANGUAGE plpgsql;
CREATE TRIGGER copy_trigger_on_500
AFTER INSERT ON table1
FOR EACH STATEMENT
EXECUTE PROCEDURE save_table2_on_500();
EDIT: What was wrong with the code
I've used the keyword PROCEDURE because it is very common on various database systems (SQL Server, Oracle, MySQL). But it is not legit on PostgreSQL.
On PostgreSQL you must use FUNCTION and specify the return type VOID, which I think is kind of a contradiction, but I'm digressing on details here.
The full explanation of function vs procedure is here.
The difference is mainly that a function returns always a scalar value while a procedure may return nothing (VOID), a scalar value or a data table. It is more flexible but also has other caveats. Refer to the link above for more details.
You should call the function not the variable from the function:
SELECT count_function ()
Also in the function you do not need the variable and have this:
RETURN (SELECT COUNT(*) FROM table1);

PostgreSQl function return multiple dynamic result sets

I have an old MSSQL procedure that needs to be ported to a PostgreSQL function. Basically the SQL procedure consist in a CURSOR over a select statement. For each cursor entity i have three select statements based on the current cursor output.
FETCH NEXT FROM #cursor INTO #entityId
WHILE ##FETCH_STATUS = 0
BEGIN
SELECT * FROM table1 WHERE col1 = #entityId
SELECT * FROM table2 WHERE col2 = #entityId
SELECT * FROM table3 WHERE col3 = #entityId
END
The tables from the SELECT statements have different columns.
I know that the PostgreSQL use refcursor in order to return multiple result sets but the question is if is possible to open and return multiple dynamic refcursors inside of a loop?
The Npgsql .NET data provider is used for handling the results.
Postgres test code with only 1 cursor inside loop:
CREATE OR REPLACE FUNCTION "TestCursor"(refcursor)
RETURNS SETOF refcursor AS
$BODY$
DECLARE
entity_id integer;
BEGIN
FOR entity_id IN SELECT "FolderID" from "Folder"
LOOP
OPEN $1 FOR SELECT * FROM "FolderInfo" WHERE "FolderID" = entity_id;
RETURN NEXT $1;
CLOSE $1;
END LOOP;
END;
$BODY$
LANGUAGE 'plpgsql' VOLATILE;
Then the test code:
BEGIN;
SELECT * FROM "TestCursor"('c');
FETCH ALL IN c;
COMMIT;
The SELECT * FROM "TestCursor"('c'); output is like on screenshot:
Then when i try to fetch data i get the error: ERROR: cursor "c" does not exist
You can emulate it via SETOF refcursor. But it is not good idea. This T-SQL pattern is not supported well in Postgres, and should be prohibited when it is possible. PostgreSQL support functions - function can return scalar, vector or relation. That is all. Usually in 90% is possible to rewrite T-SQL procedures to clean PostgreSQL functions.

Dynamic columns in SQL statement

I am trying to have a dynamic variable that I can specify different column's with (depending on some if statements). Explained in code, I am trying to replace this:
IF (TG_TABLE_NAME='this') THEN INSERT INTO table1 (name_id) VALUES id.NEW END IF;
IF (TG_TABLE_NAME='that') THEN INSERT INTO table1 (lastname_id) VALUES id.NEW END IF;
IF (TG_TABLE_NAME='another') THEN INSERT INTO table1 (age_id) VALUES id.NEW END IF;
With this:
DECLARE
varName COLUMN;
BEGIN
IF (TG_TABLE_NAME='this') THEN varName = 'name_id';
ELSE IF (TG_TABLE_NAME='that') THEN varName = 'lastname_id';
ELSE (TG_TABLE_NAME='another') THEN varName = 'age_id';
END IF;
INSERT INTO table1 (varName) VALUES id.NEW;
END;
The INSERT string is just an example, it's actually something longer. I am a beginner at pgSQL. I've seen some examples but I'm only getting more confused. If you can provide an answer that is also more safe from SQL injection that would be awesome.
One way to do what you're looking for is to compose your INSERT statement dynamically based on the named table. The following function approximates the logic you laid out in the question:
CREATE OR REPLACE FUNCTION smart_insert(table_name TEXT) RETURNS VOID AS $$
DECLARE
target TEXT;
statement TEXT;
BEGIN
CASE table_name
WHEN 'this' THEN target := 'name_id';
WHEN 'that' THEN target := 'lastname_id';
WHEN 'another' THEN target := 'age_id';
END CASE;
statement :=
'INSERT INTO '||table_name||'('||target||') VALUES (nextval(''id''));';
EXECUTE statement;
END;
$$ LANGUAGE plpgsql;
Note that I'm using a sequence to populate these tables (the call to nextval). I'm not sure if that is your use case, but hopefully this example is extensible enough for you to modify it to fit your scenario. A contrived demo:
postgres=# SELECT smart_insert('this');
smart_insert
--------------
(1 row)
postgres=# SELECT smart_insert('that');
smart_insert
--------------
(1 row)
postgres=# SELECT name_id FROM this;
name_id
---------
101
(1 row)
postgres=# SELECT lastname_id FROM that;
lastname_id
-------------
102
(1 row)
Your example doesn't make a lot of sense. Probably over-simplified. Anyway, here is a trigger function for the requested functionality that inserts the new id in a selected column of a target table, depending on the triggering table:
CREATE OR REPLACE FUNCTION smart_insert(table_name TEXT)
RETURNS trigger AS
$func$
BEGIN
EXECUTE
'INSERT INTO table1 ('
|| CASE TG_TABLE_NAME
WHEN 'this' THEN 'name_id'
WHEN 'that' THEN 'lastname_id'
WHEN 'another' THEN 'age_id'
END CASE
||') VALUES ($1)'
USING NEW.id;
END
$func$ LANGUAGE plpgsql;
To refer to the id column of the new row, use NEW.id not id.NEW.
To pass a value to dynamic code, use the USING clause of EXECUTE. This is faster and more elegant, avoids casting to text and back and also makes SQL injection impossible.
Don't use many variables and assignments in plpgsql, where this is comparatively expensive.
If the listed columns of the target table don't have non-default column defaults, you don't even need dynamic SQL:
CREATE OR REPLACE FUNCTION smart_insert(table_name TEXT)
RETURNS trigger AS
$func$
BEGIN
INSERT INTO table1 (name_id, lastname_id, age_id)
SELECT CASE WHEN TG_TABLE_NAME = 'this' THEN NEW.id END
, CASE WHEN TG_TABLE_NAME = 'that' THEN NEW.id END
, CASE WHEN TG_TABLE_NAME = 'another' THEN NEW.id END;
END
$func$ LANGUAGE plpgsql;
A CASE expression without ELSE clause defaults to NULL, which is the default column default.
Both variants are safe against SQL injection.