强制列唯一性但允许重复的最佳实践?

Best practice to enforce uniqueness on column but allow some duplicates?

这是我想弄清楚的:应该有一个 table 来存储我们新的客户管理系统的授权,并且每个授权都有其唯一的标识符。此约束很容易转换为 SQL 但不幸的是,由于官僚主义的缓慢,有时我们需要创建一个带有占位符 ID(例如 "temp")的条目,以便客户端可以开始接受服务了。

执行此条件唯一性约束的最佳做法是什么?

这些是我有限的经验可以想出的:


编辑:

对不起,我对授权ID的描述有点晦涩:它是由国家部门提供的,格式为NMED012345678,并且是手工输入的。它是独一无二的,但有时只在以后提供 for unknown reasons.

评论有点长

我想我会得到一个具有独特授权的授权 table。授权可以有两种类型:"approved" 和 "temporary"。你可以用两列来处理这个问题。

但是,我可能会将授权 ID 作为序列列,"approved" ID 是 table 中的 字段。 table 可以有一个唯一的约束。您可以使用完整的 unique 约束或具有过滤值的唯一约束(Postgres 允许在唯一约束中使用多个 NULL 值,但第二个更明确)。

您可以对临时授权使用相同的流程 -- 使用不同的列。大概您有一些机制来授权他们并存储批准日期、时间和人员。

我不会使用两个 table。将授权分布在多个 table 之间似乎很可能会造成混乱。代码中任何您想查看谁拥有授权的地方都可能误读数据。

有一个简单、快速、安全的方法:

添加一个布尔列来标记默认为 NULL 的临时条目,例如:

temp bool DEFAULT NULL CHECK (temp)

添加的检查约束不允许FALSE,只有NULLTRUE是可能的。默认 NULL 值的存储成本通常是......没有 - 除非该行中没有其他 NULL 值。

  • How much disk-space is needed to store a NULL value using postgresql DB?

列默认值意味着您通常不必处理该列。它默认为 NULL(which is the default default anyway,我只是在这里明确说明)。您只需要明确标记少数例外情况。

然后创建部分唯一索引,如:

CREATE UNIQUE INDEX tbl_unique_id_uni ON tbl (unique_id) WHERE temp IS NULL;

这只包括应该是唯一的行。索引大小根本没有增加。 请务必将谓词 WHERE temp IS NULL 添加到应该使用唯一索引的查询中。

相关:

  • Create unique constraint with null columns

你可以有几种可能:

  1. 使 temp 标识符唯一;例如,如果它们是自动创建的(未输入 手动 ),则使它们:

    CREATE SEQUENCE temp_ids_seq ;  -- This done only once for the database
    

    每当您需要新的临时 ID 时,请发布

    'temp' || nxtval('temp_ids_seq') AS id 
    
  2. 使用部分索引,假设允许的值为temp

    CREATE UNIQUE INDEX tbl_unique_idx ON tbl (id) WHERE (id IS DISTINCT FROM 'temp')
    

    为了提高效率,在这些情况下,您可能还希望有补充索引:

    CREATE INDEX tbl_temp_idx ON tbl (id) WHERE (id IS NOT DISTINCT FROM 'temp')
    

    最后一个索引将有助于查询 id = 'temp'

IMO 不建议使用 远程 键作为(部分)主键。

  • 它们不受您的控制;他们可以改变
  • 您不能保证正确性and/or唯一性(电子邮件地址、电话号码、许可证号、序列号)
  • 将它们用作 PK 会导致它们被用作此 table 中其他 table 的 FK,具有脂肪索引和大量级联变化。

\i tmp.sql

CREATE TABLE the_persons
        ( seq SERIAL NOT NULL PRIMARY KEY -- surrogate key
        , registrationnumber varchar -- "remote" KEY, not necesarily UNIQUE
        , is_validated BOOLEAN NOT NULL DEFAULT FALSE
        , last_name varchar
        , dob DATE
        );

CREATE INDEX name_dob_idx ON the_persons(last_name, dob)
        ;

CREATE UNIQUE INDEX registrationnumber_idx ON the_persons(registrationnumber,seq)
-- WHERE is_validated = False
        ;

CREATE UNIQUE INDEX registrationnumber_key ON the_persons(registrationnumber)
WHERE is_validated = True
        ;

INSERT INTO the_persons(is_validated,registrationnumber,last_name, dob)VALUES
 ( True, 'OKAY001', 'Smith', '1988-02-02')
,( True, 'OKAY002', 'Jones', '1988-02-02')
,( False, 'OKAY001', 'Smith', '1988-02-02')
,( False, 'OMG001', 'Smith', '1988-08-02')
        ;

-- validated records:
SELECT *
FROM the_persons
WHERE is_validated = True
        ;

-- some records with nasty cousins
SELECT *
FROM the_persons p
WHERE EXISTS (
        SELECT*
        FROM the_persons x
        WHERE x.registrationnumber = p.registrationnumber
        AND x.is_validated = False
        )
AND last_name LIKE 'Smith%'
        ;