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

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;

Related

MySQL: Function not returning the correct integer

We have a question regarding a function returning the wrong integer-value in MySQL. We have checked that "booked_passengers" contains the right value, 0, and it works just fine when removing that variable, meaning just returning the integer 40. But as soon as we try to subtract "booked_passengers" from it, which still should end up returning 40, it does not work.
Including the code below.
Thanks in advance! :-)
CREATE FUNCTION calculateFreeSeats(flightnumber INT)
RETURNS INT
NOT DETERMINISTIC
BEGIN
DECLARE booked_passengers INT;
SELECT BOOKED_PASSENGERS INTO booked_passengers FROM FLIGHT WHERE (flightnumber = NR);
RETURN (40-booked_passengers);
END $$
When column name and local variable name interfere and there is no table alias then the variable is preferred. So your SELECT BOOKED_PASSENGERS ... selects variable value, not column value. Use
CREATE FUNCTION calculateFreeSeats(flightnumber INT)
RETURNS INT
READS SQL DATA
BEGIN
DECLARE booked_passengers INT;
SELECT FLIGHT.BOOKED_PASSENGERS INTO booked_passengers FROM FLIGHT WHERE (flightnumber = NR);
RETURN (40-booked_passengers);
END $$
From the other side the variable usage is obviously excess:
CREATE FUNCTION calculateFreeSeats(flightnumber INT)
RETURNS INT
READS SQL DATA
RETURN (SELECT 40 - BOOKED_PASSENGERS FROM FLIGHT WHERE flightnumber = NR LIMIT 1);

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

Calling table function from procedure (pl sql)

I am having big trouble calling a function that returns a table from my procedure. I believe there is something wrong with my declaration so its not compiling. Here is my code.. the developer underlines with red the SELECT "D_ID" and from table "(IREL_FN (X))"; . And here is my code.
CREATE OR replace PROCEDURE Irrelevant_skata (x NUMBER)
AS
d_id T_ID_TABLE;
BEGIN
DECLARE
TYPE yo_table
IS
TABLE OF YO_TABLE;
YO_TABLE "(IREL_FN (X))"%TYPE;
id NUMBER;
BEGIN
SELECT "D_ID"
INTO yo_table
FROM TABLE "(IREL_FN (X))";
EXCEPTION
WHEN no_data_found THEN
dbms_output.Put_line('NO DATA FOUND');
END;
END irrelevant_skata;
And the function
CREATE OR replace FUNCTION Irel_fn (x IN NUMBER)
RETURN T_ID_TABLE
AS
id T_ID_TABLE;
BEGIN
BEGIN
SELECT Cast(MULTISET(SELECT "id"
FROM "somethingcopy"
WHERE "kati" IN (SELECT "auto"
FROM "ekeino"
WHERE "id" = x)) AS T_ID_TABLE)
INTO id
FROM dual;
RETURN id;
EXCEPTION
WHEN no_data_found THEN
dbms_output.Put_line('null');
END;
END irel_fn;
It's hard to answer without knowing what you're trying to do.
YO_TABLE "(IREL_FN (X))"%TYPE;
This doesn't make any sense. You can't declare a variable to be %type of a function. Looking up at YO_TABLE declaration, you write
DECLARE
TYPE yo_table
IS
TABLE OF YO_TABLE;
Huh? table declaration is table of same variable you're declaring?
And this:
SELECT "D_ID"
INTO yo_table
FROM TABLE "(IREL_FN (X))";
You don't need quotes when casting table, and the x in IREL_FN (X) is a formal parameter, you need to replace it with the actual value what you need to pass
create or replace
PROCEDURE IRRELEVANT_SKATA (INSID IN NUMBER) AS ID T_ID_TABLE ;
BEGIN
DECLARE
YO_TABLE T_ID_TABLE;
BEGIN
select ID
into YO_TABLE
from table(IREL_FN(INSID));
EXCEPTION
WHEN NO_DATA_FOUND THEN dbms_output.put_line('NO DATA FOUND');
END;
END IRRELEVANT_TWEET;
<-----------------------------FUNCTION---------------------------------->
create or replace
FUNCTION IREL_FN ( D_ID IN NUMBER ) RETURN T_ID_TABLE AS
ID T_ID_TABLE;
BEGIN
BEGIN
SELECT CAST(
MULTISET(
SELECT "Id"
FROM "SOMETHINGCOPY"
WHERE "KATI" = (SELECT "EKEINO" FROM "AUTO" WHERE "Id"=D_ID)
INTO ID
FROM DUAL;
return ID;
EXCEPTION
WHEN NO_DATA_FOUND THEN dbms_output.put_line('null');
END;
END IREL_FN;
I hope I helped :)
I am answering to your question marked as duplicate (the problem with exact fetch).
I rewrote your code in the following way, hope it helps. I makes the Irel_fn to be a pipelined function, however you can still write Cast multiset if you like , however you need to use type constructor as well.
create table auto(id number)
this table is instead of your source "AUTO" (so I could compile it).
create or replace type t_id as object (id number);
/
Create or replace type t_id_table is table of t_id;
/
create or replace FUNCTION Irel_fn (x IN NUMBER) RETURN T_ID_TABLE PIPELINED
as
BEGIN
for rec in (select id from auto where id=x)
loop
Pipe row (t_id(rec.id));
end loop;
return;
end;
/
create or replace procedure Irrelevant_skata (insid in NUMBER) is
bob t_id_table;
BEGIN
select t_id(id) bulk collect into bob from table(irel_fn(insid));
END;

Using variables in PostgreSQL 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;

Calling a stored procedure within an IF statement MySQL

Does anybody know if this is allowed?
IF CALL GET_RIGHT_NODE(edge) = 15
THEN
SELECT "IT WORKS";
I'm getting an error on this syntax, is it possible any other way?
The return values from stored procedures should be captured in OUT paramters (whereas those from user defined functions can be captured as #returnValue = function()).
So, your GET_RIGHT_NODE should take an OUT parameter and set it to the return value.
CREATE PROCEDURE GET_RIGHT_NODE
(
#edge INT,
#returnValue INT OUTPUT
)
AS
-- Definition of the proc.
then you would call the procedure as follows:
DECLARE #returnValue INT
CALL GET_RIGHT_NODE(#edge, #returnValue)
IF (#returnValue = 15)
THEN
SELECT 'IT WORKS'