skip to Main Content

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?

2

Answers


  1. 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
    
    Login or Signup to reply.
  2. 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;$$
    
    
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search