제약 조건-하나의 부울 행이 true이고 다른 모든 행이 false입니다.


13

열이 있습니다. standard BOOLEAN NOT NULL

한 행을 True로, 다른 모든 행을 False로 적용하고 싶습니다. 이 제약 조건에 따라 FK 또는 다른 것은 없습니다. 나는 plpgsql로 그것을 달성 할 수 있다는 것을 알고 있지만 이것은 망치처럼 보입니다. CHECK또는 UNIQUE제약 조건 과 같은 것을 선호합니다 . 간단할수록 좋습니다.

한 행은 True 여야하고 모두 False 일 수는 없습니다 (따라서 삽입 된 첫 번째 행은 True 여야 함).

행을 업데이트해야합니다. 즉, 모든 행이 먼저 False로 설정되고 이후에 한 행이 True로 설정 될 수 있으므로 업데이트가 완료 될 때까지 제약 조건을 확인해야합니다.

products.tax_rate_id와 사이에 FK가 tax_rate.id있지만 기본 또는 표준 세율과는 아무런 관련이 없으며 새 제품을 쉽게 만들 수 있도록 사용자가 선택할 수 있습니다.

중요한 경우 PostgreSQL 9.5.

배경

표는 세율입니다. 세율 중 하나가 기본값입니다 ( standard기본값은 Postgres 명령이므로). 새 제품이 추가되면 표준 세율이 제품에 적용됩니다. 이 없으면 standard데이터베이스는 추측 또는 모든 종류의 불필요한 검사를 수행해야합니다. 내가 생각한 간단한 해결책은 standard.

위의 "기본"은 표현 계층 (UI)을 의미합니다. 기본 세율을 변경하기위한 사용자 옵션이 있습니다. GUI / 사용자가 tax_rate_id를 NULL로 설정하지 않도록 기본 검사를 추가하거나 기본 세율을 설정해야합니다.


대답이 있습니까?
Erwin Brandstetter

네, @ErwinBrandstetter 의견을 보내 주셔서 감사합니다. 나는 지금 방아쇠를 향해 기울고있다. 이것은 내 시간에 오픈 소스 프로젝트입니다. 실제로 구현할 때 내가 사용하는 답변을 수락으로 표시합니다.
theGtknerd

답변:


15

변형 1

이있는 단일 열만 있으면되므로 모든 standard = true다른 행에서 standard를 NULL로 설정하십시오. UNIQUENULL 값이 위반하지 않기 때문에 일반 제약 조건이 작동합니다.

CREATE TABLE taxrate (
   taxrate int PRIMARY KEY
 , standard bool DEFAULT true
 , CONSTRAINT standard_true_or_null CHECK (standard) -- yes, that's the whole constraint
 , CONSTRAINT standard_only_1_true UNIQUE (standard)
);

DEFAULT입력 한 첫 번째 행이 기본값이되어야한다는 선택적 알림입니다. 아무것도 시행 하지 않습니다 . 로 둘 이상의 행을 standard = true설정할 수 없지만 모든 행을 NULL로 설정할 수 있습니다. 단일 테이블에서 제한 조건 만으로이를 방지 할 수있는 확실한 방법은 없습니다 . CHECK제약 조건은 다른 행을 고려하지 않습니다 (더티 트릭 없음).

관련 :

업데이트하려면 :

BEGIN;
UPDATE taxrate SET standard = NULL WHERE standard;
UPDATE taxrate SET standard = TRUE WHERE taxrate = 2;
COMMIT;

다음과 같은 명령을 허용하려면 (문의 끝에서만 제한 조건이 충족되는 경우) :

WITH kingdead AS (
   UPDATE taxrate
   SET standard = NULL
   WHERE standard
   )
UPDATE taxrate
SET standard = TRUE
WHERE taxrate = 1;

.. UNIQUE제약 조건은이어야 DEFERRABLE합니다. 보다:

여기 dbfiddle

변형 2

다음 과 같은 단일 행 이있는 두 번째 테이블 이 있습니다.

이것을 수퍼 유저로 생성하십시오 :

CREATE TABLE taxrate (
   taxrate int PRIMARY KEY
);

CREATE TABLE taxrate_standard (
   taxrate int PRIMARY KEY REFERENCES taxrate
);

CREATE UNIQUE INDEX taxrate_standard_singleton ON taxrate_standard ((true));  -- singleton

REVOKE DELETE ON TABLE taxrate_standard FROM public;  -- can't delete

INSERT INTO taxrate (taxrate) VALUES (42);
INSERT INTO taxrate_standard (taxrate) VALUES (42);

이제 표준을 가리키는 단일 행이 항상 있습니다 (이 간단한 경우에도 표준 비율을 직접 나타냄). 수퍼 유저 만이이를 깨뜨릴 수 있습니다. 트리거로도 허용하지 않을 수 있습니다 BEFORE DELETE.

