I have an Oracle stored procedure that gets 2 parameters and returns 2 parameters (status and message).
I am making changes to this legacy application capable of only executing select statements,
My question is it possible to wrap the stored procedure with some function or other stored procedure or a view, or any other object I might not no about, so can I execute the stored procedure using simple a select statement?
Correct execution code will be something like:
DECLARE
PRINTER_ID VARCHAR2(200);
O_STATUS VARCHAR2(200);
O_MESSAGE VARCHAR2(200);
BEGIN
PRINTER_ID := '551555115';
IMPL_XEROX_PRINTER_CHECK( PRINTER_ID => PRINTER_ID, O_STATUS => O_STATUS, O_MESSAGE => O_MESSAGE );
DBMS_OUTPUT.PUT_LINE('O_STATUS = ' || O_STATUS);
DBMS_OUTPUT.PUT_LINE('O_MESSAGE = ' || O_MESSAGE);
END;
What I am trying to get is something like:
Select O_STATUS,O_MESSAGE from IMPL_XEROX_PRINTER_CHECk_WRAPPER where PRINTER_ID = '551555115';
The thing is that the SP is inserting some data to a temporary table...
this is the table:
CREATE TABLE "TEST_PRNT_DATA" ( "COLUMN1" VARCHAR2(20 BYTE), "COLUMN2" VARCHAR2(20 BYTE), "COLUMN3" VARCHAR2(20 BYTE) )
/
This is the stored procedure:
CREATE OR REPLACE PROCEDURE IMPL_XEROX_PRINTER_CHECK
(
PRINTER_ID IN VARCHAR2
, O_STATUS OUT VARCHAR2
, O_MESSAGE OUT VARCHAR2
) AS
PROC_STATUS VARCHAR2(10);
PROC_ERROR_MESSAGE VARCHAR2(4000);
rand_num number;
BEGIN
dbms_output.put_line('IMPL_XEROX_PRINTER_CHECK ');
select round(dbms_random.value(1,10)) into rand_num from dual;
insert into TEST_PRNT_DATA values(1,2,3);
IF rand_num < 5 THEN
PROC_STATUS := 'TRUE';
O_STATUS:= 'TRUE';
PROC_ERROR_MESSAGE := 'ALL IS GOOD';
O_MESSAGE:= 'ALL IS GOOD';
ELSE
PROC_STATUS := 'FALSE';
O_STATUS:= 'FALSE';
PROC_ERROR_MESSAGE := 'SOMTHING WENT WRONG!!! ';
O_MESSAGE:= 'SOMTHING WENT WRONG!!! ';
END IF;
END IMPL_XEROX_PRINTER_CHECK;
You could create a package with a pipelined table function:
CREATE OR REPLACE
PACKAGE PACKAGE1
AS
type status_t is record ( o_status varchar2(10)
, o_message varchar2(4000));
type status_tt is table of status_t;
function impl_xerox_printer_check_w(printer_id varchar2) RETURN status_tt PIPELINED;
END PACKAGE1;
/
With the following implementation:
CREATE OR REPLACE
PACKAGE BODY PACKAGE1 AS
function impl_xerox_printer_check_w(printer_id varchar2) RETURN status_tt PIPELINED AS
status status_t;
BEGIN
impl_xerox_printer_check(printer_id, status.o_status, status.o_message);
PIPE ROW (status);
RETURN;
END impl_xerox_printer_check_w;
END PACKAGE1;
/
and use it like this:
with printers as (
select dbms_random.string('X',10) printer from dual connect by level <=5
)
select *
from printers
cross apply table(package1.impl_xerox_printer_check_w(printers.printer));
Example output or check out the db<>fiddle:
PRINTER O_STATUS O_MESSAGE
--------------- ---------- ------------------------------
55FBCMHYOS TRUE ALL IS GOOD
0Z37VPOSLK TRUE ALL IS GOOD
XK1QKTZ8X2 FALSE SOMTHING WENT WRONG!!!
K0Y6TN9YTR FALSE SOMTHING WENT WRONG!!!
8D0505711L TRUE ALL IS GOOD
Based on a combination of a couple of Alex's answers (sys.odcivarchar2list collections and with functions) here are a couple variations on the themes:
The first one returns a single row as with most examples by using a pivot in the last query:
with function wrap(printer_id in varchar2) return sys.odcivarchar2list as
status sys.odcivarchar2list;
begin
status := new sys.odcivarchar2list();
status.extend(2);
impl_xerox_printer_check(printer_id, status(1), status(2));
return status;
end;
t1 as (
select rownum r, column_value
from wrap('551555115')
)
select *
from t1
pivot (max(column_value)
for r in ( 1 as status
, 2 as message));
/
Sample Output:
STATUS MESSAGE
-------- -------------------------
FALSE SOMTHING WENT WRONG!!!
This second example demonstrates using CROSS APPLY to get the status of multiple printers at one time:
with function wrap(printer_id in varchar2) return sys.odcivarchar2list as
status sys.odcivarchar2list;
begin
status := new sys.odcivarchar2list();
status.extend(2);
impl_xerox_printer_check(printer_id, status(1), status(2));
return status;
end;
printers as (
select dbms_random.string('X',10) printer from dual connect by level <=5
), t1 as (
select printer, mod(rownum-1,2) r, w.*
from printers
cross apply wrap(printers.printer) w
)
select *
from t1
pivot (max(column_value) for r in (0 as status, 1 as message));
/
Sample Output:
PRINTER STATUS MESSAGE
---------- -------- -------------------------
M6N6MZ5NG6 TRUE ALL IS GOOD
4H2WKK52V7 TRUE ALL IS GOOD
6MB7B9FRWV TRUE ALL IS GOOD
389KALS4U9 FALSE SOMTHING WENT WRONG!!!
6Y1ACVUHY6 TRUE ALL IS GOOD
It depends what your application can handle. You could have a wrapper function that returns a ref cursor:
create or replace function impl_xerox_printer_check_wrap (
printer_id in varchar2
)
return sys_refcursor as
o_status varchar2(200);
o_message varchar2(200);
o_refcursor sys_refcursor;
begin
impl_xerox_printer_check(printer_id => printer_id, o_status => o_status, o_message => o_message);
open o_refcursor for select o_status as status, o_message as message from dual;
return o_refcursor;
end;
/
select impl_xerox_printer_check_wrap('551555115') from dual;
IMPL_XEROX_PRINTER_C
--------------------
CURSOR STATEMENT : 1
CURSOR STATEMENT : 1
STATUS MESSAGE
---------- ------------------------------
TRUE ALL IS GOOD
(output as shown by SQL Developer, run as a script). But your application might not know what to do with that.
You could use a collection or object type but unless you define your own at schema level it's a bit of a pain to interpret:
create or replace function impl_xerox_printer_check_wrap (
printer_id in varchar2
)
return sys.odcivarchar2list as
o_result sys.odcivarchar2list;
begin
o_result := new sys.odcivarchar2list();
o_result.extend(2);
impl_xerox_printer_check(printer_id => printer_id, o_status => o_result(1), o_message => o_result(2));
return o_result;
end;
/
select * from table (impl_xerox_printer_check_wrap('551555115'));
Result Sequence
------------------------------------------------
TRUE
ALL IS GOOD
Or you could go via XML, which sounds odd but gives a nice result:
create or replace function impl_xerox_printer_check_wrap (
printer_id in varchar2
)
return xmltype as
o_status varchar2(200);
o_message varchar2(200);
o_refcursor sys_refcursor;
begin
impl_xerox_printer_check(printer_id => printer_id, o_status => o_status, o_message => o_message);
open o_refcursor for select o_status as status, o_message as message from dual;
return xmltype(o_refcursor);
end;
/
select impl_xerox_printer_check_wrap('551555115') from dual;
IMPL_XEROX_PRINTER_CHECK_WRAP('551555115')
--------------------------------------------------------------------------------
<?xml version="1.0"?>
<ROWSET>
<ROW>
<STATUS>FALSE</STATUS>
<MESSAGE>SOMTHING WENT WRONG!!! </MESSAGE>
</ROW>
</ROWSET>
OK, that doesn't look very helpful... but then you extract the values:
select status, message
from xmltable(
'/ROWSET/ROW'
passing impl_xerox_printer_check_wrap('551555115')
columns status varchar2(200) path 'STATUS',
message varchar2(200) path 'MESSAGE'
);
STATUS MESSAGE
---------- ------------------------------
FALSE SOMTHING WENT WRONG!!!
db<>fiddle
Your application can run that query - passing the printer ID as a bind variable of course - and will get back a simple result set.
As you're on 12c you can use the PL/SQL capabilities added to subquery factoring, so you don't need to create a permanent function at all (though you may still prefer to):
drop function IMPL_XEROX_PRINTER_CHECK_WRAP;
with
function impl_xerox_printer_check_wrap (
printer_id in varchar2
)
return xmltype as
o_status varchar2(200);
o_message varchar2(200);
o_refcursor sys_refcursor;
begin
impl_xerox_printer_check(printer_id => printer_id, o_status => o_status, o_message => o_message);
open o_refcursor for select o_status as status, o_message as message from dual;
return xmltype(o_refcursor);
end;
select impl_xerox_printer_check_wrap('551555115')
from dual
/
if you want XML (as per comment), or with XMLTable if you don't:
IMPL_XEROX_PRINTER_CHECK_WRAP('551555115')
--------------------------------------------------------------------------------
<?xml version="1.0"?>
<ROWSET>
<ROW>
<STATUS>TRUE</STATUS>
<MESSAGE>ALL IS GOOD</MESSAGE>
</ROW>
</ROWSET>
with
function impl_xerox_printer_check_wrap (
printer_id in varchar2
)
return xmltype as
o_status varchar2(200);
o_message varchar2(200);
o_refcursor sys_refcursor;
begin
impl_xerox_printer_check(printer_id => printer_id, o_status => o_status, o_message => o_message);
open o_refcursor for select o_status as status, o_message as message from dual;
return xmltype(o_refcursor);
end;
select status, message
from xmltable(
'/ROWSET/ROW'
passing impl_xerox_printer_check_wrap('551555115')
columns status varchar2(200) path 'STATUS',
message varchar2(200) path 'MESSAGE'
)
/
STATUS MESSAGE
---------- ------------------------------
FALSE SOMTHING WENT WRONG!!!
The thing is that the SP is inserting some data to a temporary table
That's a fairly crucial omission. You can't perform an insert or update in a function call from a select. The documentation lists restrictions on functions called from SQL, and goes into more detail in this warning:
Because SQL is a declarative language, rather than an imperative (or procedural) one, you cannot know how many times a function invoked by a SQL statement will run—even if the function is written in PL/SQL, an imperative language.
If the function was allowed to do DML then you would have no control over how many times that DML was performed. If it was doing an insert, for instance, it might try to insert the same row twice and either duplicate data or get a constraint violation.
You could, technically, declare the function with pragma autonomous_transaction, as in this modified db<>fiddle, but that's a horrible hack and one that will probably ultimately cause more problems than it solves, for the reasons shown above. You might get away with it if you only ever make single-row calls as in your example, but even then it isn't guaranteed to work; and even if it works now it might break in the future.
Make a new Sql trigger which watches a table Table_legacyInputOutput. Insert your input in table with Printer id PRINTER_ID = '551555115'
Then trigger will call stored procedure and update table
for O_STATUS and O_MESSAGE .
I think your legacy application can d o insert and select at least. It just cannot call SP and check return parameters
Table_legacyInputOutput structure.
PRINTER O_STATUS O_MESSAGE
Related
CREATE OR REPLACE FUNCTION update()
RETURNS TRIGGER
LANGUAGE PLPGSQL
AS
$$
BEGIN
with name_array as (select jsonb_array_elements(inputs #> '{lists}') AS lists
from random.model
)
select name_array.lists #>> '{name}' as name,
name_array.lists #>> '{value}' as value
from name_array;
IF NEW.value <> OLD.value THEN
INSERT INTO random.model_tracker(userid,modelid,datetime,oldjsoninput,newjsoninput,action)
Values (old.user_id,old.id,now(),
'{"lists": [{"name": "OLD.name"}, {"value": "OLD.value"}]}'
,'{"lists": [{"name": "NEW.name"},{"value": "NEW.value"}]}'
,'UPDATE');
END IF;
RETURN NEW;
END;
$$
Trigger
CREATE TRIGGER update
AFTER UPDATE
ON random.model
FOR EACH ROW
EXECUTE PROCEDURE update();
When i am running inner query, it produces outputs as text for name and value. This function gets created however when i update values on the table i am getting this error:
ERROR: query has no destination for result data
HINT: If you want to discard the results of a SELECT, use PERFORM instead.
CONTEXT: PL/pgSQL function update() line 6 at SQL statement
SQL state: 42601
any select inside of a function need to be placed inside of a variable, that was the error message.
made another version of the json :
CREATE OR REPLACE FUNCTION update()
RETURNS TRIGGER
LANGUAGE PLPGSQL
AS
$$
BEGIN
IF NEW.value <> OLD.value THEN
INSERT INTO random.model_tracker(userid,modelid,datetime,oldjsoninput,newjsoninput,action)
Values (old.user_id,old.id,now(),
(SELECT * FROM (WITH lists (lists ) AS (
WITH q ( name , value ) AS
( SELECT OLD.name , OLD.value ) SELECT array_to_json( ARRAY[row_to_json(q)] ) FROM q
)SELECT row_to_json(list.*) FROM lists
)) ,
(SELECT * FROM (
WITH lists (lists ) AS (
WITH q ( name , value ) AS
( SELECT NEW.name , NEW.value ) SELECT array_to_json( ARRAY[row_to_json(q)] ) FROM q
)SELECT row_to_json(list.*) FROM lists
)) ,'UPDATE');
END IF;
RETURN NEW;
END;
$$
Using PostgreSQL 13.2, wherein a stored procedure (the Requestor) is given a name of a list of stored procedures to run (the job group). All sp's executed this way are coded to write a log record as their last task. I have chosen to pull that 'append log' code from all of the sp's, and instead send back the log record (always a single record) using an INOUT rowtype param, but have run into trouble. In my example below, the requestor sp will load the records returned from the sp's it calls into a temp table shaped like the permanent log table.
That permanent table looks like this:
create table public.job_log (
log_id integer,
event_id integer,
job_id integer,
rows_affected integer);
Any one of the jobs that is executed by the requestor sp might look like this one:
CREATE OR REPLACE procedure public.get_log_rcd(
inout p_log_rcd public.job_log)
LANGUAGE 'plpgsql'
as
$BODY$
declare
v_log_id integer = 40;
v_event_id integer = 698;
v_job_id integer = 45;
v_rows_affected integer = 60;
begin
select
v_log_id
, v_event_id
, v_job_id
, v_rows_affected
into
p_log_rcd.log_id,
p_log_rcd.event_id,
p_log_rcd.job_id,
p_log_rcd.rows_affected;
end;
$BODY$
This sample sp doesn't do anything--it's purpose here is only to simulate initialize of the log parameters to return to caller.
Again, the requestor sp that's going to run jobs like the one above creates a temp table with the same structure as the permanent log:
drop table if exists tmp_log_cache;
create temp table tmp_log_cache as table public.job_log with no data;
If the requestor sp didn't have to do dynamic SQL, it would look something like this block here:
do
$$
declare
big_local public.job_log;
begin
call public.get_log_rcd( big_local );
insert into tmp_log_cache (
log_id
, event_id
, job_id
, rows_affected )
values (
big_local.log_id
, big_local.event_id
, big_local.job_id
, big_local.rows_affected);
end;
$$;
Doing a
select * from tmp_log_cache;
Returns a row containing the 4 column values expected, all is well. But, dynamic execution is required. And, as I'm sure most folks here know, the following dog don't hunt:
do
$$
declare
big_local public.job_log;
v_query_text varchar;
v_job_name varchar = 'public.get_log_rcd';
begin
select 'call ' || v_job_name || '( $1 );'
into v_query_text;
execute v_query_text using big_local::public.job_log;
insert into tmp_log_cache (
log_id
, event_id
, job_id
, rows_affected )
values (
big_local.log_id
, big_local.event_id
, big_local.job_id
, big_local.rows_affected);
end;
$$;
The above dynamic statement executes without error, but the insert statement only has NULL values to work with--a row is inserted, all nulls. Any suggestions warmly welcomed. The sp's that comprise the various job groups could probably have been implemented as functions, although in all cases their primary tasks are to massage, normalize, cleanse telemetry data, not to spit anything out, per se.
Hmm, the documentation states that "parameter symbols (...) only work in SELECT, INSERT, UPDATE, and DELETE commands.", so this probably isn't possible using parameters.
But as a workaround you can build a dynamic DO and include a variable to get the values and the INSERT in there.
DO
$o$
DECLARE
v_query_text varchar;
v_job_name varchar := format('%I.%I',
'public',
'get_log_rcd');
BEGIN
v_query_text := concat('DO ',
'$i$ ',
'DECLARE ',
' big_local public.job_log; ',
'BEGIN ',
' CALL ', v_job_name, '(big_local); ',
' INSERT INTO tmp_log_cache ',
' (log_id, ',
' event_id, ',
' job_id, ',
' rows_affected) ',
' VALUES (big_local.log_id, ',
' big_local.event_id, ',
' big_local.job_id, '
' big_local.rows_affected); ',
'END; ',
'$i$; ');
EXECUTE v_query_text;
END;
$o$;
db<>fiddle
Thanks--I would not have considered the ability to execute a 'do' using execute. It just would not have occurred to me. Well, here's my solution: flip to functions.
With the understanding that my 'Requestor' is only given sp's to run because that's what we had to do with SQL Server and it was reflex, I did the 1-line change needed to flip my example sp above to a function:
CREATE OR REPLACE function public.get_log_rcdf(
inout p_log_rcd public.job_log)
returns public.job_log
LANGUAGE 'plpgsql'
as
$BODY$
declare
v_log_id integer = 40;
v_event_id integer = 698;
v_job_id integer = 45;
v_rows_affected integer = 60;
begin
select
v_log_id
, v_event_id
, v_job_id
, v_rows_affected
into
p_log_rcd.log_id,
p_log_rcd.event_id,
p_log_rcd.job_id,
p_log_rcd.rows_affected;
end;
$BODY$
In fact, the change to a function required the addition of a RETURNS line. Done. Then, the dynamic call was tweaked to a SELECT and the execute modified with an INTO:
do
$$
declare
big_local public.job_log;
v_query_text varchar;
v_job_name varchar = 'public.get_log_rcdf';
begin
select 'select * from ' || v_job_name || '( $1 );'
into v_query_text;
raise info 'SQL text is: %', v_query_text;
execute v_query_text into big_local using big_local;
insert into tmp_log_cache (
log_id
, event_id
, job_id
, rows_affected )
values (
big_local.log_id
, big_local.event_id
, big_local.job_id
, big_local.rows_affected);
end;
$$;
and the process now works exactly as desired. I tidy up my handling of the dynamic function name as illustrated in the first answer, and I think we're done here.
I am getting the following error while creating a stored procedure for testing purpose:
SQL Error [42601]: An unexpected token "DECLARE GLOBAL TEMPORARY TABLE
SESSION" was found following "RSOR WITH RETURN FOR". Expected tokens may include: "".. SQLCODE=-104, SQLSTATE=42601, DRIVER=4.21.29
Code:
CREATE OR REPLACE PROCEDURE Test ( IN GE_OutPutType SMALLINT)
----------------------------------------------------------------------------------------------------
DYNAMIC RESULT SETS 1 LANGUAGE SQL
BEGIN
DECLARE C CURSOR WITH RETURN FOR DECLARE GLOBAL TEMPORARY TABLE
SESSION.TEMP (DATE CHAR(10) NOT NULL,
SALARY DECIMAL(9,
2) ,
COMM DECIMAL(9,
2));
INSERT
INTO
SESSION.TEMP (DATE,
SALARY,
COMM) SELECT
VARCHAR_FORMAT(CURRENT_DATE,
'MM/DD/YYYY'),
10.2,
11.5
FROM
sysibm.sysdummy1
IF GE_OutPutType = 1
BEGIN
SELECT
*
FROM
TEMP
ELSEIF GE_OutPutType = 2 SELECT
'HEADER' CONCAT SPACE(1979) CONCAT 'H'
FROM
sysibm.sysdummy1
END OPEN C;
END
Your syntax is not valid.
You must declare your temporary table independently of your cursor.
You cannot combine these in a single statement.
Use dynamic-SQL features to achieve what you need.
Use instead the format:
Declare c1 cursor with return to caller for Statement1
and
set v_cursor_text = 'select ... from session.temp ; `
then use
prepare Statement1 from v_cursor_text;
and before you exit the stored procedure you need to leave the cursor opened:
open c1;
Do study the Db2 online documentation to learn more about these features.
Here is a small fragment of your procedure showing what I mean:
CREATE OR REPLACE PROCEDURE mytest ( IN GE_OutPutType SMALLINT)
DYNAMIC RESULT SETS 1
LANGUAGE SQL
specific mytest
BEGIN
DECLARE v_cursor_text varchar(1024);
DECLARE C1 CURSOR WITH RETURN FOR Statement1;
DECLARE GLOBAL TEMPORARY TABLE SESSION.TEMP (
DATE CHAR(10) NOT NULL,
SALARY DECIMAL(9,
2) ,
COMM DECIMAL(9,
2))
with replace on commit preserve rows not logged;
INSERT INTO SESSION.TEMP (DATE, SALARY, COMM)
SELECT VARCHAR_FORMAT(CURRENT_DATE, 'MM/DD/YYYY'),
10.2,
11.5
FROM sysibm.sysdummy1 ;
if GE_OutPutType = 1
then
set v_cursor_text = 'select * from session.temp';
end if;
if GE_OutPutType = 2
then
set v_cursor_text = 'select ''header'' concat space(1979) concat ''H'' from sysibm.sysdummy1';
end if;
prepare Statement1 from v_cursor_text;
open c1;
END#
I'm having a function where I would like to get the info from the table based of the parameter.
p_text is something like 'name' , or 'email' or 'birthday' so in my cursist table that would be like calling the select cursist.name , cursist.email or cursist.birthday.
get_cursist_info(p_app => :APP_USER, p_text => 'name')
this is my function:
create or replace function get_cursist_info(p_app in varchar2, p_text in varchar2)
return varchar
is
v_userid varchar(200);
v_text varchar(200);
v_test varchar(200):= p_text;
begin
v_userid := get_cursist_id2(p_app);
select v_test
into v_text
from cursist
where cursist.cursistid = v_userid;
return v_text;
end get_cursist_info;
All it does now is getting the variabele v_test back (name) instead of the cursists name(Dave).
Isnt it possible to use a pl sql variable in the select? I don't really feel like making 8 functions for this.. :)
You can do this without dynamic sql which is less efficient by using CASE clause:
select CASE v_test
WHEN 'name' THEN name
WHEN 'email' THEN email
WHEN 'birthday' THEN birthday
ELSE null
END
into v_text
from cursist
where cursist.cursistid = v_userid;
You need to create your query dynamically:
create or replace function get_cursist_info(p_app in varchar2, p_text in varchar2)
return varchar
is
v_userid varchar2(200);
v_text varchar2(200);
v_test varchar2(200):= p_text;
v_sql varchar2(1000);
begin
v_userid := get_cursist_id2(p_app);
v_sql := 'select cursist.' || v_test || ' from cursist where cursist.cursistid = ' || v_userid;
execute immediate v_sql into v_text;
return v_text;
end get_cursist_info;
Hope that helps.
I am trying to fetch data from remote db by using dblink through function but getting an error "query has no destination for result data". I am using plpgsql language to do the same.
Function:
CREATE OR REPLACE FUNCTION fun()
RETURNS text AS
$$
begin
select dblink_connect(
'port=5432 dbname=test user=postgres password=****');
WITH a AS (
SELECT *
FROM dblink(
'SELECT slno,fname,mname,lname
FROM remote_tbl'
) AS t (slno int, fname text, mname text, lname text)
)
, b AS (
INSERT INTO temptab1
SELECT slno, name
FROM a
)
, c AS (
INSERT INTO temptab2
SELECT slno, name
FROM a
)
INSERT INTO temptab3
SELECT slno, name
FROM a;
select dblink_disconnect();
end;
$$
LANGUAGE plpgsql;
Calling Function:
select fun();
Error: query has no destination for result data
The stored procedure won't just return the result of the last SELECT. You need to actually return the value:
CREATE OR REPLACE FUNCTION fun() RETURNS text AS $$
BEGIN
--- ....
RETURN(SELECT dblink_disconnect());
END
$$ LANGUAGE plpgsql;
You're getting the error because Postgres expects the function to return something of type text, but your function doesn't return anything.
Use a plain SQL function instead of PL/PgSQL, or use SELECT INTO and ordinary RETURN.
Reason for the error you're getting is because there is no return in between your BEGIN and END for example:
BEGIN
update mytable set lastcodeused = to_char(cast(lastcodeused as INTEGER)+1, 'FM999999999999999999') where
classid = classIdVar and appid= appIdInt
RETURNING concat(pageUniqueCode,lastcodeused) as pageUniqueCode
into taskcode;
return taskcode;
END;
If you have this error using a pgplsql procedure or function, and you are sure that the return is defined correctly, there exists a different non-intuitive cause. I needed some time to realize this so I think it is worth sharing. I had a function like this:
CREATE OR REPLACE FUNCTION "db".fn_x(
id integer)
RETURNS TABLE(b_val varchar(100), c_val varchar(100))
LANGUAGE 'plpgsql'
AS $BODY$
DECLARE
var_b_val varchar(100);
var_c_val varchar(100);
BEGIN
select var_b, var_c
-- Missing INTO clause was the cause of the error.
var_b_val, var_c_val
from "db".table_y where y_id = id;
return query(select var_b_val, var_c_val);
END;
$BODY$;
Just adding that missing INTO clause made the function work correctly.
In conclusion, this error can also trigger on silent syntax errors.