0

I have a ‘test’ PostgreSQL 12 schema, defined with initial tables:

begin;
drop schema if exists test cascade;
create schema test;

set search_path = test,public;
create table jobs (
  job_id serial primary key,
  job_type_lu text);

create table phases (
  phase_id serial primary key,
  job_id_fk int references jobs(job_id),
  phase_type_lu text);

create table phase_steps (
  phase_step_id serial primary key,
  phase_id_fk int references phases(phase_id),
  step_type_lu text);
  
commit;

‘jobs’ are composed of one or more ‘phases’, which in turn have multiple ‘phase_steps’ What I want to accomplish is to fire two triggers when a new job is inserted into the ‘jobs’ table. I have these two triggers defined:

create or replace function test.setup_phases () returns trigger as $$
    begin
        insert into test.phase_steps (job_id_fk,phase_type_lu)
            values
                (new.jobs_id,'RESEARCH: JOB_INPUT'),
                (new.jobs_id,'RESEARCH: WATER'),
                (new.jobs_id,'RESEARCH: OTHER'),
                (new.jobs_id,'RESEARCH: REVISION_ATEAM'),
                (new.jobs_id,'RESEARCH: REVISION_CLIENT');
                return new;
end;
$$ language plpgsql;

create trigger setup_test_phases after insert on test.jobs
for each row execute procedure test.setup_phases();

create or replace function test.setup_phase_steps() returns trigger as $$
    begin
        if (new.phase_type_lu = 'RESEARCH')
        then
            insert into test.phase_steps (phase_id_fk,step_type_lu)
                values
                    (new.phase_id,'RESEARCH: JOB_INPUT'),
                    (new.phase_id,'RESEARCH: WATER'),
                    (new.phase_id,'RESEARCH: OTHER'),
                    (new.phase_id,'RESEARCH: REVISION_ATEAM'),
                    (new.phase_id,'RESEARCH: REVISION_CLIENT');
                    return new;
        end if;
        
        if (new.phase_type_lu = 'SURVEY')
        then
            insert into test.phase_steps (phase_id_fk,step_type_lu)
                values
                    (new.phase_id,'SURVEY: SCHEDULING'),
                    (new.phase_id,'SURVEY: FIELDING'),
                    (new.phase_id,'SURVEY: REVISION_ATEAM'),
                    (new.phase_id,'SURVEY: REVISION_CLIENT');
                    return new;
        end if;
end;
$$ language plpgsql;

create trigger setup_test_phase_steps after insert on test.phases
for each row execute procedure test.setup_phase_steps();

