Retutring from a function using case - postgresql

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;

Related

Calling a Function from SQL

This is my Function :
CREATE FUNCTION "UpdatePMPM"(nbr_mem_months integer, effectivedate date) RETURNS void
LANGUAGE plpgsql
AS
$$
DECLARE
ym varchar := to_char(effectivedate,'YYYYMM');
BEGIN
FOR r IN 1..nbr_mem_months LOOP
UPDATE elan.pmpm set mbrmonths = mbrmonths+1 where yyyyymm = ym;
effectivedate = effectivedate + interval '1 month';
ym=to_char(effectivedate,'YYYYMM');
END LOOP;
RETURN;
END
$$;
and when I call it manually from pgAdmin client it works perfectly.
Select public."UpdatePMPM"(5, '2016-04-01')
However, I am getting an error when calling it from within a SQL query:
select cast((extract(year from age(case when terminationdate is null then
CURRENT_DATE else terminationdate END ,effectivedate ))) *12 +
(extract(month from age(case when terminationdate is null then
CURRENT_DATE else terminationdate END ,effectivedate )) +1) as integer)
as "mbrmonths" ,effectivedate ,public."UpdatePMPM"(mbrmonths, effectivedate)
from elan.elig
order by 1
ERROR: column "mbrmonths" does not exist
LINE 5: ...s "mbrmonths" ,effectivedate ,public."UpdatePMPM"(mbrmonths,...
Any help would be appreciated.
You cannot use a column alias in the SELECT list to reference another column of the same list.
Either repeat the expression or use a subquery:
SELECT mbrmonths,
effectivedate,
public."UpdatePMPM"(mbrmonths, effectivedate)
FROM (select cast(
(extract(year from age(case when terminationdate is null
then CURRENT_DATE
else terminationdate
END,
effectivedate)
)) *12 +
(extract(month from age(case when terminationdate is null
then CURRENT_DATE
else terminationdate
END,
effectivedate
)) +1
as integer) as "mbrmonths",
effectivedate,
from elan.elig) AS subq
order by 1;
The type of second argument is text, not date.
The Correct call is
Select public."UpdatePMPM"(5, '2016-04-01'::date)

Postgres function returns ERROR of no destination

Here is my function for my DB:
create function calculate_metrics()
returns table
(
fail_count int,
hold_pay_count int
)
as
$$
declare
total_count int;
fail_count int;
hold_pay_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
from bundle where updated_at > (now() - interval '1 day');
if total_count > 0
then
return query select fail_count, hold_pay_count;
end if;
end;
$$ language plpgsql;
When I'm trying to get select calculate_metrics(), I'm getting the error:
SQL Error [42601]: ERROR: query has no destination for result data
Suggestion: If you want to discard the results of a SELECT, use PERFORM instead.
Where: PL/pgSQL function calculate_metrics() line 10 at SQL statement
The function is successfully create, and I have already insert data which consists the conditions of the requests. Where is my mistake?
Naming the column is not enough, you need to save the output into your variables
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 day');

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

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.

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;

How to call Postgres function returning SETOF record?

I have written the following function:
-- Gets stats for all markets
CREATE OR REPLACE FUNCTION GetMarketStats (
)
RETURNS SETOF record
AS
$$
BEGIN
SELECT 'R approved offer' AS Metric,
SUM(CASE WHEN M.MarketName = 'A+' AND M.Term = 24 THEN LO.Amount ELSE 0 end) AS MarketAPlus24,
SUM(CASE WHEN M.MarketName = 'A+' AND M.Term = 36 THEN LO.Amount ELSE 0 end) AS MarketAPlus36,
SUM(CASE WHEN M.MarketName = 'A' AND M.Term = 24 THEN LO.Amount ELSE 0 end) AS MarketA24,
SUM(CASE WHEN M.MarketName = 'A' AND M.Term = 36 THEN LO.Amount ELSE 0 end) AS MarketA36,
SUM(CASE WHEN M.MarketName = 'B' AND M.Term = 24 THEN LO.Amount ELSE 0 end) AS MarketB24,
SUM(CASE WHEN M.MarketName = 'B' AND M.Term = 36 THEN LO.Amount ELSE 0 end) AS MarketB36
FROM "Market" M
INNER JOIN "Listing" L ON L.MarketID = M.MarketID
INNER JOIN "ListingOffer" LO ON L.ListingID = LO.ListingID;
END
$$
LANGUAGE plpgsql;
And when trying to call it like this...
select * from GetMarketStats() AS (
Metric VARCHAR(50),
MarketAPlus24 INT,
MarketAPlus36 INT,
MarketA24 INT,
MarketA36 INT,
MarketB24 INT,
MarketB36 INT);
I get an 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 "getmarketstats" line 2 at SQL statement
I don't understand this output. I've tried using perform too, but I thought one only had to use that if the function doesn't return anything.
Your function doesn't maken sense, it doesn't return anything. It looks like a VIEW, so why don't you create a view?
Edit:
You have use the OUT parameters or RETURN TABLE() with the parameters:
CREATE OR REPLACE FUNCTION my_func(OUT o_id INT, OUT o_bar TEXT)
RETURNS SETOF RECORD AS
$$
BEGIN
RETURN QUERY SELECT id, bar FROM foo;
END;
$$
LANGUAGE plpgsql;
SELECT * FROM my_func();