Access data of variable in PL/PgSQL - postgresql

I have a PL/PgSQL function like this:
CREATE FUNCTION get_value(firstval integer) RETURNS SETOF mytable AS
$func$
DECLARE
current mytable;
BEGIN
SELECT fv FROM mytable WHERE fv = fistval INTO current;
IF current IS NULL THEN
INSERT INTO mytable(fv) VALUES (firstval);
END IF;
RETURN current.fv;
END
$func$ LANGUAGE plpgsql;
I need to return a specific row (fv) of the current variable, but this code is not working (it does not return anything), so how I have to do this?

There are lot of issues:
current is declared like record. But you try to assign integer value to this composite variable SELECT INTO.
The return from table functions should be realised by RETURN TABLE statement. There are not any table with name mytable. In Postgres concept - RETURNS SETOF mytable means - returns rows of type like table "mytable".
The test IS NULL is not safe in this case. You want to check FOUND variable.
Maybe you don't want to return table - then there is badly used SETOF clause.
CREATE OR REPLACE FUNCTION get_value(firstval int)
RETURNS mytable AS $$
DECLARE
_r mytable;
BEGIN
SELECT * FROM mytable WHERE fv = firstval INTO _r;
IF NOT FOUND THEN
INSERT INTO mytable(fv) VALUES(firstval);
SELECT * FROM mytable WHERE fv = firstval INTO _r;
END IF;
RETURN _r;
END;
$$ LANGUAGE plpgsql;
Attention! - this code maybe does what you want, but it is not safe against race conditions. Better to use Postgres statement INSERT ON CONFLICT DO.
Please, try to read documentation first - it is designed differently than you are expecting.

Related

problem creating PLpgSQL function which accepts ARRAY as INPUT and returns SETOF RECORD from the table

I'm trying to create function which will accept ARRAY as INPUT and then return SETOF RECORD for each of the parameter in ARRAY.
I have table country_regions which consists of 3 Columns: id int, region_name TEXT, country_name TEXT;
My Functions code looks like this:
CREATE OR REPLACE FUNCTION search1(TEXT[])
RETURNS SETOF RECORD AS $$
DECLARE x RECORD;
BEGIN
FOR x IN
SELECT *
FROM company_regions
WHERE country_name = $1::TEXT
LOOP
RETURN NEXT x;
END LOOP;
END; $$
LANGUAGE plpgSQL;
This Function was created successfully, but when I try to call the function like this:
SELECT * FROM search1(ARRAY ['usa', 'canada']) AS search1(id int, region_name TEXT, country_name text)
it returns table with 0 rows in it.
Can someone tell me what am I doing wrong? I'm completely new to SQL, tried to find answer in other post but I still could not figure out the problem.
You try to compare text value versus text[].
CREATE OR REPLACE FUNCTION search1(text[])
RETURNS SETOF company_regions AS $$
BEGIN
RETURN QUERY SELECT * FROM company_regions
WHERE country_name = ANY($1);
END
$$ LANGUAGE plpgsql STABLE
Attention - functions like this are black box for optimizer. Usually is not too good (from performance perspective) using functions like envelops of one SQL statement. In complex query it can block some optimizations (Mainly if you forget to set correct flag of function - in this case STABLE).

Query on Return Statement - PostgreSQL

