PostgreSQL Error: set-valued function called in context that connot accept a set - postgresql

I'm trying to convert a T-SQL procedure to PL/PGSQL procedure,
When i run my function i have this following error: "set-valued function called in context that cannot accept a set".
without the "RETURN QUERY" i will have an error "query has no destination for result data" that why i added it
Find below the error message and my function
create or replace function public.UP_GetCumulPerformancesParPortefeuille(strMatricule VARCHAR(20), strDevise varchar(3), dateDebut DATE) returns setof record
language plpgsql
as $$
BEGIN
DECLARE NO_PTF_V INT;
DT_CRS_V DATE;
PC_PRF_V FLOAT;
ResultF FLOAT = 0;
PreviousResult FLOAT = 0;
PreviousCPA INT = 0;
Performances public.performancestb;
PerfCumul public.perfcumultb;
curseur CURSOR FOR
SELECT NO_PTF, DT_CRS, SUM(PC_PRF * MT_DEM)
FROM (
SELECT D.NO_PTF, P.DT_CRS, P.PC_PRF
, D.MT_DEM/100 as MT_DEM
FROM public.TB_Demande D
INNER JOIN
public.performancestb P ON D.ID_CPA = P.ID_CPA
AND D.MC_UTL = strMatricule
) Q
WHERE DT_CRS >= dateDebut
GROUP BY NO_PTF, DT_CRS
ORDER BY NO_PTF, DT_CRS;
-- Chargement de l'historique des performances
--
BEGIN
INSERT INTO public.performancestb (ID_CPA, DT_CRS, PC_PRF)
SELECT ID_CPA, DT_CRS, PC_PRF FROM public.UF_GetHistoriquePerformances(strDevise);
OPEN curseur;
FETCH NEXT FROM curseur
INTO NO_PTF_V, DT_CRS_V, PC_PRF_V;
WHILE(found) loop
IF PreviousCPA = 0 OR PreviousCPA <> NO_PTF_V then
PreviousResult := 0;
PreviousCPA := NO_PTF_V;
end if;
ResultF := PreviousResult + PC_PRF_V * (PreviousResult + 100);
PreviousResult := ResultF;
INSERT INTO public.perfcumultb (NO_PTF, DT_CRS, MT_PRF)
VALUES (NO_PTF_V, DT_CRS_V, ResultF);
FETCH NEXT FROM curseur
INTO NO_PTF_V, DT_CRS_V, PC_PRF_V;
end loop;
CLOSE curseur;
Return query(SELECT NO_PTF, DT_CRS, MT_PRF, MT_PRF + 100 as MT_PRF_BSE_100
, CASE WHEN DT_CRS = FIRST_VALUE(DT_CRS) OVER (PARTITION BY NO_PTF ORDER BY NO_PTF,DT_CRS)
THEN 0
ELSE ((100 + MT_PRF) / (100 + LAG(MT_PRF, 1) OVER (PARTITION BY NO_PTF ORDER BY NO_PTF, DT_CRS))) - 1
END
* 100 AS MT_VOL
, CASE WHEN MT_PRF = FIRST_VALUE(MT_PRF) OVER (PARTITION BY NO_PTF ORDER BY NO_PTF, DT_CRS)
AND MT_PRF < 0
THEN MT_PRF
ELSE ((MT_PRF + 100) / MAX(MT_PRF + 100) OVER (PARTITION BY NO_PTF ORDER BY NO_PTF, DT_CRS
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) - 1 )
* 100
END AS MT_MAX_DDO
FROM public.perfcumultb);
end;
END;
$$;

