Using variables in PostgreSQL function - function

I get this error when I try to run a custom PostgreSQL function:
ERROR: query has no destination for result data
PostgreSQL functions are very new for me. I am working with Navicat for PostgreSQL 11.0.17.
I have a table named translations with three columns: id, english, français. Here is how I create my function in the Console window:
test=# CREATE FUNCTION add_translation(english varchar(160), français varchar(160))
RETURNS integer
AS $BODY$
DECLARE
translation_id integer;
BEGIN
INSERT INTO translations
("english", "français")
VALUES (english, français)
RETURNING id
AS translation_id;
RETURN translation_id;
END;
$BODY$
LANGUAGE plpgsql;
Query OK, 0 rows affected (0.02 sec)
When I call this from the Console window, I get a not-very-useful error message.
test=# add_translation('one', 'un');
ERROR: syntax error at or near "add_translation"
LINE 1: add_translation('one', 'un')
^
When I call it from the Design Function window, I get the error quoted at the top.
I specifically want to isolate translation_id, because in the final version of this function I want to insert the latest id from the translation table into a new record in a different table.
I have also tried with:
DECLARE
translation_id integer;
BEGIN
INSERT INTO translations
("english", "français")
VALUES (english, français);
SELECT LASTVAL() INTO translation_id;
RETURN translation_id;
END;
When I run this from the Design Function panel, it behaves correctly, but when I call it from the console I get the same error as before.
If you can recommend any good tutorials and examples for understanding how to use variables correctly in postgres functions, I would be most grateful.

From the console you need a select command
select add_translation('one', 'un');
or
select * from add_translation('one', 'un');
Your function can be plain SQL
create or replace function add_translation (
english varchar(160), français varchar(160)
) returns integer as $body$
insert into translations ("english", "français")
values (english, français)
returning id as translation_id;
$body$ language sql;
In plpgsql a setof some type must be "returned from" the query
create or replace function add_translation (
english varchar(160), français varchar(160)
) returns setof integer as $body$
begin
return query
insert into translations ("english", "français")
values (english, français)
returning id as translation_id;
end;
$body$ language plpgsql;
Or to return a single value do the insert inside a CTE
create or replace function add_translation (
english varchar(160), français varchar(160)
) returns integer as $body$
declare
translation_id integer;
begin
with i as (
insert into translations ("english", "français")
values (english, français)
returning id
)
select id into translation_id from i;
return translation_id;
end;
$body$ language plpgsql;

Related

plpgsql extract array before loop on it's elements

I am trying to create a plpg function taking as parameter :
[{'id_product': 100000158, 'd_price': '7,75'}, {'id_product': 100000339, 'd_price': '9,76'}]
Or maybe :
{'products': [{'id_product': 100000158, 'd_price': '7,75'}, {'id_product': 100000339, 'd_price': '9,76'}]}
Can't tell the best approach yet.
I want to transform this jsonb object or string to an array so I can loop on it.
The idea is to loop en every {'id_product': xxxxxxxxx, 'd_price': 'xxxxx'} so I if values are the same in a table.
What's the most optimal way to do this ?
I am still playing with jsonb functions.
You can create a function containing JSONB_POPULATE_RECORDSET() function
CREATE OR REPLACE FUNCTION fn_extract_elements( i_jsonb JSONB )
RETURNS TABLE (o_product VARCHAR(500), o_price VARCHAR(500))
AS $BODY$
BEGIN
RETURN QUERY
WITH tab AS
(
SELECT *
FROM JSONB_POPULATE_RECORDSET(NULL::record,i_jsonb )
AS tab(id_product VARCHAR(500), d_price VARCHAR(500))
)
SELECT *
FROM tab;
END
$BODY$
LANGUAGE plpgsql;
and invoke in such a way that
SELECT *
FROM fn_extract_elements(
'[{"id_product": "100000158", "d_price": "7,75"},
{"id_product": "100000339", "d_price": "9,76"}]'
);
o_product o_price
100000158 7,75
100000339 9,76
Demo
Here is a solution for those who might want to do something similar :
I changed the input to something like : {0: [100000158, 7.76], 1: [100000339, 9.76]}
And function :
CREATE OR REPLACE FUNCTION public.check_d_price(
p_products jsonb)
RETURNS jsonb
LANGUAGE 'plpgsql'
COST 100
VOLATILE PARALLEL UNSAFE
AS $BODY$
DECLARE
_key varchar;
_real_price NUMERIC;
_bad_price jsonb;
BEGIN
FOR _key IN (
SELECT jsonb_object_keys(p_products)
)
LOOP
SELECT ei.price INTO _real_price FROM product pr JOIN ecom_input ei ON (pr.ecom_id,pr.sku) = (ei.ecom_id,ei.sku_supplier) WHERE pr.id = (p_products->_key->>0)::INT;
IF _real_price <> (p_products->_key->>1)::NUMERIC THEN
_bad_price = COALESCE(_bad_price,'{}'::jsonb) || jsonb_build_object((p_products->_key->>0)::TEXT,
jsonb_build_object('d_price', p_products->_key->>1,'new_price', _real_price));
END IF;
END LOOP;
RETURN _bad_price;
END;
$BODY$;

