get rank of players' height in plpgsql - postgresql

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

Related

In psql how to run a Loop for a Select query with CTEs and get the output shown in read-only db?

EDIT: I am creating a new question as per suggestion from sticky bit.
I have a query with CTEs to output results for several values (1 to 12). Below is a simplified example. Running it I got the following 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 inline_code_block line 8 at SQL statement
I can't output the result of Select in a table. How can I solve this problem?
DO $$
DECLARE r integer;
BEGIN
r := 1;
WHILE r <= 2
LOOP
r := r + 1;
WITH params AS (
SELECT r AS rownumber
),
time AS (
SELECT id
FROM params, analysis
ORDER BY date DESC
LIMIT 1
OFFSET (SELECT rownumber - 1 from params)
)
SELECT * FROM time;
END LOOP;
END; $$;
An example of what I mentioned in my comment:
DO $$
DECLARE
r integer;
int_var integer;
BEGIN
r := 1;
WHILE r <= 12 LOOP
WITH params AS (
SELECT r AS rownumber
),
time AS (
SELECT id
FROM params, analysis
ORDER BY date DESC
LIMIT 1
OFFSET (SELECT rownumber - 1 from params)
)
SELECT INTO int_var id FROM time;
RAISE NOTICE 'id: %', int_var;
r := r + 1;
END LOOP;
END; $$;
You can't RETURN a value from a DO function, but you can RAISE NOTICE a value as above. The SELECT INTO will eliminate the error as you are giving the SELECT a destination(the int_var) for its output. NOTE: SELECT INTO inside plpgsql is different then the same command outside. Outside it is equivalent to CREATE TABLE AS.

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.

INTERVAL add i day is not working in postgresql

I have a table like this:
CREATE TABLE DateInsert(
DateInsert timestamp without time zone,
DateInt integer NOT NULL
);
I want insert list day from 2018-01-01 to 2045-05-18 but it give me an erro
"invalid input syntax for type interval:"
CREATE OR REPLACE FUNCTION insertdate() RETURNS integer AS $$
DECLARE i integer := 0;
d timestamp without time zone := '2018-01-01';
di integer := 0;
BEGIN
while i <10000
LOOP
d := d + INTERVAL ''+ i::character varying + ' day';
di := to_char(d , 'yyyymmdd')::int;
insert into DateInsert(DateInsert,DateInt) values(d, di);
i := i+1;
END LOOP ;
return i;
END;
$$ LANGUAGE plpgsql;
How can I insert to db with timestamp increase 1 in n day loop?
Code In sql server has been working.
declare #i int=0
declare #d datetime
declare #di int = 0
while #i <10000
begin
set #d = DATEADD(DAY, #i, '2018-01-01')
set #di = cast(CONVERT(VARCHAR(10), #d, 112) as int)
insert into DateInsert(DateInsert,DateInt) values(#d, #di)
set #i = #i+1
end
The concatenation operator is || not +. And the prefixed form doesn't seem to like anything else than literals. But you can cast the concatenation expression.
So changing
...
d := d + INTERVAL ''+ i::character varying + ' day';
...
to
...
d := d + (i || ' day')::interval;
...
should work for you.

ERROR: missing "LOOP" at end of SQL expression

ERROR: missing "LOOP" at end of SQL expression
CONTEXT: compilation of PL/pgSQL function "player_height_rank" near line 9
Here is my code:
CREATE OR REPLACE FUNCTION player_height_rank (irstname VARCHAR, lastname VARCHAR) RETURNS int AS $$
DECLARE
rank INTEGER := 0;
offset INTEGER := 0;
tempValue INTEGER := 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
WHERE p.firstname = $1 AND p.lastname = $2;
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.lastname = $4 AND r.lastname = $3 THEN
RETURN rank;
END IF;
END LOOP;
-- not in DB
RETURN -1;
END;
$$ LANGUAGE plpgsql;
In your WHERE clause semicolon is superfluous
WHERE p.firstname = $1 AND p.lastname = $2; -- delete semicolon
correct that part and try again.

How to return number of iterations in for loop? PostgreSQL

I'm trying to return the number of times a for loop in postgreSQL has iterated.
table:
players(season_year,firstname, lastname, season_win, season_loss, playoff_win, playoff_loss)
What I'm attempting to do is use an aggregate to see which player has the most overall season wins SUM(p.season_win + p.playoff_win - p.season_loss - p.playoff_loss). Using this aggregate and the season year, I want to return what rank player with firstname = 'x' lastname = 'y' is in the win/loss differential. However, I cannot seem to understand how the for loop works.
Here is what I've attempted with what I've looked up online:
CREATE OR REPLACE FUNCTION get_player_rank(year INTEGER, firstn VARCHAR, lastn VARCHAR) RETURNS INTEGER AS $$
DECLARE player_rank INTEGER;
BEGIN
FOR player_rank IN
SELECT p.season_year, p.lastname, p.firstname, SUM(season_win + playoff_win - season_loss - playoff_loss) as total_wins
FROM player p
GROUP BY p.year, p.lastname, p.firstname
WHERE p.season_year = year LOOP
IF(firstn = p.firstname AND lastn = p.lastname) THEN
RETURN player_rank;
END IF
END LOOP
END
$$ LANGUAGE plpgsql;
I really appreciate any help I can get!
Thanks!
Try something like this:
-- ...
RETURN NEXT player_rank; -- return current row of SELECT
END IF
END LOOP
-- ...
See this for more info on return: http://www.postgresql.org/docs/9.1/static/plpgsql-control-structures.html