I finally found the solution by creating an intermediate table as return type instead of using setof record as return type.
Find below the solution
CREATE OR REPLACE FUNCTION public.up_getcumulperformancesparportefeuille(strmatricule character varying, strdevise character varying, datedebut date) returns setof resultfinal
LANGUAGE plpgsql
AS $function$
BEGIN
DECLARE NO_PTF_V INT;
DT_CRS_V DATE;
PC_PRF_V FLOAT;
ResultF FLOAT = 0;
PreviousResult FLOAT = 0;
PreviousCPA INT = 0;
Performances public.performancestb;
PerfCumul public.perfcumultb;
curseur CURSOR FOR
SELECT NO_PTF, DT_CRS, SUM(PC_PRF * MT_DEM)
FROM (
SELECT D.NO_PTF, P.DT_CRS, P.PC_PRF
, D.MT_DEM/100 as MT_DEM
FROM public.TB_Demande D
INNER JOIN
public.performancestb P ON D.ID_CPA = P.ID_CPA
AND D.MC_UTL = strMatricule
) Q
WHERE DT_CRS >= dateDebut
GROUP BY NO_PTF, DT_CRS
ORDER BY NO_PTF, DT_CRS;
-- Chargement de l'historique des performances
BEGIN
INSERT INTO public.performancestb (ID_CPA, DT_CRS, PC_PRF)
SELECT ID_CPA, DT_CRS, PC_PRF FROM public.UF_GetHistoriquePerformances(strDevise);
OPEN curseur;
FETCH NEXT FROM curseur
INTO NO_PTF_V, DT_CRS_V, PC_PRF_V;
WHILE(found) loop
IF PreviousCPA = 0 OR PreviousCPA <> NO_PTF_V then
PreviousResult := 0;
PreviousCPA := NO_PTF_V;
end if;
ResultF := PreviousResult + PC_PRF_V * (PreviousResult + 100);
PreviousResult := ResultF;
INSERT INTO public.perfcumultb (NO_PTF, DT_CRS, MT_PRF)
VALUES (NO_PTF_V, DT_CRS_V, ResultF);
FETCH NEXT FROM curseur
INTO NO_PTF_V, DT_CRS_V, PC_PRF_V;
end loop;
CLOSE curseur;
return query(SELECT NO_PTF, DT_CRS, MT_PRF, MT_PRF + 100 as MT_PRF_BSE_100
, CASE WHEN DT_CRS = FIRST_VALUE(DT_CRS) OVER (PARTITION BY NO_PTF ORDER BY NO_PTF,DT_CRS)
THEN 0
ELSE ((100 + MT_PRF) / (100 + LAG(MT_PRF, 1) OVER (PARTITION BY NO_PTF ORDER BY NO_PTF, DT_CRS))) - 1
END
* 100 AS MT_VOL
, CASE WHEN MT_PRF = FIRST_VALUE(MT_PRF) OVER (PARTITION BY NO_PTF ORDER BY NO_PTF, DT_CRS)
AND MT_PRF < 0
THEN MT_PRF
ELSE ((MT_PRF + 100) / MAX(MT_PRF + 100) OVER (PARTITION BY NO_PTF ORDER BY NO_PTF, DT_CRS
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) - 1 )
* 100
END AS MT_MAX_DDO
FROM public.perfcumultb);
end;
END;
$function$;

