Home > Blockchain >  Postgresql stored procedure with if then condition on updating table
Postgresql stored procedure with if then condition on updating table

Time:11-06

I am new to postgresql and am trying to update a table based on conditions using PostgreSQL stored procedure. The table 'pref2' looks like this:

geneid              pred_perf_r2        pred_perf_pval      vers
ENSG00000107959     0.03                 0.02                1.0
ENSG00000106321     0.05                 0.01                1.0
ENSG00000102222     0.22                 0.05                1.0
ENSG00000101111     0.11                 0.03                1.0
ENSG00000102355     0.33                 0.01                1.0

I want to create a stored procedure for updating this table for pred_perf_r2 and pred_perf_pval if the new scores are better (bigger R2 and smaller pval). My attempt:

create or replace procedure new_version(
gene varchar(50),
new_r2 numeric,
new_p numeric,
new_v numeric   
)
language plpgsql
as $$
begin
if (new_r2 > perf2.pred_perf_r2)
and (new_p < perf2.pred_perf_pval) then
    update perf2 set
    perf2.pred_perf_r2 = new_r2,

    perf2.pred_perf_pval = new_p ,
    
    perf2.vers = new_v

where perf2.geneid = gene;
end if;
commit;
END;$$

call new_version('ENSG00000107959',0.55,0.01,2.0);
select * from perf2;

It gives me this error:

ERROR:  missing FROM-clause entry for table "perf2"
LINE 1: (new_r2 > perf2.pred_perf_r2)
                  ^
QUERY:  (new_r2 > perf2.pred_perf_r2)
and (new_p < perf2.pred_perf_pval)
CONTEXT:  PL/pgSQL function new_version(character varying,numeric,numeric,numeric) line 3 at IF
SQL state: 42P01

My desired result will look like this when calling the stored procedure:

geneid              pred_perf_r2        pred_perf_pval      vers
ENSG00000107959     0.55                 0.01                2.0
ENSG00000106321     0.05                 0.01                1.0
ENSG00000102222     0.22                 0.05                1.0
ENSG00000101111     0.11                 0.03                1.0
ENSG00000102355     0.33                 0.01                1.0

if

call new_version('ENSG00000107959',0.02,0.05,2.0);

The original table should not change since R square is worse (0.02 < 0.03) and pval is larger (0.05>0.02) It keeps giving me errors. Any ideas on how I can fix this?

CodePudding user response:

The problem with your procedure is that you cannot guarantee nobody will bypass it by writing a regular UPDATE statement.
You can solve this with a trigger or a rule.

Solution 1: Trigger

Solution 1 will raise an exception, which:

  • client applications can catch and eventually react to (useful to remember if you perform the UPDATE in the middle of a transaction).
  • means, when updating several records, 1 error will block the whole statement.

Code:

CREATE OR REPLACE FUNCTION RaiseError() RETURNS TRIGGER
AS $$
BEGIN
RAISE EXCEPTION USING MESSAGE = 'Invalid r-square or pval', ERRCODE = '23514' /*check_violation*/;
RETURN NEW;
END;
$$ LANGUAGE PLPGSQL;

CREATE TRIGGER CheckScoreUpdate BEFORE UPDATE OF pred_perf_r2, pred_perf_pval ON perf2
FOR EACH ROW
WHEN (NEW.pred_perf_r2 < OLD.pred_perf_r2 OR NEW.pred_perf_pval > OLD.pred_perf_pval)
EXECUTE FUNCTION RaiseError()

Solution 2: Rule

Solution 2 skips records breaking your rule and will not raise any error, which:

  • means records get updated when valid, even if another record being updated in the same statement is not.
  • prevents client applications from knowing if all the records they intended to update were effectively updated (unless you know in advance how many records you were attempting to update).

Code:

CREATE OR REPLACE RULE CheckScoreUpdate AS ON UPDATE TO perf2
WHERE NEW.pred_perf_r2 < OLD.pred_perf_r2 OR NEW.pred_perf_pval > OLD.pred_perf_pval
DO INSTEAD NOTHING

CodePudding user response:

As the error states this, perf2.pred_perf_r2, is table reference for a table that does not exist in the function context. Same will happen with perf2.pred_perf_pval. The other issue is that neither value is filtered to a specific gene. Also you can't table specify the updated columns so this perf2.pred_perf_r2 = new_r2 needs to be pred_perf_r2 = new_r2, same for the other columns. You would need to do something like:

create or replace procedure new_version(
gene varchar(50),
new_r2 numeric,
new_p numeric,
new_v numeric   
)
language plpgsql
as $$
DECLARE
    old_pred_perf_r2 numeric;
    old_pred_perf_pval numeric;
begin
   SELECT pred_perf_r2, pred_perf_pval INTO 
      old_pred_perf_r2, old_pred_perf_pval
   FROM 
      perf2
   WHERE
     geneid = gene;
   if (new_r2 > old_pred_perf_r2)
   and (new_p < old_pred_perf_pval) then
       update perf2 set
        pred_perf_r2 = new_r2,
        pred_perf_pval = new_p ,
        vers = new_v
        where perf2.geneid = gene;
   end if;
commit;
END;$$

Option B. Put the if logic in the UPDATE statement.

create or replace procedure new_version(
gene varchar(50),
new_r2 numeric,
new_p numeric,
new_v numeric   
)
language plpgsql
as $$

begin
   update perf2 set
        pred_perf_r2 = new_r2,
        pred_perf_pval = new_p,
        vers = new_v
   where 
     perf2.geneid = gene
   and 
      new_r2 > pred_perf_r2
   and
      new_p < pred_perf_pval      
;
   end if;
commit;
END;$$


  • Related