Jac*_*las 32 constraint database-agnostic referential-integrity
例如,有一个类似这样的表:
create table foo(bar int identity, chk char(1) check (chk in('Y', 'N')));
Run Code Online (Sandbox Code Playgroud)
标志是否实现为 a char(1)、 abit或其他无关紧要。我只是希望能够强制执行它只能在单行上设置的约束。
Mar*_*ith 32
SQL Server 2008 - 筛选的唯一索引
CREATE UNIQUE INDEX IX_Foo_chk ON dbo.Foo(chk) WHERE chk = 'Y'
Run Code Online (Sandbox Code Playgroud)
Jac*_*las 16
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);
Run Code Online (Sandbox Code Playgroud)
对于 2000 年,您可能需要SET ARITHABORT ON(感谢 @gbn 提供此信息)
Vin*_*rat 14
甲骨文:
由于 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);
Run Code Online (Sandbox Code Playgroud)
该索引最多只能索引一行。
知道这个索引事实,你还可以稍微不同地实现位列:
create table foo(bar integer, chk char(1) check (chk ='Y') UNIQUE);
Run Code Online (Sandbox Code Playgroud)
这里列的可能值chk是Yand NULL。最多只有一行可以有值Y.
小智 13
我认为这是正确构建数据库表的一种情况。更具体地说,如果你有一个人有多个地址,并且你想要一个作为默认地址,我认为你应该将默认地址的 addressID 存储在 person 表中,而不是在地址表中有一个默认列:
Person
-------
PersonID
Name
etc.
DefaultAddressID (fk to addressID)
Address
--------
AddressID
Street
City, State, Zip, etc.
Run Code Online (Sandbox Code Playgroud)
您可以使 DefaultAddressID 可以为空,但这种结构会强制执行您的约束。
Jac*_*las 12
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
Run Code Online (Sandbox Code Playgroud)
MySQL 中忽略检查约束,因此我们必须将nullorfalse视为 false 和truetrue。最多 1 行可以有chk=true
您可能认为在插入/更新时添加触发器更改false为一种改进,true作为缺少检查约束的解决方法 - IMO 虽然这不是改进。
我希望能够使用 char(0) 因为它
当您需要一个只能取两个值的列时也非常好:定义为 CHAR(0) NULL 的列只占用一位,并且只能取值 NULL 和 ''
不幸的是,至少使用 MyISAM 和 InnoDB,我得到
ERROR 1167 (42000): The used storage engine can't index column 'chk'
Run Code Online (Sandbox Code Playgroud)
- 编辑
这毕竟不是一个好的解决方案,因为在 MySQL 上,它boolean是 的同义词tinyint(1),因此允许非空值大于 0 或 1。这可能bit是更好的选择
Jac*_*las 10
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"
Run Code Online (Sandbox Code Playgroud)
- 编辑
或者(更好),使用唯一的部分索引:
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"
Run Code Online (Sandbox Code Playgroud)
这种问题是我问这个问题的另一个原因:
如果您的数据库中有一个应用程序设置表,您可以有一个条目来引用您希望被视为“特殊”的记录的 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)
);
Run Code Online (Sandbox Code Playgroud)
更改应用程序语义,以便考虑的“默认”是新表中的行。可能使用视图来封装这个逻辑。
3) 删除CHAR(1)列。添加seq整数列。对 施加唯一约束seq。更改应用程序语义,以便考虑的“默认”是seq值为 1 或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 ;
Run Code Online (Sandbox Code Playgroud)
为了确保您的表干净并且存储过程正常工作,假设 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;
Run Code Online (Sandbox Code Playgroud)
这是一个也有帮助的触发器:
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 ;
Run Code Online (Sandbox Code Playgroud)
为确保您的表干净且触发器正常工作,假设 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;
Run Code Online (Sandbox Code Playgroud)
试一试 !!!