例如,使用与此类似的表:
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
或任何形式都没有关系。我只希望能够强制执行只能在单行上设置的约束。
Answers:
SQL Server 2008-筛选后的唯一索引
CREATE UNIQUE INDEX IX_Foo_chk ON dbo.Foo(chk) WHERE chk = 'Y'
甲骨文:
由于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);
该索引最多只会索引单个行。
了解了这个索引事实后,您还可以稍微不同地实现bit列:
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的跳过-在一些清晰的成本也许
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
视为false和true
true。最多1行可以chk=true
你可以认为这是一种改进,添加一个触发器来改变false
成true
在插入/更新为缺乏检查约束的解决方法-国际海事组织它不是一个虽然改善。
我希望能够使用char(0),因为它
不幸的是,至少有了MyISAM和InnoDB,我得到了
ERROR 1167 (42000): The used storage engine can't index column 'chk'
- 编辑
这毕竟不是一个好的解决方案,因为在MySQL上,它boolean
是的同义词tinyint(1)
,因此允许非空值大于0或1。这可能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"
这种问题是我问这个问题的另一个原因:
如果数据库中有一个应用程序设置表,则可能会有一个条目引用您希望被视为“特殊”记录的ID。然后,您只需从设置表中查找ID是什么,就不必为要设置的一项仅填写一列。
使用广泛实施的技术的可能方法:
1)撤消桌上的“作家”特权。创建CRUD程序,以确保在事务边界处强制执行约束。
2)6NF:放下CHAR(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)
);
更改应用程序的语义,以使所考虑的“默认”为新表中的行。可能使用视图来封装此逻辑。
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 ;
要确保您的表是干净的并且存储过程正在运行,假定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-92,广泛实施,例如SQL Server 2000及更高版本:
从表中撤消“作者”特权。为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:在CHECK
约束中使用子查询,但未广泛实现,例如在ANSI-92查询模式下在Access2000(ACE2007,Jet 4.0等)及更高版本中受支持。
示例代码:CHECK
Access中的note 约束始终是表级别的。由于问题中的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允许它,但不确定有多少其他供应商接受它。