예를 들어 다음과 비슷한 테이블이 있습니다.
create table foo(bar int identity, chk char(1) check (chk in('Y', 'N')));
플래그가 char(1)
, a bit
또는 무엇 으로 구현되는지는 중요하지 않습니다 . 단지 단일 행에서만 설정할 수있는 제약 조건을 적용하고 싶습니다.
예를 들어 다음과 비슷한 테이블이 있습니다.
create table foo(bar int identity, chk char(1) check (chk in('Y', 'N')));
플래그가 char(1)
, a bit
또는 무엇 으로 구현되는지는 중요하지 않습니다 . 단지 단일 행에서만 설정할 수있는 제약 조건을 적용하고 싶습니다.
답변:
SQL Server 2000, 2005 :
고유 색인에 하나의 널만 허용된다는 사실을 이용할 수 있습니다.
create table t( id int identity,
chk1 char(1) not null default 'N' check(chk1 in('Y', 'N')),
chk2 as case chk1 when 'Y' then null else id end );
create unique index u_chk on t(chk2);
2000의 경우 필요할 수 있습니다 SET ARITHABORT ON
(이 정보는 @gbn 덕분에)
신탁:
Oracle은 모든 색인화 된 열이 널인 항목을 색인화하지 않으므로 함수 기반 고유 색인을 사용할 수 있습니다.
create table foo(bar integer, chk char(1) not null check (chk in('Y', 'N')));
create unique index idx on foo(case when chk='Y' then 'Y' end);
이 인덱스는 최대 하나의 행만 인덱싱합니다.
이 인덱스 사실을 알면 비트 열을 약간 다르게 구현할 수도 있습니다.
create table foo(bar integer, chk char(1) check (chk ='Y') UNIQUE);
여기서 열 수있는 값이 chk
될 것이다 Y
및NULL
. 최대 하나의 행만 값을 가질 수 있습니다Y.
not null
제약이 필요 합니까?
not null
null을 원하지 않으면 제약 조건을 추가 할 수 있습니다 (질문 스펙에서 나에게 명확하지 않았습니다). 어쨌든 하나의 행만 'Y'값을 가질 수 있습니다.
default
한다.
Y
또는 null
일 수 있음을 수락하면 더 간단한 가능성을 알 수 있습니다 . 내 업데이트를 참조하십시오.
null
아마도 일부 명확성의 비용 - s는 생략됩니다
데이터베이스 테이블을 올바르게 구성하는 경우라고 생각합니다. 좀 더 구체적으로 말하면, 여러 주소를 가진 사람이 있고 하나를 기본값으로 사용하려면 주소 테이블에 기본 열이 아닌 개인 테이블에 기본 주소의 addressID를 저장해야한다고 생각합니다.
Person
-------
PersonID
Name
etc.
DefaultAddressID (fk to addressID)
Address
--------
AddressID
Street
City, State, Zip, etc.
DefaultAddressID를 널 입력 가능하게 만들 수 있지만이 방법으로 구조체가 제약 조건을 적용합니다.
MySQL :
create table foo(bar serial, chk boolean unique);
insert into foo(chk) values(null);
insert into foo(chk) values(null);
insert into foo(chk) values(false);
insert into foo(chk) values(true);
select * from foo;
+-----+------+
| bar | chk |
+-----+------+
| 1 | NULL |
| 2 | NULL |
| 3 | 0 |
| 4 | 1 |
+-----+------+
insert into foo(chk) values(true);
ERROR 1062 (23000): Duplicate entry '1' for key 2
insert into foo(chk) values(false);
ERROR 1062 (23000): Duplicate entry '0' for key 2
검사 제약 조건은 MySQL에서 무시되므로 거짓 null
또는 참 false
으로 간주 하거나 고려해야 true
합니다. 최대 1 행chk=true
변경 false
을 위한 트리거를 추가하는 것이 개선 된 것으로 간주 할 수 있습니다.true
점검 제한 조건의 부족에 대한 해결 방법으로 삽입 / 업데이트에 - IMO는 그래도 개선되지 않습니다.
char (0)을 사용할 수 있기를 바랐습니다.
불행히도 MyISAM과 InnoDB는 적어도
ERROR 1167 (42000): The used storage engine can't index column 'chk'
--편집하다
이것은 MySQL 이후로 좋은 해결책이 아니며에 boolean
대한 동의어tinyint(1)
이므로 0 또는 1보다 null이 아닌 값을 허용 bit
합니다. 더 나은 선택이 될 수 있습니다.
null
, 더 좋은 것이 있는지 궁금합니다 ...false
true
PostgreSQL :
create table foo(bar serial, chk char(1) unique check(chk='Y'));
insert into foo default values;
insert into foo default values;
insert into foo(chk) values('Y');
select * from foo;
bar | chk
-----+-----
1 |
2 |
3 | Y
insert into foo(chk) values('Y');
ERROR: duplicate key value violates unique constraint "foo_chk_key"
--편집하다
또는 (훨씬 좋은 경우) 고유 부분 인덱스를 사용하십시오 .
create table foo(bar serial, chk boolean not null default false);
create unique index foo_i on foo(chk) where chk;
insert into foo default values;
insert into foo default values;
insert into foo(chk) values(true);
select * from foo;
bar | chk
-----+-----
1 | f
2 | f
3 | t
(3 rows)
insert into foo(chk) values(true);
ERROR: duplicate key value violates unique constraint "foo_i"
이런 종류의 문제는 내가이 Quiestion을 요구 한 또 다른 이유입니다.
데이터베이스에 응용 프로그램 설정 테이블이있는 경우 '특별한'것으로 간주하려는 한 레코드의 ID를 참조하는 항목이있을 수 있습니다. 그런 다음 설정 테이블에서 ID가 무엇인지 검색하면 하나의 항목을 설정하기 위해 전체 열이 필요하지 않습니다.
널리 구현 된 기술을 사용하는 가능한 접근 방식 :
1) 테이블에서 '작성자'권한을 취소하십시오. 트랜잭션 경계에서 제한 조건이 적용되도록 CRUD 프로 시저를 작성하십시오.
2) 6NF : CHAR(1)
열을 삭제하십시오 . 카디널리티가 1을 초과 할 수 없도록 제한되는 참조 테이블을 추가하십시오.
alter table foo ADD UNIQUE (bar);
create table foo_Y
(
x CHAR(1) DEFAULT 'x' NOT NULL UNIQUE CHECK (x = 'x'),
bar int references foo (bar)
);
고려 된 'default'가 새 테이블의 행이되도록 응용 프로그램 시맨틱을 변경하십시오. 이 논리를 캡슐화하기 위해 뷰를 사용할 수 있습니다.
3) CHAR(1)
컬럼을 삭제하십시오 . seq
정수 열을 추가하십시오 . 에 고유 제한 조건을 설정하십시오 seq
. 고려되는 'default'가 seq
값이 하나이거나 seq
가장 큰 / 가장 작은 값 또는 유사한 값인 행이되도록 응용 프로그램 의미를 변경하십시오 . 이 논리를 캡슐화하기 위해 뷰를 사용할 수 있습니다.
MySQL을 사용하는 사람들에게 적합한 저장 프로시 저는 다음과 같습니다.
DELIMITER $$
DROP PROCEDURE IF EXISTS SetDefaultForZip;
CREATE PROCEDURE SetDefaultForZip (NEWID INT)
BEGIN
DECLARE FOUND_TRUE,OLDID INT;
SELECT COUNT(1) INTO FOUND_TRUE FROM PostalCode WHERE isDefault = TRUE;
IF FOUND_TRUE = 1 THEN
SELECT ID INTO OLDID FROM PostalCode WHERE isDefault = TRUE;
IF NEWID <> OLDID THEN
UPDATE PostalCode SET isDefault = FALSE WHERE ID = OLDID;
UPDATE PostalCode SET isDefault = TRUE WHERE ID = NEWID;
END IF;
ELSE
UPDATE PostalCode SET isDefault = TRUE WHERE ID = NEWID;
END IF;
END;
$$
DELIMITER ;
ID 200이 기본값이라고 가정하고 테이블이 깨끗하고 저장 프로 시저가 작동하는지 확인하려면 다음 단계를 수행하십시오.
ALTER TABLE PostalCode DROP INDEX isDefault_ndx;
UPDATE PostalCodes SET isDefault = FALSE;
ALTER TABLE PostalCode ADD INDEX isDefault_ndx (isDefault);
CALL SetDefaultForZip(200);
SELECT ID FROM PostalCodes WHERE isDefault = TRUE;
다음도 도움이되는 트리거입니다.
DELIMITER $$
CREATE TRIGGER postalcodes_bu BEFORE UPDATE ON PostalCodes FOR EACH ROW
BEGIN
DECLARE FOUND_TRUE,OLDID INT;
IF NEW.isDefault = TRUE THEN
SELECT COUNT(1) INTO FOUND_TRUE FROM PostalCode WHERE isDefault = TRUE;
IF FOUND_TRUE = 1 THEN
SELECT ID INTO OLDID FROM PostalCode WHERE isDefault = TRUE;
UPDATE PostalCodes SET isDefault = FALSE WHERE ID = OLDID;
END IF;
END IF;
END;
$$
DELIMITER ;
ID 200이 기본값이라고 가정하고 테이블이 깨끗하고 트리거가 작동하는지 확인하려면 다음 단계를 수행하십시오.
DROP TRIGGER postalcodes_bu;
ALTER TABLE PostalCode DROP INDEX isDefault_ndx;
UPDATE PostalCodes SET isDefault = FALSE;
ALTER TABLE PostalCode ADD INDEX isDefault_ndx (isDefault);
DELIMITER $$
CREATE TRIGGER postalcodes_bu BEFORE UPDATE ON PostalCodes FOR EACH ROW
BEGIN
DECLARE FOUND_TRUE,OLDID INT;
IF NEW.isDefault = TRUE THEN
SELECT COUNT(1) INTO FOUND_TRUE FROM PostalCode WHERE isDefault = TRUE;
IF FOUND_TRUE = 1 THEN
SELECT ID INTO OLDID FROM PostalCode WHERE isDefault = TRUE;
UPDATE PostalCodes SET isDefault = FALSE WHERE ID = OLDID;
END IF;
END IF;
END;
$$
DELIMITER ;
UPDATE PostalCodes SET isDefault = TRUE WHERE ID = 200;
SELECT ID FROM PostalCodes WHERE isDefault = TRUE;
시도 해봐 !!!
SQL Server 2000 이상에서는 인덱싱 된 뷰를 사용하여 원하는 것과 같은 복잡한 (또는 다중 테이블) 제약 조건을 구현할 수 있습니다.
또한 Oracle은 검사 제한 조건이 지연된 구체화 된 뷰에 대해 유사한 구현을 가지고 있습니다.
여기
내 게시물을 참조 하십시오 .
SQL Server 2000 이상과 같이 널리 구현 된 표준 전환 SQL-92 :
테이블에서 'writer'권한을 취소하십시오. 두 가지 뷰를 생성 WHERE chk = 'Y'
및 WHERE chk = 'N'
포함, 각각 WITH CHECK OPTION
. 들어 WHERE chk = 'Y'
보기의 카디널리티를 초과 할 수 없다는 취지의 검색 조건을 포함한다. 보기에 대한 '작성자'권한을 부여하십시오.
뷰의 예제 코드 :
CREATE VIEW foo_chk_N
AS
SELECT *
FROM foo AS f1
WHERE chk = 'N'
WITH CHECK OPTION
CREATE VIEW foo_chk_Y
AS
SELECT *
FROM foo AS f1
WHERE chk = 'Y'
AND 1 >= (
SELECT COUNT(*)
FROM foo AS f2
WHERE f2.chk = 'Y'
)
WITH CHECK OPTION
좀 더 우아한 가상 열을 사용하는 MySQL 및 MariaDB 솔루션이 있습니다. MySQL> = 5.7.6 또는 MariaDB> = 5.2가 필요합니다.
MariaDB [db]> create table foo(bar varchar(255), chk boolean);
MariaDB [db]> describe foo;
+-------+--------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+--------------+------+-----+---------+-------+
| bar | varchar(255) | YES | | NULL | |
| chk | tinyint(1) | YES | | NULL | |
+-------+--------------+------+-----+---------+-------+
2 rows in set (0.00 sec)
고유 제한을 적용하지 않으려는 경우 NULL 인 가상 열을 작성하십시오.
MariaDB [db]> ALTER table foo ADD checked_bar varchar(255) as (IF(chk, bar, null)) PERSISTENT UNIQUE;
(MySQL의 경우 STORED
대신 사용하십시오 PERSISTENT
.)
MariaDB [db]> insert into foo(bar, chk) values('a', false);
Query OK, 1 row affected (0.00 sec)
MariaDB [db]> insert into foo(bar, chk) values('a', false);
Query OK, 1 row affected (0.01 sec)
MariaDB [salt_dev]> insert into foo(bar, chk) values('a', false);
Query OK, 1 row affected (0.00 sec)
MariaDB [db]> insert into foo(bar, chk) values('a', true);
Query OK, 1 row affected (0.00 sec)
MariaDB [db]> insert into foo(bar, chk) values('a', true);
ERROR 1062 (23000): Duplicate entry 'a' for key 'checked_bar'
MariaDB [db]> insert into foo(bar, chk) values('b', true);
Query OK, 1 row affected (0.00 sec)
MariaDB [db]> select * from foo;
+------+------+-------------+
| bar | chk | checked_bar |
+------+------+-------------+
| a | 0 | NULL |
| a | 0 | NULL |
| a | 0 | NULL |
| a | 1 | a |
| b | 1 | b |
+------+------+-------------+
표준 FULL SQL-92 : ANSI-92 쿼리 모드CHECK
에서 Access2000 (ACE2007, Jet 4.0 등)에서 지원되는 등 널리 구현되지 않은 제약 조건 에서 하위 쿼리를 사용합니다 .
예제 코드 : CHECK
Access의 참고 제약 조건은 항상 테이블 수준입니다. CREATE TABLE
질문 의 문은 행 수준 CHECK
제약 조건을 사용하므로 쉼표를 추가하여 약간 수정해야합니다.
create table foo(bar int identity, chk char(1), check (chk in('Y', 'N')));
ALTER TABLE foo ADD
CHECK (1 >= (
SELECT COUNT(*)
FROM foo AS f2
WHERE f2.chk = 'Y'
));
나는 대답을 훑어 보았으므로 비슷한 대답을 놓쳤을 수도 있습니다. 아이디어는 pk 또는 pk의 값으로 존재하지 않는 상수 인 생성 된 열을 사용하는 것입니다.
create table foo
( bar int not null primary key
, chk char(1) check (chk in('Y', 'N'))
, some_name generated always as ( case when chk = 'N'
then bar
else -1
end )
, unique (somename)
);
AFAIK 이것은 SQL2003에서 유효합니다 (무지한 솔루션을 찾는 위치부터). DB2는이를 허용하는 다른 벤더 수를 확실하지 않습니다.