在列上强制执行唯一性但允许一些重复的最佳实践?

时间:2017-07-01 12:24:57

标签: sql postgresql database-design unique-index

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

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

我可以用我有限的经验来提出这些:

  • 使用Postgresql手册(5.3.3. - > Example 11-3.)中提到的部分索引。它还提到This is a particularly efficient approach when there are few successful tests and many unsuccessful ones。在我们要迁移的旧数据库中,每月有130,000行和大约5个临时授权,但整个表每年仅增长约200行。这是正确的方法吗? (我也不确定"效率"在这种情况下意味着什么。)
  • 为临时授权创建一个单独的表,但它会复制表结构。
  • 为一组列定义唯一约束。授权是针对发给个人的特定时间段内的特定服务。

编辑:

对不起,我认为我对授权ID的描述有点模糊:它由状态部门提供,格式为NMED012345678,并且是手动输入的。它是独一无二的,但有时仅在以后for unknown reasons提供。

4 个答案:

答案 0 :(得分:5)

有一种简单,快速和安全的方式:

添加布尔列以标记临时条目,默认情况下为NULL,例如:

temp bool DEFAULT NULL CHECK (temp)

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

列默认表示您通常不必处理该列。它默认为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添加到应该使用唯一索引的查询中。

相关:

答案 1 :(得分:2)

您可以有多种可能性:

  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'

答案 2 :(得分:1)

评论时间有点长。

我想我会拥有一个具有唯一授权的授权表。授权可以有两种类型:“已批准”和“临时”。你可以用两列来处理这个问题。

但是,我可能会将授权ID作为序列列,其中“已批准”的ID是表中的字段。该表可能有一个独特的约束。您可以使用完整的unique约束或带有过滤值的唯一约束(Postgres允许在唯一约束中使用多个NULL值,但第二个更明确)。

您可以使用相同的过程进行临时授权 - 使用不同的列。大概你有一些机制来授权它们并存储批准日期,时间和人。

我不会使用两张桌子。授权在多个表之间传播似乎很容易引起混淆。代码中您希望查看谁拥有授权的任何地方都可能会误读数据。

答案 3 :(得分:1)

IMO不建议使用远程键作为主键的一部分。

  • 他们不在你的控制之下;他们可以改变
  • 您无法保证正确性和/或唯一性(电子邮件地址,电话号码,许可证号码,序列号)
  • 使用它们AS PK会导致它们被用作其他表的FK到这个表中,其中包含胖索引和大量更改的级联。

\ t 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%'
        ;