If I input data directly for just the phases (therefore invoking only the ‘setup_test_phase_steps’ trigger, e.g.,

insert into test.phases (phase_type_lu)
    values
        ('RESEARCH'),
        ('SURVEY');

The correct results are obtained:

enter image description here

However, if I enter input directly into the ‘jobs’ table (which then will invoke the ‘setup_test_phases’ AND the ‘setup_test_phase_steps’ triggers):

insert into test.jobs (job_type_lu) values ('NT-TYP');

I get this error:

enter image description here

I’m obviously misunderstanding cascading triggers, but what?

3
  • Table phase_steps has no column job_id_fk, so you cannot insert into that column. Seems to be a typo. – Laurenz Albe Apr 30 at 3:14
  • @LaurenzAlbe, are you saying that I need to have fields that are totally unrelated to the table (such as storing 'job_id_fk' in table 'phase_steps')? That doesn't make sense to me. – Rudy Stricklan Apr 30 at 4:32
  • No, but if you say insert into test.phase_steps (phase_id_fk, ...) VALUES ..., you are saying that you want to insert something into column phase_id_fk. Perhaps you have a basic misunderstanding of 'INSERT`. – Laurenz Albe Apr 30 at 6:00
0

To resolve your problem, I did the following (all of the code below can be found on the fiddle here):

I changed the formatting somewhat - it helps me think - I try to base my SQL coding standards on this, but that's, as Fr. Jack would say, an ecumenical matter (if you're not a Fr. Ted fan, don't worry...).

The crux of your problem is that you have a cascading chain of FOREIGN KEYs from job to phase to phase_step - which is good - helps keep track of what work goes with what contract &c.

The problem is with your code here:

create or replace function test.setup_phases () returns trigger as $$
    begin
        insert into test.phase_steps (job_id_fk,phase_type_lu)
            values
                (new.jobs_id,'RESEARCH: JOB_INPUT'),

You have setup_phases, BUT underneath that, you have insert into test.phase_steps (job_id_fk,phase_type_lu) - you "skip" the INSERT INTO phases step - so your chain of FOREIGN KEYs is broken.

Now, I imagine that your work flow is something like this:

You receive a job - the first phase of which is normally RESEARCH, so I created the phase table as follows (job remains unchanged):

CREATE TABLE phase 
(
  phase_id      SERIAL PRIMARY KEY,
  job_id_fk     INTEGER references job (job_id),
  phase_type_lu TEXT DEFAULT 'RESEARCH',
  
  CONSTRAINT phase_type_ck CHECK (phase_type_lu IN ('RESEARCH', 'SURVEY'))  
);

So, then I did this:

CREATE OR REPLACE FUNCTION setup_phase_bis()
RETURNS TRIGGER AS 
$$
BEGIN
  INSERT INTO phase (job_id_fk) VALUES
    (NEW.job_id);
    RETURN NEW;
END;
$$ LANGUAGE PLpgSQL;

and:

CREATE TRIGGER setup_phase AFTER INSERT ON job
FOR EACH ROW EXECUTE PROCEDURE setup_phase_bis();

So, now, when I create a job, it sets up a RESEARCH phase. You missed this in your code. What happens now when I INSERT a job is the following:

INSERT INTO job (job_type_lu) VALUES ('NT-TYP');

and:

SELECT * FROM job;
SELECT * FROM phase;
SELECT * FROM phase_step;

Result:

job_id  job_type_lu
1   NT-TYP
phase_id    job_id_fk   phase_type_lu
1   1   RESEARCH
phase_step_id   phase_id_fk step_type_lu

Note: no phase steps because I haven't done that yet.

So then I copied your code - with the notable exception (pardon the pun...) of the lines (see fiddle):

  IF NEW.job_id_fk IS NULL THEN
    RAISE EXCEPTION 'You must supply a job_id for this phase...';
  END IF;

Not having this provokes the error message you are receiving...

So, now, when a job is inserted, the job cascades to (RESEARCH) phase and the phase cascades to the appropriate steps - as follows:

INSERT INTO job (job_type_lu) VALUES ('XXXXX');

And:

SELECT * FROM job;
SELECT * FROM phase;
SELECT * FROM phase_step;

Result:

job_id  job_type_lu
1   NT-TYP
2   XXXXX
phase_id    job_id_fk   phase_type_lu
1   1   RESEARCH
2   2   RESEARCH
phase_step_id   phase_id_fk step_type_lu
1   2   RESEARCH: JOB_INPUT
2   2   RESEARCH: WATER
3   2   RESEARCH: OTHER
4   2   RESEARCH: REVISION_ATEAM
5   2   RESEARCH: REVISION_CLIENT

So, we have our cascade.

Now (again, making assumptions about your workflow) - if the client is happy with the research phase, they'll want to progress to the SURVEY phase and so you will wish to enter that into the system but not as a new job (hope I'm OK so far... :-) )

So, now we do this:

INSERT INTO phase (phase_type_lu) VALUES ('SURVEY');

BUT, there's no associated job_id, so, thanks to the EXCEPTION, this fails as follows:

ERROR:  You must supply a job_id for this phase...
CONTEXT:  PL/pgSQL function setup_phase_steps() line 5 at RAISE

But, if we do this:

INSERT INTO phase (job_id_fk, phase_type_lu) VALUES (2, 'SURVEY');

we get:

SELECT * FROM job;
SELECT * FROM phase;
SELECT * FROM phase_step;

Result:

job_id  job_type_lu
1   NT-TYP
2   XXXXX
phase_id    job_id_fk   phase_type_lu
1   1   RESEARCH
2   2   RESEARCH
4   2   SURVEY
phase_step_id   phase_id_fk step_type_lu
1   2   RESEARCH: JOB_INPUT
2   2   RESEARCH: WATER
3   2   RESEARCH: OTHER
4   2   RESEARCH: REVISION_ATEAM
5   2   RESEARCH: REVISION_CLIENT
6   4   SURVEY: SCHEDULING
7   4   SURVEY: FIELDING
8   4   SURVEY: REVISION_ATEAM
9   4   SURVEY: REVISION_CLIENT

Our SURVEY phase steps have been propagated with the correct FOREIGN KEYs. So, all is rosy in the garden? I ran a final test at the bottom of the fiddle (not shown for brevity), and all appears OK!

For future reference, it would be great if you could provide appropriate fiddles with your questions and also if you could give us the version of PostgreSQL that you are running - help us to help you!

p.s. welcome to the forum!

2
  • Thanks very much, @Verace. Yes, there were several typos in my code snippets. They were composed from the meatier code with constraints as you recommend. I tried to boil the problem I was having down as simply as possible. The actual issue was the ordering of the 'return new' and 'endif' statements in the setup_phase_steps conditional tests. Perhaps it would be beneficial to publish an "alternative answer" that illustrates what I think is a confusing point in pgsql RETURNs from conditional testing? BTW, I did specify the PostgreSQL version in my first line. Great advice, thanks again! – Rudy Stricklan Apr 30 at 17:49
  • Glad I was able to help! Feel free to upvote and/or mark as correct as you think appropriate! :-) Sorry about missing your version - I'll be more careful in future.... – Vérace Apr 30 at 18:34

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.