强制列唯一性但允许重复的最佳实践?
Best practice to enforce uniqueness on column but allow some duplicates?
这是我想弄清楚的:应该有一个 table 来存储我们新的客户管理系统的授权,并且每个授权都有其唯一的标识符。此约束很容易转换为 SQL 但不幸的是,由于官僚主义的缓慢,有时我们需要创建一个带有占位符 ID(例如 "temp")的条目,以便客户端可以开始接受服务了。
执行此条件唯一性约束的最佳做法是什么?
这些是我有限的经验可以想出的:
- 使用 Postgresql 手册中提到的部分索引ing (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 个临时授权,但整个 table 每年只增长大约 200 行。这是正确的方法吗? (我也不确定 "efficient" 在这种情况下是什么意思。)
- 为临时授权创建一个单独的 table,但它会复制 table 结构。
- 为一组列定义唯一约束。授权是在特定时间段内向个人颁发的特定服务。
编辑:
对不起,我对授权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
,只有NULL
或TRUE
是可能的。默认 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
你可以有几种可能:
使 temp 标识符唯一;例如,如果它们是自动创建的(未输入 手动 ),则使它们:
CREATE SEQUENCE temp_ids_seq ; -- This done only once for the database
每当您需要新的临时 ID 时,请发布
'temp' || nxtval('temp_ids_seq') AS id
使用部分索引,假设允许的值为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%'
;
这是我想弄清楚的:应该有一个 table 来存储我们新的客户管理系统的授权,并且每个授权都有其唯一的标识符。此约束很容易转换为 SQL 但不幸的是,由于官僚主义的缓慢,有时我们需要创建一个带有占位符 ID(例如 "temp")的条目,以便客户端可以开始接受服务了。
执行此条件唯一性约束的最佳做法是什么?
这些是我有限的经验可以想出的:
- 使用 Postgresql 手册中提到的部分索引ing (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 个临时授权,但整个 table 每年只增长大约 200 行。这是正确的方法吗? (我也不确定 "efficient" 在这种情况下是什么意思。) - 为临时授权创建一个单独的 table,但它会复制 table 结构。
- 为一组列定义唯一约束。授权是在特定时间段内向个人颁发的特定服务。
编辑:
对不起,我对授权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
,只有NULL
或TRUE
是可能的。默认 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
你可以有几种可能:
使 temp 标识符唯一;例如,如果它们是自动创建的(未输入 手动 ),则使它们:
CREATE SEQUENCE temp_ids_seq ; -- This done only once for the database
每当您需要新的临时 ID 时,请发布
'temp' || nxtval('temp_ids_seq') AS id
使用部分索引,假设允许的值为
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%'
;