답변:
편집 : 이것은 우리가 MySQL을 알기 전에 SQL Server에 맞춰졌습니다.
이 작업을 수행하는 방법 에는 4 가지가 있습니다.
우리는 RDBMS가 무엇인지 알지 못한다.
가장 좋은 방법은 필터링 된 인덱스입니다. 이것은 DRI를 사용하여 고유성을 유지합니다.
고유 한 계산 열 (Jack Douglas의 답변 참조) (편집 2로 추가)
DRI를 사용한 필터링 된 인덱스와 같은 인덱스 / 구체화 된 뷰
트리거 (다른 답변에 따라)
"하나의 기본값"에 대한 요구 사항은 저장 프로 시저가 아니라 테이블에 있습니다. 저장 프로 시저에는 udf의 검사 제약 조건과 동일한 동시성 문제가 있습니다.
참고 : SO에 여러 번 질문했습니다.
참고 :이 답변은 asker가 MySQL에 특정한 것을 원하기 전에 제공되었습니다. 이 답변은 SQL Server에 편향되어 있습니다.
UDF 를 호출 하고 리턴 값이 <= 1인지 확인 하는 테이블에 CHECK 제한 조건을 적용 하십시오. UDF는 WHERE 테이블의 행을 계수 할 수 있습니다 . 이렇게하면 테이블에 기본 행이 1 개를 넘지 않아야합니다.isDefault = TRUE
이 UDF가 매우 빠르게 실행되도록 하려면 열에 비트 맵 또는 필터링 된 인덱스를 추가해야합니다 isDefault
(플랫폼에 따라 다름).
경고
PostalCodes
테이블 에 대한 대량 업데이트를 수행 할 가능성은 없지만 세트 기반 활동이 발생할 가능성이있는 상황에서는 성능 문제가 남아 있습니다.이러한 모든 단점을 감안할 때 , 체크 제한 대신 필터링 된 고유 인덱스에 대한 gbn의 제안 을 사용하는 것이 좋습니다 .
다음은 저장 프로 시저 (MySQL Dialect)입니다.
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;
트리거를 사용하여 규칙을 적용 할 수 있습니다. UPDATE 또는 INSERT 문이 isDefault를 True로 설정하면 트리거의 SQL이 다른 모든 행을 False로 설정할 수 있습니다.
이것을 적용 할 때 다른 상황을 고려해야합니다. 예를 들어, 둘 이상의 행이 있고 UPDATE 또는 INSERT가 Default로 True 인 경우 어떻게됩니까? 규칙을 어 기지 않기 위해 어떤 규칙이 적용됩니까?
또한 기본값이없는 조건이 가능합니까? 업데이트가 isDefault를 True에서 False로 설정하면 어떻게됩니까?
규칙을 정의하면 트리거에서 규칙을 작성할 수 있습니다.
그런 다음 다른 답변에 표시된대로 규칙을 적용하는 데 도움이되는 검사 제한 조건을 적용하려고합니다.
다음은 예제 테이블 및 데이터를 설정합니다.
IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[MyTable]') AND type in (N'U'))
DROP TABLE [dbo].[MyTable]
GO
CREATE TABLE dbo.MyTable
(
[id] INT IDENTITY(1,1) PRIMARY KEY CLUSTERED
, [IsDefault] BIT DEFAULT 0
)
GO
INSERT dbo.MyTable DEFAULT VALUES
GO 100
IsDefault 플래그를 설정하기 위해 저장 프로 시저를 만들고 기본 테이블을 변경할 수있는 권한을 제거하는 경우 아래 쿼리를 사용하여이를 적용 할 수 있습니다. 매번 테이블 스캔이 필요합니다.
DECLARE @Id INT
SET @Id = 10
UPDATE
dbo.MyTable
SET
IsDefault = CASE WHEN [id] = @Id THEN 1 ELSE 0 END
GO
테이블의 크기에 따라 IsDefault (DESC)의 인덱스는 전체 쿼리를 피하기 위해 아래 쿼리 중 하나 또는 둘 다를 초래할 수 있습니다.
DECLARE @Id INT
SET @Id = 10
UPDATE
dbo.MyTable
SET
IsDefault = CASE WHEN [id] = @Id THEN 1 ELSE 0 END
FROM
dbo.MyTable
WHERE
[id] = @Id
OR IsDefault = 1
UPDATE
dbo.MyTable
SET
IsDefault = CASE WHEN [id] = @Id THEN 1 ELSE 0 END
FROM
dbo.MyTable
WHERE
[id] = @Id
OR ([id] != @Id AND IsDefault = 1)
기본 테이블에서 권한을 제거 할 수없고 다른 방법으로 무결성을 강화해야하며 SQL2008을 사용하는 경우 필터링 된 고유 인덱스를 사용할 수 있습니다.
CREATE UNIQUE INDEX IX_MyTable_IsDefault ON dbo.MyTable (IsDefault) WHERE IsDefault = 1
필터링 된 인덱스가없는 MySQL의 경우 다음과 같은 작업을 수행 할 수 있습니다. MySQL은 고유 인덱스에 여러 NULL을 허용한다는 사실을 이용하고 전용 테이블과 외래 키를 사용하여 NULL과 1 만 값으로 가질 수있는 데이터 유형을 시뮬레이션합니다.
이 솔루션을 사용하면 true / false 대신 1 / NULL을 사용합니다.
CREATE TABLE DefaultFlag (id TINYINT UNSIGNED NOT NULL PRIMARY KEY);
INSERT INTO DefaultFlag (id) VALUES (1);
CREATE TABLE PostalCodes (
ID INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
Zip VARCHAR (32) NOT NULL,
isDefault TINYINT UNSIGNED NULL DEFAULT NULL,
CONSTRAINT FOREIGN KEY (isDefault) REFERENCES DefaultFlag (id),
UNIQUE KEY (isDefault)
);
INSERT INTO PostalCodes (Zip, isDefault) VALUES ('123', 1 );
/* Only one row can be default */
INSERT INTO PostalCodes (Zip, isDefault) VALUES ('abc', 1 );
/* ERROR 1062 (23000): Duplicate entry '1' for key 'isDefault' */
/* MySQL allows multiple NULLs in unique indexes */
INSERT INTO PostalCodes (Zip, isDefault) VALUES ('456', NULL);
INSERT INTO PostalCodes (Zip, isDefault) VALUES ('789', NULL);
/* only NULL and 1 are admitted in isDefault thanks to foreign key */
INSERT INTO PostalCodes (Zip, isDefault) VALUES ('789', 2 );
/* ERROR 1452 (23000): Cannot add or update a child row: a foreign key constraint fails (`test`.`PostalCodes`, CONSTRAINT `PostalCodes_ibfk_1` FOREIGN KEY (`isDefault`) REFERENCES `DefaultFlag` (`id`))*/
내가 잘못 생각할 수있는 유일한 것은 누군가가 DefaultFlag
테이블에 행을 추가하는 것 입니다. 나는 이것이 큰 관심사가 아니라고 생각하며, 정말로 안전하고 싶다면 항상 권한으로 해결할 수 있습니다.
SELECT ID, Zip FROM PostalCodes WHERE isDefault=True
필드를 잘못된 위치에 놓기 만하면 문제가 발생합니다. 그게 전부입니다.
PostalCode가있는 'Defaults'테이블을 사용하면 찾고있는 ID가 있습니다. 일반적으로 하나의 레코드에 따라 테이블 이름을 지정하는 것이 좋으므로 초기 테이블 이름은 'PostalCode'라고하는 것이 좋습니다. 기본값은 다른 구성 (예 : 내역)에 대해 기본값을 특수화해야 할 때까지 단일 행 테이블이됩니다.
원하는 최종 쿼리는 다음과 같습니다.
select Zip from PostalCode p, Defaults d where p.ID=d.PostalCode;
PostalCodes
비어있는 경우 는 어떻습니까? 행에 이미 속성이 있어야하는 경우 동일한 SQL 문 내에서 다른 행 (있는 경우)이 true로 설정되어 있지 않으면 false로 설정되지 않습니까? 트랜잭션 경계 사이에 0 개의 행이 속성을 가질 수 있습니까? 테이블의 마지막 행에 속성이 있어야하고 삭제되지 않아야합니까? 경험에 따르면 "정확히 하나의 행을 보장"하는 것은 실제로는 단순히 "최대 하나의 행"과는 다른 것을 의미하는 경향이 있습니다.