How to insert a parameter value in insert query in PostgreSQL function?

I wrote a function in PostgreSQL with a parameter, where I want to insert the parameter value in a database table. The script executes fine but when I call the function I got an error message:
CREATE OR REPLACE FUNCTION sp_load_purchase_order(x int)
RETURNS void AS
$$
declare var_val char;
begin
var_val='p'+i;
insert into purchase_order(
create_uid,
create_date,
write_date,
write_uid,
journal_id,
date_order,
partner_id,
amount_untaxed,
location_id,
company_id,
amount_tax,
state,
pricelist_id,
warehouse_id,
payment_term_id,
amount_total,
name,
invoice_method,
shipped,
minimum_planned_date
)
values(1,now(),now(),1,13,now(),17,1.00,12,1,0.00,'draft',2,1,3,1.00
,var_val,'order','f' ,now()
);
end;
$$
LANGUAGE 'plpgsql';
Error message:
ERROR: column "i" does not exist
LINE 1: SELECT 'p'+i
^
QUERY: SELECT 'p'+i
CONTEXT: PL/pgSQL function sp_load_purchase_order(integer) line 5 at assignment
********** Error **********
ERROR: column "i" does not exist
SQL state: 42703
Context: PL/pgSQL function sp_load_purchase_order(integer) line 5 at assignment
Please help me point out the problem.
And the assignment operator in plpgsql is :=:
The forgotten assignment operator "=" and the commonplace ":="
And do not quote the language name plpgsql!
And you do not need to declare a variable for that. The concatenation can take place in the INSERT statement, which is cheaper.
CREATE OR REPLACE FUNCTION sp_load_purchase_order(x int)
RETURNS void AS
$func$
begin
insert into purchase_order(create_uid, ..., name, ...)
values(1, ..., 'p' || x, ...);
end
$func$ LANGUAGE plpgsql;
Your function parameter is named x but your function body refers to an undeclared variable named i...
I've done two bad mistakes in my script... one was that undeclared 'i' variable, and another one was my concatenating attempt where i used '+' sign... here is the working code::::
CREATE OR REPLACE FUNCTION sp_load_purchase_order(x int)
RETURNS void AS
$$
declare var_val char(10);
begin
var_val='p'||x;
insert into purchase_order(
create_uid,
create_date,
write_date,
write_uid,
journal_id,
date_order,
partner_id,
amount_untaxed,
location_id,
company_id,
amount_tax,
state,
pricelist_id,
warehouse_id,
payment_term_id,
amount_total,
name,
invoice_method,
shipped,
minimum_planned_date
)
values(
1,now(),now(),1,13,now(),17,1.00,12,1,0.00,'draft',2,1,3,1.00,var_val,'order','f' ,now()
);
end;
$$
LANGUAGE 'plpgsql';

How to return a table, rows or record from a function in PostgreSQL 9?

