PostgreSQL에서 복잡한 조건의 CHECK 제약 조건 구현

14656 단어 PostgreSQLSQLDB

소개



설문지의 단일 답변과 같은 "어떤 집합 중 하나만 선택할 수있는"기능을 구현했습니다.
이 제약을 DB의 테이블에서도 표현할 수 있으면 좋겠다고 생각했습니다.

궁극적으로 함수 + CHECK 제약으로 실현되었습니다.

환경



DB: PostgreSQL 12

테이블 만들기



우선 테이블을 만들어 봅시다.
ER 그림으로 하면 다음과 같이 됩니다.


-- 設問 「映画は好きですか?」 など
CREATE TABLE questions (
 question_id INT NOT NULL,
 question NCHAR VARYING(100) NOT NULL
);

ALTER TABLE questions ADD CONSTRAINT PK_questions PRIMARY KEY (question_id);

-- 回答の選択肢 「当てはまらない」「やや当てはまる」 など
CREATE TABLE answer_levels (
 answer_level_id INT NOT NULL,
 answer_level_title NCHAR VARYING(10) NOT NULL,
 answer_level_score INT NOT NULL
);

ALTER TABLE answer_levels ADD CONSTRAINT PK_answer_levels PRIMARY KEY (answer_level_id);

-- 回答
CREATE TABLE responses (
 question_id INT NOT NULL,
 answer_level_id INT NOT NULL,
 response_type INT NOT NULL CHECK(response_type < 2) -- 0: 未選択 1: 選択
);

ALTER TABLE responses ADD CONSTRAINT PK_responses PRIMARY KEY (question_id,answer_level_id);


ALTER TABLE responses ADD CONSTRAINT FK_responses_0 FOREIGN KEY (question_id) REFERENCES questions (question_id);
ALTER TABLE responses ADD CONSTRAINT FK_responses_1 FOREIGN KEY (answer_level_id) REFERENCES answer_levels (answer_level_id);

테스트 데이터 만들기



그런 다음 테스트 데이터를 만듭니다.
댓글에서 알 수 있듯이 이 테스트 데이터는 예상치 못한 데이터입니다.
하지만 이 단계에서 실행하면 할 수 있습니다.
insert into
  questions(question_id, question)
values
  (1, 'q-a')
, (2, 'q-b')
, (3, 'q-c')
;

insert into
  answer_levels(answer_level_id, answer_level_title, answer_level_score)
values
  (1, 'answer-a', 20)
, (2, 'answer-b', 40)
, (3, 'answer-c', 60)
, (4, 'answer-d', 80)
, (5, 'answer-e', 100)
;

insert into
  responses(question_id, answer_level_id, response_type)
values
  (1, 1, 0)
, (1, 2, 0)
, (1, 3, 0)
, (1, 4, 1)
, (1, 5, 0)
, (2, 1, 0)
, (2, 2, 0)
, (2, 3, 0)
, (2, 4, 0)
, (2, 5, 0)
, (3, 1, 0)
, (3, 2, 1)
, (3, 3, 0)
, (3, 4, 1) -- question_id=3かつresponse_type=1の回答はすでにあるので、insertできないようにしたい
, (3, 5, 0)
;

CHECK 제약 조건



그런데 곤란했습니다.
설문에 대해 단일 응답이어야 하지만, 복수의 응답을 등록할 수 있습니다.

거기서 responses 테이블에 대해서 CHECK 제약을 붙이려고 합니다.
질문 ID를 지정하여 responses.response_type = 1의 레코드 수를 얻는 함수를 만들고
CHECK 제약의 조건으로서 이용하도록(듯이) 합니다.

Postgre SQL에서는 PL/pgSQL이라는 언어를 사용하여 함수를 구현할 수 있습니다.
다음 SQL을 실행합니다.
-- 指定したquestion_idの中で、response_type=1のレコード数を返す関数
CREATE FUNCTION GetResponsedRowCount(
  questionId int
) RETURNS int AS $$
DECLARE responsedRowCount int;
BEGIN
  SELECT COUNT(*) INTO responsedRowCount
  FROM responses
  WHERE
    question_id = questionId
    and response_type = 1
  ;
  RETURN responsedRowCount;
END;
$$ LANGUAGE plpgsql;

-- insert/updateする前の時点でresponse_type=1のレコードがなければOK
-- response_type=0の場合は常にOK
ALTER TABLE responses ADD CONSTRAINT ResponsedRowCountOnlyOne CHECK (GetResponsedRowCount(question_id) = 0 or response_type = 0);

CHECK 제약 조건은 insert/update 실행 전에 확인됩니다.



처음에는 insert/update 후의 상태를 정의한다고 생각했기 때문에
CHECK 제약을 다음과 같이 설정했지만 예상대로 작동하지 않았습니다.
ALTER TABLE responses ADD CONSTRAINT ResponsedRowCountOnlyOne CHECK (GetResponsedRowCount(question_id) < 2);

문서에는 다음의 설명이 있었으므로 CHECK 제약의 조건을 변경했습니다.


CHECK 절은 논리 유형의 결과를 생성하는 새 행 또는 갱신되는 행이 삽입 또는 갱신 처리를 성공적으로 수행하기 위해 만족해야 하는 표현식을 지정합니다.

요약



이상으로 조건부 CHECK 제약을 구현할 수있었습니다.
위에서 언급 한 테스트 데이터의 SQL을 실행하면 CHECK 제약 예외가 발생합니다.

다른 방법이 있으면 꼭 알고 싶습니다!
ERROR: new row for relation "responses" violates check constraint "responsedrowcountonlyone"
    詳細: Failing row contains (3, 4, 1).

참고문헌



htps //w w. 포스트g sql. jp / 도쿠 멘 t / 10 / HTML / sql-c 접어서 b ぇ. HTML
h tps : // s t c ゔ ぇ rf ぉ w. 코 m / 쿠에 s 치온 s / 866061 / 콘 치오나
htps : // m / SR 사와 구치 / ms / 411801 254 66f511f1 # plpgsql % 3 % 81 % A B % 3 % 82 % 88 % 3 % 82 % 8B % 9 % 96 % A 2 % 6 %95%B0

좋은 웹페이지 즐겨찾기