如何在多个(超过 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 中,布尔表达式返回 01。)

【讨论】:

确实是一个很酷的想法,但它是如何成为公认的解决方案的呢?只是好奇,因为它只会将约束数量减少两个;它仍然依赖于存在许多其他限制来发挥作用。我认为 OP 专门希望大大减少约束的数量..(即,减少到一两个) @CaiusJard 我猜他实际上是在寻找简单性。 这适用于我列出的所有测试用例(包括 Cais Jard 提到的附加测试用例)。所以,我接受了它作为答案——但没有意识到 *** 会撤销我之前的接受。【参考方案2】:

我不使用 SQLite,但我使用 || 阅读。如果一个字符串为空,则连接字符串将产生空结果。这很有用,因为我们可以通过连接它们来确保两个字符串都不为空。 a || b 必须表示两者都不为空,否则为空结果。我还认为 SQLite 将空字符串和 null 视为不同,这意味着我们必须检查 a || b 是否与 a 或 b 不同..

一个基本的集合检查可以是COALESCE(a || b, '') NOT IN (a, b, ''):

如果 a 或 b(或两者)为 null,则结果为 null,COALESCE 将其转换为空字符串,空字符串是集合之一,结果必须为 NOT IN 如果 a 和 b 都是空字符串,则合并后会产生一个空字符串,并且它又是“不允许”集合的成员 如果 a 是一个值且 b 为空,则结果为 a(并且这是“不允许”的成员。对于 b 具有值且 a 为空)遵循类似的逻辑

逻辑只证明了一组。如果一行必须只指定 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?的主要内容,如果未能解决你的问题,请参考以下文章

带有可选字段的 MongoDB 索引

如何在Cloud Firestore中仅填充一次日期字段?

集合视图 - 如何在每个部分中仅选择一个单元格

在 Actionscript 中仅接受数字的 2 个文本字段之间切换启用/禁用

如果字段超过 25 个,则发送多个嵌入 (Discord.js)

如何强制 C# .net 应用程序在 Windows 中仅运行一个实例? [复制]