여기 dbfiddle

관련 :

변형 1VIEW 과 동일하게 a 를 추가 할 수 있습니다 .

CREATE VIEW taxrate_combined AS
SELECT t.*, (ts.taxrate = t.taxrate) AS standard
FROM   taxrate t
LEFT   JOIN taxrate_standard ts USING (taxrate);

원하는 모든 것이 표준 요율 인 쿼리에서는 taxrate_standard.taxrate직접 사용하십시오.


나중에 추가했습니다.

products.tax_rate_id와 사이에 FK가 있습니다tax_rate.id

가난한 사람의 구현 변형 2 단지에 행 추가하는 것입니다 products표준 세율을 가리키는 (또는 유사한 테이블); 더미 제품은 "표준 세율"이라고 부를 수 있습니다-설정에서 허용하는 경우.

FK 제약 조건은 참조 무결성을 강화합니다. 이를 완료하려면 tax_rate_id IS NOT NULL행을 적용 하십시오 (일반적으로 열이 아닌 경우). 그리고 삭제를 허용하지 않습니다. 둘 다 방아쇠에 넣을 수 있습니다. 추가 테이블은 없지만 우아하지 않고 신뢰할 수 없습니다.


2
두 테이블 접근 방식을 적극 권장합니다. OP가 CROSS JOIN표준, LEFT JOIN특정 및 COALESCE두 방법 사이의 방법을 볼 수 있도록 해당 변형에 예제 쿼리를 추가하는 것이 좋습니다 .
jpmc26

2
+1, 나는 여분의 테이블에 대해 같은 생각을했지만 대답을 제대로 쓸 시간이 없었습니다. 첫 번째 테이블과 CONSTRAINT standard_only_1_true UNIQUE (standard): 정보 : 테이블이 크지 않기 때문에별로 중요하지 않지만 제약 조건이 전체 테이블에서 인덱스를 정의 WHERE (standard)하기 때문에 공간을 덜 사용 하는 부분 고유 인덱스가 아니 겠습니까?
ypercubeᵀᴹ

@ ypercubeᵀᴹ : 예, 전체 테이블의 인덱스가 더 큽니다. 이것이이 변형의 단점입니다. 그러나 당신이 말했듯이 : 그것은 분명히 작은 테이블이므로 거의 중요하지 않습니다. 나는 제약 조건이있는 가장 간단한 표준 솔루션을 목표로 삼았습니다. 개념의 증거. 개인적으로, 나는 jpmc26에이고 강력하게 변형 2를 선호
어윈 Brandstetter

9

필터링 된 인덱스를 사용할 수 있습니다

create table test
(
    id int primary key,
    foo bool
);
CREATE UNIQUE INDEX only_one_row_with_column_true_uix 
    ON test (foo) WHERE (foo);  --> where foo is true
insert into test values (1, false);
insert into test values (2, true);
insert into test values (3, false);
insert into test values (4, false);
insert into test values (5, true);
오류 : 중복 키 값이 고유 제한 조건 "only_one_row_with_column_true_uix"을 위반 함
세부 정보 : 키 (foo) = (t)가 이미 존재합니다.

여기 dbfiddle


그러나 말했듯이 첫 번째 행은 true이어야하며 CHECK 제약 조건을 사용할 수 있지만 함수를 사용하더라도 나중에 첫 번째 행을 삭제할 수 있습니다.

create function check_one_true(new_foo bool)
returns int as
$$
begin
    return 
    (
        select count(*) + (case new_foo when true then 1 else 0 end)
        from test 
        where foo = true
    );
end
$$
language plpgsql stable;
alter table test 
    add constraint ck_one_true check(check_one_true(foo) = 1); 
insert into test values (1, true);
insert into test values (2, false);
insert into test values (3, false);
insert into test values (4, false);
insert into test values (5, true);
오류 : 관계 "test"의 새 행이 검사 제한 조건 "ck_one_true"을 위반합니다.
세부 정보 : 실패 행에 (5, t)가 포함됩니다.

select * from test;
아이디 | 푸
-: | :-
 1 | 티  
 2 | 에프  
 3 | 에프  
 4 | 에프  
delete from test where id = 1;

여기 dbfiddle


BEFORE DELETE 트리거를 추가하여 첫 번째 행 (foo가 true 임)이 삭제되지 않도록하여 해결할 수 있습니다.

create function dont_delete_foo_true()
returns trigger as
$x$
begin
    if old.foo then
        raise exception 'Can''t delete row where foo is true.';
    end if;
    return old;
end;
$x$ language plpgsql;
create trigger trg_test_delete
before delete on test
for each row 
execute procedure dont_delete_foo_true();
delete from test where id = 1;

오류 : foo가 true 인 행을 삭제할 수 없습니다.

여기 dbfiddle

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.