I have a table called person which has id,name,status and I want to return rows as a result of a function with 1 parameter (name).
Can anyone help me? Please make it easy, because im very noob in PostgreSQL.
This is my code from a normal function
create or replace function fn_list(vname varchar) returns void as $$
begin
SELECT id,name,status from usuario WHERE name= vname;
end;
$$ language plpgsql;
I know I'm returning a void function but how can I do if I want a list of rows?
I know that pipelined returns in Oracle does this, so I used that to find 'RETURN NEXT' from plpgsql:
http://www.postgresql.org/message-id/007b01c6dc31$ae395920$0a00a8c0#trivadis.com
Also on grokbase:
http://grokbase.com/t/postgresql/pgsql-performance/069kcttrfr/pipelined-functions-in-postgres
(Edit to add official documentation): http://www.postgresql.org/docs/9.2/static/plpgsql-control-structures.html
Killer, I will have to make use of this myself.
Editing one more time to add in some demo code (directly from postgresql.org documentation):
CREATE TABLE foo (fooid INT, foosubid INT, fooname TEXT);
INSERT INTO foo VALUES (1, 2, 'three');
INSERT INTO foo VALUES (4, 5, 'six');
CREATE OR REPLACE FUNCTION getAllFoo() RETURNS SETOF foo AS
$BODY$
DECLARE
r foo%rowtype;
BEGIN
FOR r IN SELECT * FROM foo
WHERE fooid > 0
LOOP
-- can do some processing here
RETURN NEXT r; -- return current row of SELECT
END LOOP;
RETURN;
END
$BODY$
LANGUAGE 'plpgsql' ;
SELECT * FROM getallfoo();
Using a loop to return the result of a query is slow and inefficient. The overhead of PL/pgSQL is not even required for this.
The best solution is:
create or replace function fn_list(vname varchar)
returns table(id integer, name text, status text)
as $$
SELECT id,name,status
from usuario
WHERE name= vname;
$$ language sql;
If PL/pgSQL is needed because some other procedural code needs to run before the query, then return query should be used instead of a loop:
create or replace function fn_list(vname varchar)
returns table(id integer, name text, status text)
as $$
begin
-- do some work....
return query
SELECT id,name,status
from usuario
WHERE name= vname;
end;
$$ language plpgsql;
Then call it using:
select *
from fn_list('Arthur');
Many answers here omit important parts of using functions. Here's an updated way of using functions in postgres (including declaration, variables, args, return values, and running). Below is an over-baked example of updating the tweet on the bottom right "blurb" with "hello world".
id (serial)
pub_id (text)
tweet (text)
1
abc
hello world
2
def
blurb
-- Optional drop if replace fails below.
drop function if exists sync_tweets(text, text);
create or replace function sync_tweets(
src_pub_id text, -- function arguments
dst_pub_id text
) returns setof tweets as -- i.e. rows. int, text work too
$$
declare
src_id int; -- temp function variables (not args)
dest_id int;
src_tweet text;
begin
-- query result into a temp variable
src_id := (select id from tweets where pub_id = src_pub_id);
-- query result into a temp variable (another way)
select tweet into src_tweet from tweets where id = src_id;
dest_id := (select id from tweets where pub_id = dst_pub_id);
update tweets set tweet=src_tweet where id = dest_id;
return query -- i.e. rows, return 0 with return int above works too
select * from tweets where pub_id in (src_pub_id, dst_pub_id);
end
$$ language plpgsql; -- need the language to avoid ERROR 42P13
-- Run it!
select * from sync_tweets('abc', 'def');
/*
Outputs
__________________________________________________
| id (serial) | pub_id (text) | tweet (text) |
|---------------|-----------------|----------------|
| 1 | abc | hello world |
| 2 | def | blurb |
--------------------------------------------------
*/

Function in postgresql wit ref cursor

Im trying to migrate an oracle procedure to a postgresql function. Here's the function in postgres:
CREATE OR REPLACE FUNCTION tibrptsassure.call_reasons(i_start_date date, i_end_date date, i_intnbr character varying, i_intmodnbr character varying, oc_ref_cursor refcursor)
RETURNS refcursor AS
$BODY$
BEGIN
OPEN oc_ref_cursor FOR
SELECT COUNT(1),INTERACTION_NBR,INTERACTION_ID,INTERACTION_MODULE_NBR,CREATED_BY
FROM tibrptsassure.d_tcare_interaction , tibrptsassure.d_calendar d
WHERE INTERACTION_ID = i_intnbr
AND INTERACTION_MODULE_NBR = i_intmodnbr AND INTERACTION_DATE BETWEEN i_start_date AND i_end_date
AND INTERACTION_DATE BETWEEN d.week_start_date AND d.week_end_date
GROUP BY INTERACTION_NBR;
return oc_ref_cursor;
END;
$BODY$
LANGUAGE plpgsql VOLATILE
But, while executing this function, I dint get any output. Got a message: Query result with 1 row discarded.
begin;
select tibrptsassure.sampleproc_call('10-Feb-2010','31-Dec-2013','30681','Bypass_IDV','funccursor');
FETCH ALL IN "funccursor" ;
COMMIT;
Whats wrong in the query?
This is an issue with pgAdmin and multi-statement transactions. Use psql instead.
Basically pgAdmin doesn't know what to do at that point and so it discards the row and you can't with cursors outside such an environment.

