use*_*174 14 postgresql null unique-constraint
我有这个UNIQUE限制:
ALTER TABLE table ADD CONSTRAINT "abc123" UNIQUE
("col1", "col2", "col3", "col4", "col5", "col6", "col7", "col8");
Run Code Online (Sandbox Code Playgroud)
然后我这样做:
INSERT INTO table ("col1", "col2", "col3", "col4", "col5", "col6", "col7", "col8")
VALUES ('a', 'b', 'c', 'd', 'e', 'f', null, true);
INSERT INTO table ("col1", "col2", "col3", "col4", "col5", "col6", "col7", "col8")
VALUES ('a', 'b', 'c', 'd', 'e', 'f', null, true);
Run Code Online (Sandbox Code Playgroud)
两者都有效。两行已添加到表中。从逻辑上讲,第二个应该失败。但事实并非如此。
我究竟做错了什么?这让我发疯。
注意:如果这是我自己的数据,我将拥有一个真正独特的列,而不是这个“疯狂”的UNIQUE约束。问题是这个表保存了我的银行帐户的记录,而且他们愚蠢地在 CSV 转储中没有真正的“唯一”列,我可以用它来实际确保不插入重复的行,所以我有提出一个组合整个表中所有列以确定唯一性的方法。
Erw*_*ter 24
NULL是罪魁祸首,因为UNIQUE根据 SQL 标准,两个 NULL 值在约束中被认为是不同的。
Postgres 15 添加了一个选项来更改此行为,从而提供了一个简单的解决方案:
ALTER TABLE table ADD CONSTRAINT "abc123" UNIQUE NULLS NOT DISTINCT
(col1, col2, col3, col4, col5, col6, col7, col8);
Run Code Online (Sandbox Code Playgroud)
看:
现在可以开箱即用。然而,底层的唯一索引对于许多和/或宽列来说很大且效率低下。我仍然会考虑使用哈希值索引,如下所述。
您是否需要所有列才能使行唯一?通常,只需组合几个就足够了。银行数据应该有大量的非空列......
为了使其工作包括单个可为空的列,您可以使用部分索引,如下所述:
但如果有多个可为空的列,这种做法很快就会变得不切实际。
对于多个可为空的列,一个简单的解决方案是使用如下的唯一表达式索引COALESCE:
CREATE UNIQUE INDEX bank_uni_idx ON bank
(col1, col2, COALESCE(col3, ''), col4, col5, col6, COALESCE(col7, ''), col8);
Run Code Online (Sandbox Code Playgroud)
假设col3&col7是可为空的字符串类型列,其中空字符串 ( '') 和NULL在语义上是等效的。
显然,同样的方法也可以用于单个可为空的列。
您需要一个安全的替换,NULL它不会与其他合法值冲突(在我的示例中为空字符串)。
到目前为止,所有解决方案(包括原始解决方案)的缺点是许多列上的大型索引。可以让它变得相当昂贵。这让我得出了我真正想给出的答案:
UNIQUE基于行的廉价且足够唯一的哈希值(简化为定义列)创建索引或约束。
带有一个内置的记录哈希函数(包括匿名记录!),它比我下面的自定义函数便宜得多。
hash_record_extended(record, bigint) --> bigint
Run Code Online (Sandbox Code Playgroud)
看:
它属于与(详细信息如下)相同的函数系列hashtextextended()。现在,表达式索引似乎比生成列更有吸引力。所以就:
CREATE UNIQUE INDEX bank_hash_uni ON bank (hash_record_extended((col1, col2, col3, col4, col5, col6, col7, col8),0));
Run Code Online (Sandbox Code Playgroud)
就这样。以下大部分内容仍然适用。
将哈希值存储在生成的列中并UNIQUE对其创建约束。看:
假设所有text列。
CREATE OR REPLACE FUNCTION public.f_bank_bighash(col1 text, col2 text, col3 text, col4 text
, col5 text, col6 text, col7 text, col8 text)
RETURNS bigint
LANGUAGE sql IMMUTABLE COST 25 PARALLEL SAFE AS
'SELECT hashtextextended(textin(record_out(($1,$2,$3,$4,$5,$6,$7,$8))), 0)';
COMMENT ON FUNCTION public.f_bank_bighash(text, text, text, text, text, text, text, text)
IS 'Fast, practically unique signature for the set of defining columns in table bank.
IMMUTABLE for use in index. "record_out"() is only stable, but with only text input it is effectively immutable.';
ALTER TABLE bank
ADD COLUMN bank_bighash bigint NOT NULL GENERATED ALWAYS AS (public.f_bank_bighash(col1, col2, col3, col4, col5, col6, col7, col8)) STORED -- appends column in last position
, ADD CONSTRAINT bank_bighash_uni UNIQUE (bank_bighash);
Run Code Online (Sandbox Code Playgroud)
db<>在这里摆弄
与价值观一起工作NULL。
需要Postgres 12或更高版本,其中添加了扩展哈希函数和生成列。
hashtextextended()以及hastext()用于对散列分区或散列索引进行快速可靠的散列的内部函数。他们没有证件。但他们不会消失。正如Tom Lane 指出的
那样,它们在不同的硬件平台上可能不稳定。将数据库集群从小端系统移动到大端系统后重新创建哈希(如果发生类似的情况)。
第二个参数hashtextextended()是哈希值的盐。使用任何bigint常量,只要确保在各处使用相同的常量即可。坚持下去0,除非你更了解。
此外,虽然在巨大的 bigint 键空间下哈希冲突极不可能发生,但理论上的可能性始终存在。如果发生这种情况,您将收到两个不同行的唯一违规。如果对此感到不舒服,请md5()改为使用并存uuid储值。看:
16 个字节,uuid而不是 8 个字节bigint。计算、存储和比较的成本要高一些。理论上碰撞仍然是可能的,但你必须保持偏执。
旧的(或任何)版本可以对hashtext()返回integer. 使碰撞更有可能发生。达到几千个条目仍然不太可能。
并使用触发器使哈希列保持最新,或使用表达式的唯一索引而不是生成列的约束。
TL;DR:最多几百万行非常安全。
您可以使用“生日问题”的数学公式计算实际概率。假设有一个完美的哈希函数,bigint 哈希的数字(2^64 - 1四舍五入为2^64不同的值)为:
SELECT sqrt(2^65 * ln(1/(1 - 0.1)))::int AS p10 -- 1971577271
, sqrt(2^65 * ln(1/(1 - 0.01)))::int AS p1 -- 608926881
, sqrt(2^65 * ln(1/(1 - 0.001)))::int AS p01 -- 192124822
, sqrt(2^65 * ln(1/(1 - 0.0001)))::int AS p001 -- 60741529
, sqrt(2^65 * ln(1/(1 - 0.00001)))::int AS p0001 -- 19207726
, sqrt(2^65 * ln(1/(1 - 0.000001)))::int AS p00001 -- 6074003
Run Code Online (Sandbox Code Playgroud)
读取最后的计算p00001:
对于大约 600 万个条目,至少单个哈希冲突的概率低于 0.000001 (= 0.0001 %)。
IOW,当应用于一百万个表(每个表有 6M 行)时,我们可以预期单个表会遇到哈希冲突。
对于大约 6 亿个条目 ( p1),至少发生单个哈希冲突的概率为 0.01。
具有 16 字节密钥空间(不同值)的md5()/的计算:uuid2^128
SELECT sqrt(2^129 * ln(1/(1 - 0.000001)))::int8 AS p00001 -- 26087642172564964
Run Code Online (Sandbox Code Playgroud)
阅读:
在大约 26 万亿行时,碰撞的几率变为 0.000001。
| 归档时间: |
|
| 查看次数: |
9798 次 |
| 最近记录: |