I have this question, I was doing some migration from SQL Server to PostgreSQL 12.
The scenario, I am trying to accomplish:
The function should have a RETURN Statement, be it with SETOF 'tableType' or RETURN TABLE ( some number of columns )
The body starts with a count of records, if there is no record found based on input parameters, then simply Return Zero (0), else, return the entire set of record defined in the RETURN Statement.
The Equivalent part in SQL Server or Oracle is: They can just put a SELECT Statement inside a Procedure to accomplish this. But, its a kind of difficult in case of PostgreSQL.
Any suggestion, please.
What I could accomplish still now - If no record found, it will simply return NULL, may be using PERFORM, or may be selecting NULL as column name for the returning tableType columns.
I hope I am clear !
What I want is something like -
============================================================
CREATE OR REPLACE FUNCTION public.get_some_data(
id integer)
RETURNS TABLE ( id_1 integer, name character varying )
LANGUAGE 'plpgsql'
AS $BODY$
DECLARE
p_id alias for $1;
v_cnt integer:=0;
BEGIN
SELECT COUNT(1) FROM public.exampleTable e
WHERE id::integer = e.id::integer;
IF v_cnt= 0 THEN
SELECT 0;
ELSE
SELECT
a.id, a.name
public.exampleTable a
where a.id = p_id;
END;
$BODY$;
If you just want to return a set of a single table, using returns setof some_table is indeed the easiest way. The most basic SQL function to do that would be:
create function get_data()
returns setof some_table
as
$$
select *
from some_table;
$$
language sql;
PL/pgSQL isn't really necessary to put a SELECT statement into a function, but if you need to do other things, you need to use RETURN QUERY in a PL/pgSQL function:
create function get_data()
returns setof some_table
as
$$
begin
return query
select *
from some_table;
end;
$$
language plpgsql;
A function as exactly one return type. You can't have a function that sometimes returns an integer and sometimes returns thousands of rows with a dozen columns.
The only thing you could do, if you insist on returning something is something like this:
create function get_data()
returns setof some_table
as
$$
begin
return query
select *
from some_table;
if not found then
return query
select (null::some_table).*;
end if;
end;
$$
language plpgsql;
But I would consider the above an extremely ugly and confusing (not to say stupid) solution. I certainly wouldn't let that pass through a code review.
The caller of the function can test if something was returned in the same way I implemented that ugly hack: check the found variable after using the function.
One more hack to get as close as possible to what you want. But I will repeat what others have told you: You cannot do what you want directly. Just because MS SQL Server lets you get away poor coding does not mean Postgres is obligated to do so. As the link by #a_horse_with_no_name implies converting code is easy, once you migrate how you think about the problem in the first place. The closest you can get is return a tuple with a 0 id. The following is one way.
create or replace function public.get_some_data(
p_id integer)
returns table ( id integer, name character varying )
language plpgsql
as $$
declare
v_at_least_one boolean = false;
v_exp_rec record;
begin
for v_exp_rec in
select a.id, a.name
from public.exampletable a
where a.id = p_id
union all
select 0,null
loop
if v_exp_rec.id::integer > 0
or (v_exp_rec.id::integer = 0 and not v_at_least_one)
then
id = v_exp_rec.id;
name = v_exp_rec.name;
return next;
v_at_least_one = true;
end if;
end loop ;
return;
end
$$;
But that is still just a hack and assumes there in not valid row with id=0. A much better approach would by for the calling routing to check what the function returns (it has to do that in one way or another anyway) and let the function just return the data found instead of making up data. That is that mindset shift. Doing that you can reduce this function to a simple select statement:
create or replace function public.get_some_data2(
p_id integer)
returns table ( id integer, name character varying )
language sql strict
as $$
select a.id, a.name
from public.exampletable a
where a.id = p_id;
$$;
Or one of the other solutions offered.

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);

How to return multiple rows from PL/pgSQL function?

I have spent good amount of time trying to figure it out and I haven't been able to resolve it. So, I need your help please.
I am trying to write a PL/pgSQL function that returns multiple rows. The function I wrote is shown below. But it is not working.
CREATE OR REPLACE FUNCTION get_object_fields()
RETURNS SETOF RECORD
AS
$$
DECLARE result_record keyMetrics;
BEGIN
return QUERY SELECT department_id into result_record.visits
from fact_department_daily
where report_date='2013-06-07';
--return result_record;
END
$$ LANGUAGE plpgsql;
SELECT * FROM get_object_fields;
It is returning this error:
ERROR: RETURN cannot have a parameter in function returning set;
use RETURN NEXT at or near "QUERY"
After fixing the bugs #Pavel pointed out, also define your return type properly, or you have to provide a column definition list with every call.
This call:
SELECT * FROM get_object_fields()
... assumes that Postgres knows how to expand *. Since you are returning anonymous records, you get an exception:
ERROR: a column definition list is required for functions returning "record"
One way (of several) to fix this is with RETURNS TABLE (Postgres 8.4+):
CREATE OR REPLACE FUNCTION get_object_fields()
RETURNS TABLE (department_id int) AS
$func$
BEGIN
RETURN QUERY
SELECT department_id
FROM fact_department_daily
WHERE report_date = '2013-06-07';
END
$func$ LANGUAGE plpgsql;
Works for SQL functions just the same.
Related:
PostgreSQL: ERROR: 42601: a column definition list is required for functions returning "record"
I see more bugs:
first, a SET RETURNING FUNCTIONS call has following syntax
SELECT * FROM get_object_fields()
second - RETURN QUERY forwards query result to output directly. You cannot store this result to variable - it is not possible ever in PostgreSQL now.
BEGIN
RETURN QUERY SELECT ....; -- result is forwarded to output directly
RETURN; -- there will not be any next result, finish execution
END;
third - these simple functions is better to implement in SQL languages
CREATE OR REPLACE FUNCTION get_object_fields()
RETURNS SETOF RECORD AS $$
SELECT department_id WHERE ...
$$ LANGUAGE sql STABLE;
Here's one way
drop function if exists get_test_type();
drop type if exists test_comp;
drop type if exists test_type;
drop type if exists test_person;
create type test_type as (
foo int,
bar int
);
create type test_person as (
first_name text,
last_name text
);
create type test_comp as
(
prop_a test_type[],
prop_b test_person[]
);
create or replace function get_test_type()
returns test_comp
as $$
declare
a test_type[];
b test_person[];
x test_comp;
begin
a := array(
select row (m.message_id, m.message_id)
from message m
);
-- alternative 'strongly typed'
b := array[
row('Bob', 'Jones')::test_person,
row('Mike', 'Reid')::test_person
]::test_person[];
-- alternative 'loosely typed'
b := array[
row('Bob', 'Jones'),
row('Mike', 'Reid')
];
-- using a select
b := array (
select row ('Jake', 'Scott')
union all
select row ('Suraksha', 'Setty')
);
x := row(a, b);
return x;
end;
$$
language 'plpgsql' stable;
select * from get_test_type();
CREATE OR REPLACE FUNCTION get_object_fields()
RETURNS table (department_id integer)
AS
$$
DECLARE result_record keyMetrics;
BEGIN
return QUERY
SELECT department_id
from fact_department_daily
where report_date='2013-06-07';
--return result_record;
END;
$$ LANGUAGE plpgsql;
SELECT * FROM get_object_fields()