General advise - when you come from MS SQL world, forget all and start with reading documentation - it has only 50 pages related to PLpgSQL. Almost everything is different in Postgres.
Postgres doesn't supports unbound queries - every query should to have some target - or you can use PERFORM statement, that throw result of query. If you want to push query's result to output, you should to use RETURN QUERY statement.
Syntax of RETURN QUERY is RETURN QUERY query. Parenthesis there are useless. But in your example is visible MS SQL pattern that is not used in Postgres. There (MSSQL) you fill temp table, and on the end you read this table. Porting this pattern to Postgres is antipattern - instead filling aux table, you should to use RETURN NEXT and return row immediately. Maybe you need it (I don't know because you use window functions in final query, but there will be significant overhead).
Instead manual fetching from cursor and manipulation with cursor, you can use FOR IN cursor statement.
Your function returns undefined recordset, because you used returns setof record. Then you should to call this function with special syntax - SELECT * FROM fx(args) as (colname typename, ...). Better to use OUT arguments, and then you don't need to specify result structure in query.
CREATE OR REPLACE FUNCTION foo(IN nrows int, OUT a int, OUT b int)
RETURNS SETOF RECORD AS $$
BEGIN
RETURN QUERY SELECT i, i+1 FROM generate_series(1,nrows) g(i);
END;
$$ LANGUAGE plpgsql;
SELECT * FROM foo(3);
┌───┬───┐
│ a │ b │
╞═══╪═══╡
│ 1 │ 2 │
│ 2 │ 3 │
│ 3 │ 4 │
└───┴───┘
(3 rows)
Please, start by reading documentation - T-SQL is really different language and environment, and moving to PLpgSQL is not intuitive.

Related

How do I change a trigger fucntion in mysql to postgresql trigger

I have a trigger function in MySQL written as such:
CREATE TRIGGER after_customers_insert
BEFORE INSERT
ON customers FOR EACH ROW
BEGIN
SET #created := (SELECT IFNULL(created_at, NOW()) FROM customers ORDER BY created_at
DESC LIMIT 1);
IF YEAR(#created) <> YEAR(NEw.created_at) THEN
SET NEW.Newid := 1;
ELSE
SET NEW.Newid := (SELECT IFNULL(MAX(Newid),0) + 1 FROM customers);
END IF;
SET NEW.custom_id = CONCAT('KR_',DATE_FORMAT(now(), '%y'),'_',LPAD(NEW.Newid,3,'0'));
END
I tried to convert in Postgres this:
CREATE OR REPLACE FUNCTION after_customers_insert()
RETURNS trigger AS
$BODY$
BEGIN
SET #created := (SELECT IFNULL(created_at, NOW()) FROM customers ORDER BY created_at DESC LIMIT 1);
IF YEAR(#created) <> YEAR(NEw.created_at) THEN
SET NEW.Newid := 1;
ELSE
SET NEW.Newid := (SELECT IFNULL(MAX(Newid),0) + 1 FROM customers);
END IF;
SET NEW.custom_id = CONCAT('KR_',DATE_FORMAT(now(), '%y'),'_',LPAD(NEW.Newid,3,'0'));
END
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
but without success. Any help please??

Retutring from a function using case

Here is my postgres function:
create or replace function fail_hold_pay_count_metric()
returns table (
fail_count int,
hold_pay_count int
)
as
$$
declare
total_count int;
fail_count int;
hold_pay_count int;
required_rows_count int;
percent_count int;
begin
select count(1) as total_count,
sum(case when status = 'FAIL' then 1 else 0 end) as fail_count,
sum(case when status = 'HOLD_PAY' then 1 else 0 end) as hold_pay_count
into total_count, fail_count, hold_pay_count
from bundle where updated_at > (now() - interval '1 year');
if total_count > 10
then
required_rows_count := (select fail_count + hold_pay_count);
percent_count := (select (required_rows_count / total_count * 100));
end if;
return query (select case when (percent_count > 50) then (fail_count, hold_pay_count) else (0, 0) end);
end;
$$ language plpgsql;
I need to return (0,0) if percent_count was lower than 50. My function doesn't work. Where did I make a mistake?
You are returning a single column of a row type instead of a row of two int types.
Please try this, instead:
return query select case when percent_count > 50 then fail_count else 0 end,
case when percent_count > 50 then hold_pay_count else 0 end;

How to compare two table value using if condition in function of Postgres

create or replace function trace.get_latest_exception_custom_msg(id varchar)
returns varchar
language plpgsql
as $$
declare
msg varchar ;
begin
perform t1.message, t1.created_time from table_1 t1 where t1.id = id order by t1.created_time desc limit 1;
perform t2.message, t2.created_time from table_2 t2 where t2.id = id order by t2.created_time desc limit 1;
if date(t1.created_time ) >= date(t2.created_time) then msg= t1.message;
elsif d date(t1.created_time ) < date(t2.created_time) then msg= t1.message;
else msg =t1.message;
end if;
return msg;
end;
while i call this function it give error ERROR: missing FROM-clause entry for table "t_1
You need to store the result of the two SELECT queries into variables in order to be able to be able to use them in an IF statement.
Your IF statement is also a bit confusing as all three parts assign the same value to msg. I assume that you want to use t2.message at least in one case.
create or replace function trace.get_latest_exception_custom_msg(p_id varchar)
returns varchar
language plpgsql
as
$$
declare
t1_msg varchar;
t1_created date;
t2_msg varchar;
t2_created date;
msg varchar;
begin
select t1.message, t1.created_time::date
into t1_msg, t1_created
from table_1 t1
where t1.id = p_id
order by t1.created_time desc
limit 1;
select t2.message, t2.created_time::date
into t2_msg, t2_created
from table_2 t2
where t2.id = p_id
order by t2.created_time desc
limit 1;
if t1_created >= t2_created then
msg := t1_msg;
elsif t1_created < t2_created then
msg := t2_msg; --<< ???
else
-- this can only happen if one (or both) of the DATEs is NULL.
msg := t1_msg;
end if;
return msg;
end;
$$

get rank of players' height in plpgsql

We are going to have the rank of height, but I got 0 for all players
I convert the feet and inches into cm first, and use the sample code teacher gave us.
Here is my code:
CREATE OR REPLACE FUNCTION player_height_rank (firstname VARCHAR, lastname VARCHAR) RETURNS int AS $$
DECLARE
rank INTEGER:= 0;
offset INTEGER:= 0;
tempValue FLOAT:= NULL;
r record;
BEGIN
FOR r IN SELECT ((p.h_feet * 30.48) + (p.h_inches * 2.54)) AS height, p.firstname, p.lastname
FROM players p
ORDER BY ((p.h_feet * 30.48) + (p.h_inches * 2.54)) DESC, p.firstname, p.lastname
LOOP
IF r.height = tempValue then
offset := offset + 1;
ELSE
rank := rank + offset + 1;
offset := 0;
tempValue := r.height;
END IF;
IF r.firstname = $1 AND r.lastname = $2 THEN
RETURN rank;
END IF;
END LOOP;
-- not in DB
RETURN 0;
END;
$$ LANGUAGE plpgsql;
--select * from player_height_rank('Ming', 'Yao');
Your function works fine for me if I correct two bugs:
One of your commas is not really a comma, but a “fullwidth comma”, UNICODE code point FF0C, which causes a syntax error.
You have a variable name offset, which causes SQL syntax errors because it is a reserved key word in SQL. If you really need to use that name, you have to enclose it in double quotes (") throughout, but it is better to choose a different name.
The reason this causes a problem is that an assignment like offset := offset + 1; in PL/pgSQL is translated into an SQL statement like SELECT offset + 1 INTO offset;.
You can do the whole thing in a single SQL query, which is more efficient:
SELECT rank
FROM (SELECT firstname,
lastname,
rank() OVER (ORDER BY h_feet + 12 * h_inches)
FROM players
) dummy
WHERE firstname = 'Ming'
AND lastname = 'Yao';

postgres function - declare variable with select statement

i have modifed my function, but i have problems with declare variables. I used postgres 8.4. Have someone an idea?
Function:
CREATE OR REPLACE FUNCTION requestcounterid(_mindate timestamptz, _maxdate timestamptz)
RETURNS TABLE (kategorien text, requestcounter int) AS
$func$
DECLARE
_minid bigint;
_maxid bigint;
BEGIN
_minid := (SELECT id from tablename where starttime >= $1 ORDER BY tablename2 ASC LIMIT 1);
_maxid := (SELECT id from tablename where starttime < $2 ORDER BY tablename2 DESC LIMIT 1);
SELECT CASE WHEN duration <= 10000000 THEN '00-01 sec'::text
WHEN duration <= 40000000 THEN '01-04 sec'
WHEN duration <= 100000000 THEN '04-10 sec'
WHEN duration <= 300000000 THEN '10-30 sec'
WHEN duration <= 600000000 THEN '30-60 sec'
ELSE 'more than 60 sec' END
, count(*)::int
FROM tablename
WHERE id >= _minid and id <= _maxid
GROUP BY 1
ORDER BY 1;
END;
$func$ LANGUAGE plpgsql;
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 "requestcounterid" line 11 at SQL statement
Regrads
Now it workes:
CREATE OR REPLACE FUNCTION requestcounterid(_mindate timestamptz, _maxdate timestamptz)
RETURNS TABLE (kategorien text, requestcounter int) AS
$func$
DECLARE
_minid bigint;
_maxid bigint;
BEGIN
SELECT id INTO _minid from tablename where starttime >= $1 ORDER BY starttime ASC LIMIT 1;
SELECT id INTO _maxid from tablename where starttime < $2 ORDER BY starttime DESC LIMIT 1;
Return Query SELECT CASE WHEN duration <= 10000000 THEN '00-01 sec'::text
WHEN duration <= 40000000 THEN '01-04 sec'
WHEN duration <= 100000000 THEN '04-10 sec'
WHEN duration <= 300000000 THEN '10-30 sec'
WHEN duration <= 600000000 THEN '30-60 sec'
ELSE 'more than 60 sec' END
, count(*)::int
FROM tablename
WHERE id >= _minid and id <= _maxid
GROUP BY 1
ORDER BY 1;
END;
$func$ LANGUAGE plpgsql;