如何在多个(超过 2 个)集合中仅强制一组字段为非 NULL?
Posted
技术标签:
【中文标题】如何在多个(超过 2 个)集合中仅强制一组字段为非 NULL?【英文标题】:How to enforce only a single set of fields to be non-NULL exclusively amongst multiple (more than 2) sets? 【发布时间】:2017-08-31 10:55:12 【问题描述】:考虑这样的设置:
具有以下 TEXT 字段的表“multi”:abc、def、ijk、lmn、uvw、xyz 将 s1 设置为以下字段的组:abc、def 将 s2 集合为以下字段的一组:ijk、lmn 将 s3 设置为以下字段的组:uvw、xyz如何强制插入“multi”的数据是这样的:
一组只能连续有效。 如果该集合中的所有字段均非 NULL 且非空,则该集合被视为有效。 如果该集合中的任何字段为 NULL 或空,则该集合被视为无效。 有效的集合必须在所有行中都是唯一的。这与an earlier similar question from me 相关(已回答),但想看看是否有可能比下面的工作代码更优化(例如:更少的约束):
DROP TABLE IF EXISTS multi;
CREATE TABLE multi(
--set1
abc TEXT,
def TEXT,
--set2
ijk TEXT,
lmn TEXT,
--set3
uvw TEXT,
xyz TEXT,
-- If some member in a set is NULL, all have to be NULL.
CONSTRAINT set1_null CHECK((abc is NULL) = (def is NULL)),
CONSTRAINT set2_null CHECK((ijk is NULL) = (lmn is NULL)),
CONSTRAINT set3_null CHECK((uvw is NULL) = (xyz is NULL)),
-- If some member in a set in non-NULL, all have to be non-NULL
CONSTRAINT set1_ntnl CHECK((abc is NOT NULL) = (def is NOT NULL)),
CONSTRAINT set2_ntnl CHECK((ijk is NOT NULL) = (lmn is NOT NULL)),
CONSTRAINT set3_ntnl CHECK((uvw is NOT NULL) = (xyz is NOT NULL)),
-- A set cannot have members of empty strings.
CONSTRAINT set1_ntmt CHECK((abc is NOT "") AND (def is NOT "")),
CONSTRAINT set2_ntmt CHECK((ijk is NOT "") AND (lmn is NOT "")),
CONSTRAINT set3_ntmt CHECK((uvw is NOT "") AND (xyz is NOT "")),
-- If all members in a set are non-NULL, all others sets should only have NULL members.
CONSTRAINT set1_excl CHECK((COALESCE(abc, def) is NOT NULL) = ((COALESCE(ijk, lmn) is NULL) AND COALESCE(uvw, xyz) is NULL))
CONSTRAINT set1_excl CHECK((COALESCE(ijk, lmn) is NOT NULL) = ((COALESCE(uvw, xyz) is NULL) AND COALESCE(abc, def) is NULL))
CONSTRAINT set1_excl CHECK((COALESCE(uvw, xyz) is NOT NULL) = ((COALESCE(abc, def) is NULL) AND COALESCE(ijk, lmn) is NULL))
-- A set can have only unique combination of its non-NULL members.
CONSTRAINT set1_uniq UNIQUE(abc, def),
CONSTRAINT set2_uniq UNIQUE(lmn, ijk),
CONSTRAINT set3_uniq UNIQUE(uvw, xyz)
);
.echo on
INSERT INTO multi(abc, def) VALUES("a1", "d1"); -- should pass: unique set1
INSERT INTO multi(abc, def) VALUES("a1", "d1"); -- should FAIL: duplicate set1
INSERT INTO multi(ijk, lmn) VALUES("i3", "l3"); -- should pass: unique set2
INSERT INTO multi(ijk, lmn) VALUES("i3", "l3"); -- should FAIL: duplicate set2
INSERT INTO multi(uvw, xyz) VALUES("u5", "x5"); -- should pass: unique set3
INSERT INTO multi(uvw, xyz) VALUES("u5", "x5"); -- should FAIL: duplicate set3
INSERT INTO multi(abc, def) VALUES(NULL, NULL); -- should FAIL: null set1
INSERT INTO multi(ijk, lmn) VALUES(NULL, NULL); -- should FAIL: null set2
INSERT INTO multi(uvw, xyz) VALUES(NULL, NULL); -- should FAIL: null set3
INSERT INTO multi(abc, def) VALUES("", ""); -- should FAIL: empty set1
INSERT INTO multi(ijk, lmn) VALUES("", ""); -- should FAIL: empty set2
INSERT INTO multi(uvw, xyz) VALUES("", ""); -- should FAIL: empty set3
INSERT INTO multi(abc) VALUES(NULL); -- should FAIL: incomplete set1
INSERT INTO multi(abc) VALUES(""); -- should FAIL: incomplete set1
INSERT INTO multi(abc) VALUES("a15"); -- should FAIL: incomplete set1
INSERT INTO multi(abc, ijk) VALUES("a16", "i16"); -- should FAIL: incomplete set1
INSERT into multi(abc, def, ijk, lmn, uvw, xyz) VALUES("", "", "", "", "", ""); -- should FAIL:
INSERT into multi(abc, def, ijk, lmn, uvw, xyz) VALUES(null, null, null, null, null, null); -- should FAIL:
INSERT into multi(abc, def, ijk, lmn, uvw, xyz) VALUES("a19", "b19", "", "", null, null); -- should FAIL:
-- etc
-- ------------------------------------
-- Only these 3 rows should be present:
-- ##|abc|def|ijk|lmn|uvw|xyz
-- 1 |a1 |d1 | | | |
-- 2 | | |i3 |l3 | |
-- 3 | | | | |u5 |x5
-- ------------------------------------
.headers ON
select rowid AS ROW, * from multi;
【问题讨论】:
【参考方案1】:您不需要setX_ntnl
约束; IS NULL 和 IS NOT NULL 总是返回相反的结果。
setX_excl
约束不需要使用 COALESCE,因为早期的 setX_null
约束已经强制集合中的所有列具有相同类型的值;您可以简单地比较每组中的一列。
还有一种更简单的方法可以检查三个集合中的一个是否有效:有效集合的数量必须是一个:
CONSTRAINT set123_excl CHECK((abc IS NOT NULL) +
(ijk IS NOT NULL) +
(uvw IS NOT NULL) = 1)
(在 SQLite 中,布尔表达式返回 0
或 1
。)
【讨论】:
确实是一个很酷的想法,但它是如何成为公认的解决方案的呢?只是好奇,因为它只会将约束数量减少两个;它仍然依赖于存在许多其他限制来发挥作用。我认为 OP 专门希望大大减少约束的数量..(即,减少到一两个) @CaiusJard 我猜他实际上是在寻找简单性。 这适用于我列出的所有测试用例(包括 Cais Jard 提到的附加测试用例)。所以,我接受了它作为答案——但没有意识到 *** 会撤销我之前的接受。【参考方案2】:我不使用 SQLite,但我使用 || 阅读。如果一个字符串为空,则连接字符串将产生空结果。这很有用,因为我们可以通过连接它们来确保两个字符串都不为空。 a || b
必须表示两者都不为空,否则为空结果。我还认为 SQLite 将空字符串和 null 视为不同,这意味着我们必须检查 a || b
是否与 a 或 b 不同..
一个基本的集合检查可以是COALESCE(a || b, '') NOT IN (a, b, '')
:
逻辑只证明了一组。如果一行必须只指定 3 个集合中的一个,则它必须是“单独证明第一组”或“单独证明第二组”或“单独证明第三组”
为了证明一个集合在行上是单独的,在通过上面的测试证明它有效之后,我们必须测试其他集合,它们的值都只是空字符串。最简单的方法是将它们全部连接起来并确保结果只是一个空字符串。如果任何值长于 0,则结果不是空字符串。如果任何值为 null,则结果不是空字符串
这是所需的完整(也是唯一)约束:
CONSTRAINT a CHECK
(
(COALESCE(abc || def, '') NOT IN (abc, def, '') AND ijk||lmn||uvw||xyz='')
OR
(COALESCE(ijk || lmn, '') NOT IN (ijk, lmn, '') AND abc||def||uvw||xyz='')
OR
(COALESCE(uvw || xyz, '') NOT IN (uvw, xyz, '') AND abc||def||ijk||lmn='')
)
创建 abc,def,ijk,lmn,uvw,xyz 的主键也应该是实现唯一性功能所需的唯一其他事情,除非一组 (abc='a',def='b ') 等同于 (abc='b',def='a')
请注意,这不是“以简洁为代价”
【讨论】:
>abc,def,ghi,jkl,uvw,xyz > i3, j3, '', '',nul,nul 你的完整约束在 sqlite3 中对我来说很好。但是,上面的测试用例通过了,尽管对于我的应用程序它应该失败了。如果您知道将其添加到完整约束或作为附加约束的方法,请告诉我。无论如何,非常感谢您的帮助.. 失败是什么?我无法确定您的示例数据中的空格是空字符串还是空值.. 那些是空字符串(出于习惯,我使用双引号而不是单引号作为字符串 - 并且可能会引起混淆)。除了您指出的测试用例之外,您的建议效果很好。 所以你想确保一个填充集只在其他集为空字符串的行上找到? 感谢凯厄斯·贾德。我应用了解决方案(在您编辑之后),但对于您提到的新案例,它仍然失败(在 sqlite3 中)。不过这对我来说肯定更简洁,我还没有做过任何性能比较。以上是关于如何在多个(超过 2 个)集合中仅强制一组字段为非 NULL?的主要内容,如果未能解决你的问题,请参考以下文章
在 Actionscript 中仅接受数字的 2 个文本字段之间切换启用/禁用