I want to have my pl/pgsql script output to the screen

I have the following script that I want output to the screen from.
CREATE OR REPLACE FUNCTION randomnametest() RETURNS integer AS $$
DECLARE
rec RECORD;
BEGIN
FOR rec IN SELECT * FROM my_table LOOP
SELECT levenshtein('mystring',lower('rec.Name')) ORDER BY levenshtein;
END LOOP;
RETURN 1;
END;
$$ LANGUAGE plpgsql;
I want to get the output of the levenshein() function in a table along with the rec.Name. How would I do that? Also, it is giving me an error about the line where I call levenshtein(), saying that I should use perform instead.
Assuming that you want to insert the function's return value and the rec.name into a different table. Here is what you can do (create the table new_tab first)-
SELECT levenshtein('mystring',lower(rec.Name)) AS L_val;
INSERT INTO new_tab (L_val, rec.name);
The usage above is demonstrated below.
I guess, you can use RAISE INFO 'This is %', rec.name; to view the values.
CREATE OR REPLACE FUNCTION randomnametest() RETURNS integer AS $$
DECLARE
rec RECORD;
BEGIN
FOR rec IN SELECT * FROM my_table LOOP
SELECT levenshtein('mystring',lower(rec.Name))
AS L_val;
RAISE INFO '% - %', L_val, rec.name;
END LOOP;
RETURN 1;
END;
$$ LANGUAGE plpgsql;
Note- the FROM clause is optional in case you select from a function in a select like netxval(sequence_name) and don't have any actual table to select from i.e. like SELECT nextval(sequence_name) AS next_value;, in Oracle terms it would be SELECT sequence_name.nextval FROM dual; or SELECT function() FROM dual;. There is no dual in postgreSQL.
I also think that the ORDER BY is not necessary since my assumption would be that your function levenshtein() will most likely return only one value at any point of time, and hence wouldn't have enough data to ORDER.
If you want the output from a plpgsql function like the title says:
CREATE OR REPLACE FUNCTION randomnametest(_mystring text)
RETURNS TABLE (l_dist int, name text) AS
$BODY$
BEGIN
RETURN QUERY
SELECT levenshtein(_mystring, lower(t.name)), t.name
FROM my_table t
ORDER BY 1;
END;
$$ LANGUAGE plpgsql;
Declare the table with RETURNS TABLE.
Use RETURN QUERY to return records from the function.
Avoid naming conflicts between column names and OUT parameters (from the RETURNS TABLE clause) by table-qualifying column names in queries. OUT parameters are visible everywhere in the function body.
I made the string to compare to a parameter to the function to make this more useful.
There are other ways, but this is the most effective for the task. You need PostgreSQL 8.4 or later.
For a one-time use I would consider to just use a plain query (= function body without the RETURN QUERY above).