Skip to Main Content
  • Questions
  • Unique constraint violation not expected

Breadcrumb

Question and Answer

Connor McDonald

Thanks for the question, Barbara.

Asked: October 30, 2019 - 10:59 am UTC

Last updated: November 05, 2019 - 3:52 am UTC

Version: 12c

Viewed 1000+ times

You Asked

Hi Ask-Tom-Team,
I have a problem with an unique constraint violation I do not understand.
I have a table that stores questions to item of a questionnaire. The questions are valid for a specific range of years:

CREATE TABLE question (
    id   NUMBER
    CONSTRAINT nnc_quest_id NOT NULL,
    variable_id  VARCHAR2(7)
CONSTRAINT nnc_quest_variable_id NOT NULL,
    question  VARCHAR2(255)
CONSTRAINT nnc_quest_question NOT NULL,
    from_year  SMALLINT
CONSTRAINT nnc_quest_from_year NOT NULL,
    to_year  SMALLINT,
    creat_id  NUMBER
CONSTRAINT nnc_quest_creat_id NOT NULL,
    creat_dat  TIMESTAMP WITH LOCAL TIME ZONE DEFAULT systimestamp
CONSTRAINT nnc_quest_creat_dat NOT NULL,
    act_id  NUMBER,
    act_dat  TIMESTAMP WITH LOCAL TIME ZONE DEFAULT NULL
);
CREATE UNIQUE INDEX idx_quest_id ON
    question (
        id   ASC
 );
ALTER TABLE question ADD CONSTRAINT pk_quest PRIMARY KEY ( id );

CREATE SEQUENCE quest_id_seq START WITH 1 NOCACHE ORDER;

CREATE OR REPLACE TRIGGER quest_id_trg BEFORE
    INSERT ON question
    FOR EACH ROW
    WHEN ( new.id IS NULL )
BEGIN
    :new.id := quest_id_seq.nextval;
END;
/


I have written a procedure to insert data and automatically change the to_dat when an entry is updated.

create or replace PROCEDURE INSERT_QUESTION_PROC
(
  IN_VARIABLE_ID IN VARCHAR2 
, IN_QUESTION IN QUESTION.QUESTION%TYPE 
, IN_FROM_YEAR IN QUESTION.FROM_YEAR%TYPE 
, IN_TO_YEAR IN QUESTION.TO_YEAR%TYPE 
, IN_CREAT_ID IN QUESTION.CREAT_ID%TYPE  
, IN_CREAT_DAT IN QUESTION.CREAT_DAT%TYPE DEFAULT SYSTIMESTAMP
) AS 
BEGIN
    UPDATE question
    SET to_year = CASE 
                    WHEN (in_from_year > from_year) THEN in_from_year - 1
                    ELSE in_from_year
                    END
        , act_id = in_creat_id, act_dat = in_creat_dat
        WHERE variable_id = in_variable_id 
        and to_year IS NULL;
    INSERT INTO question 
    (variable_id, question, from_year, to_year, creat_id) 
    VALUES (in_variable_id, in_question, in_from_year, in_to_year, in_creat_id);
END INSERT_QUESTION_PROC;


To permit only one entry for a given variable and a given period of time with a similar question I implemented a unique constraint on the four columns:

ALTER TABLE question
    ADD CONSTRAINT um_quest UNIQUE ( variable_id,
                                    question,
                                    from_year,
                                    to_year );

CREATE UNIQUE INDEX idx_quest_variable_id_question_from_year_to_year ON
    question (
        variable_id ASC,
        question  ASC,
        from_year  ASC,
        to_year  ASC 
);

To cut a long story short, I have a problem when inserting data. Using the procedure the following works fine and the rows are inserted:
BEGIN
    INSERT_QUESTION_PROC('f1','A',2001,NULL,1);
    INSERT_QUESTION_PROC('f1','B',2001,NULL,1);
    INSERT_QUESTION_PROC('f1','C',2001,NULL,1);
    INSERT_QUESTION_PROC('f1','D',2001,NULL,1);
    INSERT_QUESTION_PROC('f1','D',2001,NULL,1);
END;


When trying to insert the following row, an error occurs due to the unique-constraint - as expected:
BEGIN
    INSERT_QUESTION_PROC('f1','D',2001,NULL,1);
END;  

So I changed the question (second parameter) to 'E' and tried to insert it. That is when the unique constraint violation occurs that I do not understand.
BEGIN
    INSERT_QUESTION_PROC('f1','E',2001,NULL,1);
END;  

Maybe I am missing something obious.

Thanks for your help.

and Connor said...

It is your update ... it is trying to set values without reference to the "question" value

I added some debugging so you can see it happening

SQL>
SQL>
SQL> CREATE TABLE question (
  2      id   NUMBER
  3      CONSTRAINT nnc_quest_id NOT NULL,
  4      variable_id  VARCHAR2(7)
  5  CONSTRAINT nnc_quest_variable_id NOT NULL,
  6      question  VARCHAR2(255)
  7  CONSTRAINT nnc_quest_question NOT NULL,
  8      from_year  SMALLINT
  9  CONSTRAINT nnc_quest_from_year NOT NULL,
 10      to_year  SMALLINT,
 11      creat_id  NUMBER
 12  CONSTRAINT nnc_quest_creat_id NOT NULL,
 13      creat_dat  TIMESTAMP WITH LOCAL TIME ZONE DEFAULT systimestamp
 14  CONSTRAINT nnc_quest_creat_dat NOT NULL,
 15      act_id  NUMBER,
 16      act_dat  TIMESTAMP WITH LOCAL TIME ZONE DEFAULT NULL
 17  );