PostgreSQL: ERROR: $name$ is not a scalar variable

There is a function that returns 3 parameters one of which is a composite type:
CREATE OR REPLACE FUNCTION f1(
p_text text,
OUT result_status_id smallint,
OUT result_status_msg text,
OUT result_my_type my_type
)
RETURNS record AS
$BODY$
--body here
$BODY$
LANGUAGE 'plpgsql' VOLATILE SECURITY DEFINER
COST 100
The composite type my_type looks like following:
CREATE TYPE my_type AS
(d_real real,
d_int1 integer,
d_int2 integer,
d_int3 integer,
d_int4 integer,
d_int5 integer,
d_int6 integer,
d_int7 integer,
d_int8 integer,
d_int9 integer,
d_int10 integer,
d_bool boolean,
d_date date,
d_text text
);
There is another function f2 that calls function f1 in its body:
CREATE OR REPLACE FUNCTION f2(
p_text text
)
RETURNS record AS
$BODY$
DECLARE
l_status_id smallint;
l_status_msg text;
l_my_type my_type;
BEGIN
--some logic here
--this statement fails
SELECT * FROM f1(p_text) 'x'
INTO l_status_id, l_status_msg, l_my_type;
--logic continues here
END;
$BODY$
LANGUAGE 'plpgsql' VOLATILE SECURITY DEFINER
COST 100;
The problem is that when executing sql with the function I receive the following error:
ERROR: "l_my_type" is not a scalar variable
How may one get a composite type object from another function?
You are violating the rules. The manual:
where target can be a record variable, a row variable, or a
comma-separated list of simple variables and record/row fields.
A record or row variable cannot be part of multiple-item INTO list.
One way around this:
CREATE OR REPLACE FUNCTION f2(p_text text)
RETURNS record AS
$BODY$
DECLARE
r record;
l_status_id smallint;
l_status_msg text;
l_my_type my_type;
BEGIN
SELECT *
FROM f1(p_text) x -- don't single quote 'x'
INTO r;
l_status_id := r.result_status_id;
l_status_msg := r.result_status_msg;
l_my_type := r.result_my_type;
RETURN r; -- or whatever ..
END;
$BODY$
LANGUAGE plpgsql VOLATILE;
There are more ways. Depends where you are going with this. I hardly ever return an anonymous record.
Composite types in tuples are one of those things which is supported partially and you run into odd issues (particularly with thins like storage but that's a story for another time). One way you could accomplish this is:
CREATE TYPE return_type_for_function AS (
result_status_id smallint,
result_status_msg text,
result_my_type my_type
);
CREATE FUNCTION myfunc(....) RETURNS return_type_for_function ....
This is the way I have always done it. This is a little more mature than using OUT variables.
Here's a trivial example:
or_examples=# create table rel_examples.tabletest (id int);
CREATE TABLE
or_examples=# create table comp_table_test (test rel_examples.tabletest);
CREATE TABLE
or_examples=# create function test(int) returns comp_table_test
immutable language sql as $$
select row(row($1))::comp_table_test; $$;
CREATE FUNCTION
or_examples=# select test(1);
test
---------
("(1)")
(1 row)
However, #Chris Travers has proposed an acceptable solution, in my case it was unfortunately not possible to introduce a new type for returning data from f1 function.
Nevertheless, one may call the function f1() in the function f2() and still get the data using the following syntax:
CREATE OR REPLACE FUNCTION f2(
p_text text
)
RETURNS record AS
$BODY$
DECLARE
l_status_id smallint;
l_status_msg text;
l_my_type my_type;
l_record record;
BEGIN
--some logic here
--this statement is okay now
l_record = f1(p_text);
l_status_id = l_record.result_status_id;
l_status_msg = l_record.result_status_msg;
l_my_type = l_record.result_my_type;
--logic continues here
END;
$BODY$
LANGUAGE 'plpgsql' VOLATILE SECURITY DEFINER
COST 100;
In your f2() function, you could do the select into a record variable and then extract what you need from it, e.g.:
CREATE OR REPLACE FUNCTION f2(p_text text )
RETURNS record AS
$BODY$
DECLARE
record_var record;
BEGIN
--this statement was failing
SELECT * FROM f1(p_text) INTO record_var;
SELECT l_my_type.result_my_type.d_int1; -- this should work
END;
$BODY$
LANGUAGE 'plpgsql' VOLATILE SECURITY DEFINER
COST 100;