PostgreSQL 函数中的更新似乎不起作用
update in PostgreSQL function seems to not work
要学习如何在 PostgreSQL 中使用函数,我们必须用以下语句做一个练习:
我们有一个 table Skieur(滑雪者),Competition 和一个 table Classement(滑雪者在比赛中的排名)。
我们还有第四个 table 叫做 Penalize(它告诉我们滑雪者在哪场比赛中作弊)。
练习的目标是编写一个函数(如果需要的话,可以编写更多),将所有作弊者滑雪者置于他们作弊的比赛的最后排名,我们必须更新所有作弊者的排名其他玩家。
这里有不同的table和插入:
CREATE TABLE skieur(
noSkieur serial,
nomSkieur VARCHAR,
idSpecialite INT,
idStation INT,
PRIMARY KEY (noSkieur),
CONSTRAINT fk_skieur_specialite
FOREIGN KEY (idSpecialite) REFERENCES specialite(idSpecialite),
CONSTRAINT fk_skieur_station
FOREIGN KEY (idStation) REFERENCES station(idStation)
);
INSERT INTO skieur VALUES(default,'skieur1',1,1);
INSERT INTO skieur VALUES(default,'skieur2',1,2);
INSERT INTO skieur VALUES(default,'skieur3',2,1);
INSERT INTO skieur VALUES(default,'skieur4',2,3);
INSERT INTO skieur VALUES(default,'skieur5',2,1);
INSERT INTO skieur VALUES(default,'skieur6',3,2);
CREATE TABLE competition(
idCompet serial,
libelleCompet VARCHAR,
dateCompet DATE,
idStation INT,
PRIMARY KEY (idCompet),
CONSTRAINT fk_competition_station
FOREIGN KEY (idStation) REFERENCES station(idStation)
);
INSERT INTO competition VALUES(default, 'compet1','2014-09-01',1);
INSERT INTO competition VALUES(default, 'compet2','2014-09-02',1);
INSERT INTO competition VALUES(default, 'compet3','2014-09-03',2);
INSERT INTO competition VALUES(default, 'compet4','2014-09-04',2);
INSERT INTO competition VALUES(default, 'compet5','2014-09-05',2);
INSERT INTO competition VALUES(default, 'compet6','2014-09-06',3);
CREATE TABLE classement(
noSkieur INT,
idCompet INT,
classement INT,
PRIMARY KEY(noSkieur, idCompet),
CONSTRAINT fk_classement_skieur
FOREIGN KEY (noSkieur) REFERENCES skieur(noSkieur),
CONSTRAINT fk_classement_competition
FOREIGN KEY (idCompet) REFERENCES competition(idCompet)
);
INSERT INTO classement VALUES(1,1,1);
INSERT INTO classement VALUES(2,1,2);
INSERT INTO classement VALUES(3,1,3);
INSERT INTO classement VALUES(4,1,4);
INSERT INTO classement VALUES(1,2,1);
INSERT INTO classement VALUES(2,2,2);
INSERT INTO classement VALUES(3,2,3);
INSERT INTO classement VALUES(4,2,4);
INSERT INTO classement VALUES(5,2,5);
INSERT INTO classement VALUES(6,3,1);
INSERT INTO classement VALUES(1,3,2);
CREATE TABLE penalise(
noSkieur INT,
idCompet INT,
PRIMARY KEY (noSkieur, idCompet),
CONSTRAINT fk_penalise_competition
FOREIGN KEY (idCompet) REFERENCES competition(idCompet),
CONSTRAINT fk_penalise_skieur
FOREIGN KEY (noSkieur) REFERENCES skieur(noSkieur)
);
INSERT INTO penalise VALUES(1, 1);
为了写函数,我是这样想的:
我在我迭代的所有作弊者上都有一个光标。
我用比赛中的玩家数量更新作弊者的排名,把他放在最后。
之后,我号召其他人将作弊者位置之间的所有其他玩家排名递减,直到结束或另一个作弊者。
这是我写的代码:
-- Decrement ranking function
CREATE OR REPLACE FUNCTION decrement_ranking(ranking_start integer, id_competition integer)
RETURNS integer
AS $$
DECLARE
nb_modification integer := 0;
c CURSOR FOR
SELECT c.noSkieur, c.classement
FROM classement AS c
WHERE c.idCompet = id_competition AND c.classement > ranking_start;
competiter_ranking integer;
BEGIN
FOR c_data IN c
LOOP
-- while the competiter is not a cheater
CONTINUE WHEN c_data.noSkieur NOT IN (SELECT noSkieur FROM penalise WHERE idCompet = id_competition);
-- get current competiter ranking
SELECT c.classement INTO competiter_ranking
FROM classement AS c
WHERE c.noSkieur = c_data.noSkieur AND idCompet = id_competition;
-- decrement ranking
UPDATE classement
SET classement = competiter_ranking - 1
WHERE c.noSkieur = c_data.noSkieur AND idCompet = id_competition;
nb_modification := nb_modification + 1;
END LOOP;
RETURN nb_modification;
END;
$$ LANGUAGE plpgsql;
-- Main function
CREATE OR REPLACE FUNCTION declasse_penalise()
RETURNS integer
AS $$
DECLARE
nb_modification integer := 0;
c CURSOR FOR
SELECT p.noSkieur, p.idCompet
FROM penalise AS p;
no_skieur_pen integer;
id_compet_cheat integer;
nb_competiters integer;
ranking_cheater integer;
BEGIN
FOR c_data IN c
LOOP
no_skieur_pen := c_data.noSkieur;
id_compet_cheat := c_data.idCompet;
-- get ranking cheater
SELECT cl.classement INTO ranking_cheater
FROM classement AS cl
WHERE cl.noSkieur = no_skieur_pen AND cl.idCompet = id_compet_cheat;
-- put cheater in lowest ranking = number of competiters
SELECT COUNT(sk.noSkieur) INTO nb_competiters
FROM skieur as sk
INNER JOIN classement AS cl ON cl.idCompet = id_compet_cheat;
UPDATE classement
SET classement = nb_competiters
WHERE noSkieur = no_skieur_pen AND idCompet = id_compet_cheat;
-- decrement all other ranking: from cheat player rank to before the next cheater (if there is one)
nb_modification := nb_modification + decrement_ranking(ranking_cheater, id_compet_cheat);
END LOOP;
RETURN nb_modification;
END;
$$ LANGUAGE plpgsql;
现在我的问题是:我的代码没有编译错误,但它没有像我预期的那样更新。
你知道为什么吗?
我想我明白了你想要什么,但我无法弄清楚函数 - 至少通读了几遍。但我可以告诉你,你正在做很多工作。使用 SQL 时 - 不管特定的 DBMS 让 SQL 做尽可能多的工作。如果顺其自然,它确实是一个举重运动员。你表示你需要一个循环。所以我会给你一个对作弊者的迭代(惩罚);但有所扩大。
create or replace function declasse_penalise()
returns void
language plpgsql
as $$
declare
-- get cheater, competition, cheater's finish place, and number of competitors in competition
cheaters cursor for
select p.noskieur, p.idcompet,c. classement, competors
from penalise as p
join classement c on (p.noskieur, p.idcompet) = (c.noskieur, c.idcompet)
join (select idcompet, max(classement) competors
from classement
group by idcompet
) com
on p.idcompet = com.idcompet;
cheater record;
begin
for cheater in cheaters -- for each cheater
loop
update classement
set classement = case when noskieur = cheater.noskieur -- cheater, set finish position to last
then cheater.competors
when classement > cheater.classement -- finished after cheater, move position to top
then classement - 1
else classement -- finished before cheater, leave position the same
end
where idcompet = cheater.idcompet; -- for particular competition only
end loop;
end;
$$;
-- test
select * from declasse_penalise2();
select * from classement order by idcompet, classement;
我认为这可以满足您提供的数据的要求 - 但您应该进行广泛的测试。只有一行进行测试是不够的。在同一场比赛中至少有 1 个测试用例需要 2 个作弊者。发生这种情况时可能会出现问题,因为每个人都有相同的完成位置,并且可能会跳过某些位置。
要学习如何在 PostgreSQL 中使用函数,我们必须用以下语句做一个练习:
我们有一个 table Skieur(滑雪者),Competition 和一个 table Classement(滑雪者在比赛中的排名)。 我们还有第四个 table 叫做 Penalize(它告诉我们滑雪者在哪场比赛中作弊)。
练习的目标是编写一个函数(如果需要的话,可以编写更多),将所有作弊者滑雪者置于他们作弊的比赛的最后排名,我们必须更新所有作弊者的排名其他玩家。
这里有不同的table和插入:
CREATE TABLE skieur(
noSkieur serial,
nomSkieur VARCHAR,
idSpecialite INT,
idStation INT,
PRIMARY KEY (noSkieur),
CONSTRAINT fk_skieur_specialite
FOREIGN KEY (idSpecialite) REFERENCES specialite(idSpecialite),
CONSTRAINT fk_skieur_station
FOREIGN KEY (idStation) REFERENCES station(idStation)
);
INSERT INTO skieur VALUES(default,'skieur1',1,1);
INSERT INTO skieur VALUES(default,'skieur2',1,2);
INSERT INTO skieur VALUES(default,'skieur3',2,1);
INSERT INTO skieur VALUES(default,'skieur4',2,3);
INSERT INTO skieur VALUES(default,'skieur5',2,1);
INSERT INTO skieur VALUES(default,'skieur6',3,2);
CREATE TABLE competition(
idCompet serial,
libelleCompet VARCHAR,
dateCompet DATE,
idStation INT,
PRIMARY KEY (idCompet),
CONSTRAINT fk_competition_station
FOREIGN KEY (idStation) REFERENCES station(idStation)
);
INSERT INTO competition VALUES(default, 'compet1','2014-09-01',1);
INSERT INTO competition VALUES(default, 'compet2','2014-09-02',1);
INSERT INTO competition VALUES(default, 'compet3','2014-09-03',2);
INSERT INTO competition VALUES(default, 'compet4','2014-09-04',2);
INSERT INTO competition VALUES(default, 'compet5','2014-09-05',2);
INSERT INTO competition VALUES(default, 'compet6','2014-09-06',3);
CREATE TABLE classement(
noSkieur INT,
idCompet INT,
classement INT,
PRIMARY KEY(noSkieur, idCompet),
CONSTRAINT fk_classement_skieur
FOREIGN KEY (noSkieur) REFERENCES skieur(noSkieur),
CONSTRAINT fk_classement_competition
FOREIGN KEY (idCompet) REFERENCES competition(idCompet)
);
INSERT INTO classement VALUES(1,1,1);
INSERT INTO classement VALUES(2,1,2);
INSERT INTO classement VALUES(3,1,3);
INSERT INTO classement VALUES(4,1,4);
INSERT INTO classement VALUES(1,2,1);
INSERT INTO classement VALUES(2,2,2);
INSERT INTO classement VALUES(3,2,3);
INSERT INTO classement VALUES(4,2,4);
INSERT INTO classement VALUES(5,2,5);
INSERT INTO classement VALUES(6,3,1);
INSERT INTO classement VALUES(1,3,2);
CREATE TABLE penalise(
noSkieur INT,
idCompet INT,
PRIMARY KEY (noSkieur, idCompet),
CONSTRAINT fk_penalise_competition
FOREIGN KEY (idCompet) REFERENCES competition(idCompet),
CONSTRAINT fk_penalise_skieur
FOREIGN KEY (noSkieur) REFERENCES skieur(noSkieur)
);
INSERT INTO penalise VALUES(1, 1);
为了写函数,我是这样想的:
我在我迭代的所有作弊者上都有一个光标。
我用比赛中的玩家数量更新作弊者的排名,把他放在最后。
之后,我号召其他人将作弊者位置之间的所有其他玩家排名递减,直到结束或另一个作弊者。
这是我写的代码:
-- Decrement ranking function
CREATE OR REPLACE FUNCTION decrement_ranking(ranking_start integer, id_competition integer)
RETURNS integer
AS $$
DECLARE
nb_modification integer := 0;
c CURSOR FOR
SELECT c.noSkieur, c.classement
FROM classement AS c
WHERE c.idCompet = id_competition AND c.classement > ranking_start;
competiter_ranking integer;
BEGIN
FOR c_data IN c
LOOP
-- while the competiter is not a cheater
CONTINUE WHEN c_data.noSkieur NOT IN (SELECT noSkieur FROM penalise WHERE idCompet = id_competition);
-- get current competiter ranking
SELECT c.classement INTO competiter_ranking
FROM classement AS c
WHERE c.noSkieur = c_data.noSkieur AND idCompet = id_competition;
-- decrement ranking
UPDATE classement
SET classement = competiter_ranking - 1
WHERE c.noSkieur = c_data.noSkieur AND idCompet = id_competition;
nb_modification := nb_modification + 1;
END LOOP;
RETURN nb_modification;
END;
$$ LANGUAGE plpgsql;
-- Main function
CREATE OR REPLACE FUNCTION declasse_penalise()
RETURNS integer
AS $$
DECLARE
nb_modification integer := 0;
c CURSOR FOR
SELECT p.noSkieur, p.idCompet
FROM penalise AS p;
no_skieur_pen integer;
id_compet_cheat integer;
nb_competiters integer;
ranking_cheater integer;
BEGIN
FOR c_data IN c
LOOP
no_skieur_pen := c_data.noSkieur;
id_compet_cheat := c_data.idCompet;
-- get ranking cheater
SELECT cl.classement INTO ranking_cheater
FROM classement AS cl
WHERE cl.noSkieur = no_skieur_pen AND cl.idCompet = id_compet_cheat;
-- put cheater in lowest ranking = number of competiters
SELECT COUNT(sk.noSkieur) INTO nb_competiters
FROM skieur as sk
INNER JOIN classement AS cl ON cl.idCompet = id_compet_cheat;
UPDATE classement
SET classement = nb_competiters
WHERE noSkieur = no_skieur_pen AND idCompet = id_compet_cheat;
-- decrement all other ranking: from cheat player rank to before the next cheater (if there is one)
nb_modification := nb_modification + decrement_ranking(ranking_cheater, id_compet_cheat);
END LOOP;
RETURN nb_modification;
END;
$$ LANGUAGE plpgsql;
现在我的问题是:我的代码没有编译错误,但它没有像我预期的那样更新。
你知道为什么吗?
我想我明白了你想要什么,但我无法弄清楚函数 - 至少通读了几遍。但我可以告诉你,你正在做很多工作。使用 SQL 时 - 不管特定的 DBMS 让 SQL 做尽可能多的工作。如果顺其自然,它确实是一个举重运动员。你表示你需要一个循环。所以我会给你一个对作弊者的迭代(惩罚);但有所扩大。
create or replace function declasse_penalise()
returns void
language plpgsql
as $$
declare
-- get cheater, competition, cheater's finish place, and number of competitors in competition
cheaters cursor for
select p.noskieur, p.idcompet,c. classement, competors
from penalise as p
join classement c on (p.noskieur, p.idcompet) = (c.noskieur, c.idcompet)
join (select idcompet, max(classement) competors
from classement
group by idcompet
) com
on p.idcompet = com.idcompet;
cheater record;
begin
for cheater in cheaters -- for each cheater
loop
update classement
set classement = case when noskieur = cheater.noskieur -- cheater, set finish position to last
then cheater.competors
when classement > cheater.classement -- finished after cheater, move position to top
then classement - 1
else classement -- finished before cheater, leave position the same
end
where idcompet = cheater.idcompet; -- for particular competition only
end loop;
end;
$$;
-- test
select * from declasse_penalise2();
select * from classement order by idcompet, classement;
我认为这可以满足您提供的数据的要求 - 但您应该进行广泛的测试。只有一行进行测试是不够的。在同一场比赛中至少有 1 个测试用例需要 2 个作弊者。发生这种情况时可能会出现问题,因为每个人都有相同的完成位置,并且可能会跳过某些位置。