Table created.

SQL> CREATE UNIQUE INDEX idx_quest_id ON
  2      question (
  3          id   ASC
  4   );

Index created.

SQL> ALTER TABLE question ADD CONSTRAINT pk_quest PRIMARY KEY ( id );

Table altered.

SQL>
SQL> CREATE SEQUENCE quest_id_seq START WITH 1 NOCACHE ORDER;

Sequence created.

SQL>
SQL> CREATE OR REPLACE TRIGGER quest_id_trg BEFORE
  2      INSERT ON question
  3      FOR EACH ROW
  4      WHEN ( new.id IS NULL )
  5  BEGIN
  6      :new.id := quest_id_seq.nextval;
  7  END;
  8  /

Trigger created.

SQL>
SQL> create or replace procedure PPP(m varchar2, s int) is
  2  begin
  3    dbms_output.put_line(m||':'||s);
  4
  5    for i in ( select * from question )
  6    loop
  7      dbms_output.put_line(i.variable_id||','||i.question||','||i.from_year||','||nvl(i.to_year,-1));
  8    end loop;
  9    dbms_output.put_line('----------------');
 10  end;
 11  /

Procedure created.

SQL>
SQL>
SQL> create or replace PROCEDURE INSERT_QUESTION_PROC
  2  (
  3    IN_VARIABLE_ID IN VARCHAR2
  4  , IN_QUESTION IN QUESTION.QUESTION%TYPE
  5  , IN_FROM_YEAR IN QUESTION.FROM_YEAR%TYPE
  6  , IN_TO_YEAR IN QUESTION.TO_YEAR%TYPE
  7  , IN_CREAT_ID IN QUESTION.CREAT_ID%TYPE
  8  , IN_CREAT_DAT IN QUESTION.CREAT_DAT%TYPE DEFAULT SYSTIMESTAMP
  9  ) AS
 10  BEGIN
 11    dbms_output.put_line('about to update');
 12      UPDATE question
 13      SET to_year = CASE
 14                      WHEN (in_from_year > from_year) THEN in_from_year - 1
 15                      ELSE in_from_year
 16                      END
 17          , act_id = in_creat_id, act_dat = in_creat_dat
 18          WHERE variable_id = in_variable_id
 19          and to_year IS NULL;
 20   ppp ('after update ',sql%rowcount);
 21      INSERT INTO question
 22      (variable_id, question, from_year, to_year, creat_id)
 23      VALUES (in_variable_id, in_question, in_from_year, in_to_year, in_creat_id);
 24
 25   ppp ('after insert',null);
 26
 27  END INSERT_QUESTION_PROC;
 28  /

Procedure created.

SQL>
SQL>
SQL> ALTER TABLE question
  2      ADD CONSTRAINT um_quest UNIQUE ( variable_id,
  3                                      question,
  4                                      from_year,
  5                                      to_year );

Table altered.

SQL>
SQL> set serverout on
SQL> BEGIN
  2      INSERT_QUESTION_PROC('f1','A',2001,NULL,1);
  3      INSERT_QUESTION_PROC('f1','B',2001,NULL,1);
  4      INSERT_QUESTION_PROC('f1','C',2001,NULL,1);
  5      INSERT_QUESTION_PROC('f1','D',2001,NULL,1);
  6      INSERT_QUESTION_PROC('f1','D',2001,NULL,1);
  7  END;
  8  /
about to update
after update :0
----------------
after insert:
f1,A,2001,-1
----------------
about to update
after update :1
f1,A,2001,2001
----------------
after insert:
f1,A,2001,2001
f1,B,2001,-1
----------------
about to update
after update :1
f1,A,2001,2001
f1,B,2001,2001
----------------
after insert:
f1,A,2001,2001
f1,B,2001,2001
f1,C,2001,-1
----------------
about to update
after update :1
f1,A,2001,2001
f1,B,2001,2001
f1,C,2001,2001
----------------
after insert:
f1,A,2001,2001
f1,B,2001,2001
f1,C,2001,2001
f1,D,2001,-1
----------------
about to update
after update :1
f1,A,2001,2001
f1,B,2001,2001
f1,C,2001,2001
f1,D,2001,2001
----------------
after insert:
f1,A,2001,2001
f1,B,2001,2001
f1,C,2001,2001
f1,D,2001,2001
f1,D,2001,-1
----------------

PL/SQL procedure successfully completed.

SQL>
SQL> BEGIN
  2      INSERT_QUESTION_PROC('f1','D',2001,NULL,1);
  3  END;
  4  /
about to update
BEGIN
*
ERROR at line 1:
ORA-00001: unique constraint (MCDONAC.UM_QUEST) violated
ORA-06512: at "MCDONAC.INSERT_QUESTION_PROC", line 12
ORA-06512: at line 2


SQL>
SQL> BEGIN
  2      INSERT_QUESTION_PROC('f1','E',2001,NULL,1);
  3  END;
  4  /
about to update
BEGIN
*
ERROR at line 1:
ORA-00001: unique constraint (MCDONAC.UM_QUEST) violated
ORA-06512: at "MCDONAC.INSERT_QUESTION_PROC", line 12
ORA-06512: at line 2


SQL>



Is this answer out of date? If it is, please let us know via a Comment

More to Explore

Design

New to good database design? Check out Chris Saxon's full fundamentals class.