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 个作弊者。发生这种情况时可能会出现问题,因为每个人都有相同的完成位置,并且可能会跳过某些位置。