MySQL 中的 OR 非空约束
Posted
技术标签:
【中文标题】MySQL 中的 OR 非空约束【英文标题】:Either OR non-null constraints in MySQL 【发布时间】:2008-10-04 15:14:28 【问题描述】:在 mysql 中创建非 NULL 约束以使 fieldA 和 fieldB 不能都为 NULL 的最佳方法是什么?我不在乎任何一个本身是否为 NULL,只要另一个字段具有非 NULL 值即可。如果它们都具有非 NULL 值,那就更好了。
【问题讨论】:
【参考方案1】:这不是直接回答您的问题,而是一些附加信息。
在处理多个列并检查是否全部为空或一个不为空时,我通常使用COALESCE()
- 如果列表增长,它会简短、易读且易于维护:
COALESCE(a, b, c, d) IS NULL -- True if all are NULL
COALESCE(a, b, c, d) IS NOT NULL -- True if any one is not null
这可以在您的触发器中使用。
【讨论】:
【参考方案2】:@Sklivvz:使用 MySQL 5.0.51a 进行测试,我发现它解析了一个 CHECK 约束,但没有强制执行它。我可以插入 (NULL, NULL) 而没有错误。测试了 MyISAM 和 InnoDB。随后使用 SHOW CREATE TABLE 表明 CHECK 约束不在表定义中,即使我定义表时没有给出错误。
这与 MySQL manual 匹配,它表示:“CHECK 子句已解析,但被所有存储引擎忽略。”
所以对于 MySQL,您必须使用触发器来强制执行此规则。唯一的问题是 MySQL 触发器无法引发错误或中止 INSERT 操作。您可以在触发器中导致错误的一件事是将 NOT NULL 列设置为 NULL。
CREATE TABLE foo (
FieldA INT,
FieldB INT,
FieldA_or_FieldB TINYINT NOT NULL;
);
DELIMITER //
CREATE TRIGGER FieldABNotNull BEFORE INSERT ON foo
FOR EACH ROW BEGIN
IF (NEW.FieldA IS NULL AND NEW.FieldB IS NULL) THEN
SET NEW.FieldA_or_FieldB = NULL;
ELSE
SET NEW.FieldA_or_FieldB = 1;
END IF;
END//
INSERT INTO foo (FieldA, FieldB) VALUES (NULL, 10); -- OK
INSERT INTO foo (FieldA, FieldB) VALUES (10, NULL); -- OK
INSERT INTO foo (FieldA, FieldB) VALUES (NULL, NULL); -- gives error
您还需要一个类似的触发器 BEFORE UPDATE。
【讨论】:
您可以执行类似SET NEW.FieldA = 1/0
之类的操作,而不是使用NOT NULL
列来强制出错,这将引发除零错误。不是一个非常有用的错误信息,但至少你不必添加特殊的列。我真的希望 MySQL 支持正确的 CHECK
约束,或者至少有更好的方法从触发器中抛出错误。 :(
@friedo:由于 OP 的问题是关于强制执行一些特殊的非空条件,我认为空违规错误消息是合适的。您可以在 MySQL 触发器中执行的另一个技巧是在触发器中声明一个整数变量并尝试为其分配一个字符串。它会引发一条错误消息,其中包含您使用的字符串。 :)
从 MySQL 5.5 开始,您可以引发错误。我相应地添加了一个答案:***.com/a/28775560/2651243
@MichaelPlatings,谢谢我给了你+1。 FWIW,当我在 2008 年写出上面的答案时,MySQL 5.5 还不在 GA 中。
这是我最初接受的答案,直到 MySQL 5.5 和 @MichaelPlatings 新答案。只是为了后代更新。【参考方案3】:
MySQL 5.5 引入了SIGNAL,因此我们不再需要 Bill Karwin 答案中的额外列。 Bill 指出您还需要一个更新触发器,所以我也将其包括在内。
CREATE TABLE foo (
FieldA INT,
FieldB INT
);
DELIMITER //
CREATE TRIGGER InsertFieldABNotNull BEFORE INSERT ON foo
FOR EACH ROW BEGIN
IF (NEW.FieldA IS NULL AND NEW.FieldB IS NULL) THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = '\'FieldA\' and \'FieldB\' cannot both be null';
END IF;
END//
CREATE TRIGGER UpdateFieldABNotNull BEFORE UPDATE ON foo
FOR EACH ROW BEGIN
IF (NEW.FieldA IS NULL AND NEW.FieldB IS NULL) THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = '\'FieldA\' and \'FieldB\' cannot both be null';
END IF;
END//
DELIMITER ;
INSERT INTO foo (FieldA, FieldB) VALUES (NULL, 10); -- OK
INSERT INTO foo (FieldA, FieldB) VALUES (10, NULL); -- OK
INSERT INTO foo (FieldA, FieldB) VALUES (NULL, NULL); -- gives error
UPDATE foo SET FieldA = NULL; -- gives error
【讨论】:
【参考方案4】:这是此类约束的标准语法,但 MySQL 很高兴随后忽略了该约束
ALTER TABLE `generic`
ADD CONSTRAINT myConstraint
CHECK (
`FieldA` IS NOT NULL OR
`FieldB` IS NOT NULL
)
【讨论】:
因为这是标准的 SQL 语法,它也应该适用于其他基于 SQL 的数据库。 (它绝对适用于 PostgreSQL。) MySQL 手册说:“CHECK 子句被解析,但被所有存储引擎忽略。”我试过了,是真的。 它甚至被 InnoDB 表忽略了?太糟糕了。 相应编辑。如果您认为答案现在有用,请删除反对票。谢谢, 我会删除反对票,但现在 SO 告诉我“这个投票太旧了,无法撤消或更改。”此外,虽然你现在的回答是真实的,但它没有用,因为它没有解决mpeters提出的问题。 :-)【参考方案5】:我在 SQL Server 中做过类似的事情,我不确定它是否可以直接在 MySQL 中工作,但是:
ALTER TABLE tableName ADD CONSTRAINT constraintName CHECK ( (fieldA IS NOT NULL) OR (fieldB IS NOT NULL) );
至少我相信这是语法。
但是,请记住,您不能跨表创建检查约束,您只能检查一个表中的列。
【讨论】:
【参考方案6】:我使用带有 COALESCE ... NOT NULL 的 GENERATED ALWAYS 列完成了这项工作:
DROP TABLE IF EXISTS `error`;
CREATE TABLE IF NOT EXISTS `error` (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
left_id BIGINT UNSIGNED NULL,
right_id BIGINT UNSIGNED NULL,
left_or_right_id BIGINT UNSIGNED GENERATED ALWAYS AS (COALESCE(left_id, right_id)) NOT NULL,
when_occurred TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
message_text LONGTEXT NOT NULL,
INDEX id_index (id),
INDEX when_occurred_index (when_occurred),
INDEX left_id_index (left_id),
INDEX right_id_index (right_id)
);
INSERT INTO `error` (left_id, right_id, message_text) VALUES (1, 1, 'Some random text.'); -- Ok.
INSERT INTO `error` (left_id, right_id, message_text) VALUES (null, 1, 'Some random text.'); -- Ok.
INSERT INTO `error` (left_id, right_id, message_text) VALUES (1, null, 'Some random text.'); -- Ok.
INSERT INTO `error` (left_id, right_id, message_text) VALUES (null, null, 'Some random text.'); -- ER_BAD_NULL_ERROR: Column 'left_or_right_id' cannot be null
在 MySQL 8.0.22 版上
【讨论】:
以上是关于MySQL 中的 OR 非空约束的主要内容,如果未能解决你的问题,请参考以下文章