如何创建包含多个列的 MD5 的 GENERATED 列?
How to create a GENERATED column containing the MD5 of multiple columns?
我尝试在 PostgreSQL 14.3 中添加以下 table:
CREATE TABLE client_cache (
id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
request VARCHAR COMPRESSION lz4 NOT NULL CHECK (LENGTH (request) <= 10240),
request_body BYTEA COMPRESSION lz4 NOT NULL CHECK (LENGTH (request_body) <= 1048576),
request_hash VARCHAR GENERATED ALWAYS AS (MD5(ROW(request::BYTEA, request_body)::VARCHAR)) STORED
);
但是 Postgres 抱怨:
[42P17] ERROR: generation expression is not immutable
我看过很多讨论如何创建包含单列 MD5
的 GENERATED
列的答案,但是一旦您添加 ROW()
来计算 MD5
在多个列上,表达式不再是 immutable.
我可以使用 ROW(MD5(A), MD5(B))
创建一个 GENERATED
列,但不能使用 MD5(ROW(A, B))
。
我该怎么做才能在不同类型的多个列(如上所示)上创建单个 MD5
值?
我知道我可以使用触发器创建视图或填充列,但如果可能的话,我真的很想坚持使用 GENERATED
列。
我想我找到了解决办法!
Postgres 不喜欢:
request_hash VARCHAR GENERATED ALWAYS AS (MD5(ROW(request, request_body)::VARCHAR)) STORED
但
request_hash VARCHAR GENERATED ALWAYS AS (MD5(request || request_body::VARCHAR)) STORED
工作正常。
万岁!
我建议使用 immutable 辅助函数:
CREATE OR REPLACE FUNCTION f_request_md5(_request text, _request_body bytea)
RETURNS uuid
LANGUAGE sql IMMUTABLE PARALLEL SAFE AS
'SELECT md5(textin(record_out((md5(_request_body), _request))))::uuid';
还有一个 table 这样的:
CREATE TABLE client_cache (
id bigint PRIMARY KEY GENERATED ALWAYS AS IDENTITY
, request text COMPRESSION lz4 NOT NULL CHECK (length(request) <= 10240)
, request_body bytea COMPRESSION lz4 NOT NULL CHECK (length(request_body) <= 1048576)
, request_hash uuid GENERATED ALWAYS AS (f_request_md5(request, request_body)) STORED
);
db<>fiddle here
注意效率更高的 uuid
而不是 varchar
。参见:
背景
在 Postgres 14(或任何支持的版本)中有两个 md5()
的重载变体:
test=> SELECT (proargtypes::regtype[])[0], prorettype::regtype, provolatile
test-> FROM pg_proc
test-> WHERE proname = 'md5';
proargtypes | prorettype | provolatile
-------------+------------+-------------
bytea | text | i
text | text | i
(2 rows)
一个bytea
,一个text
,都是IMMUTABLE
和returntext
。所以这个表达式是 immutable:
ROW(MD5(request), MD5(request_body))
但事实并非如此,就像您通过艰难的方式发现的那样:
MD5(ROW(A, B)::varchar)
record
的文本表示不是 immutable。原因有很多。手头案例的一个明显原因是:bytea
输出可以是(默认)hex
格式或过时的 escape
格式。一个普通的
SET bytea_output = 'escape';
... 会破坏您生成的列。
要获得 bytea
值的 immutable 文本表示,您需要 运行 通过 encode(request_body, 'hex')
。但是不要去那里。 md5(request_body)
为我们的目的提供了更快的 immutable 文本“表示”。
我们仍然无法录制。所以我创建了包装函数。请务必阅读此相关答案以获得更多解释:
就像那个答案中讨论的那样,新的 built-in 函数 hash_record_extended()
会 很多 更有效率为目的。因此,如果 bigint
足够好,请考虑以下:
CREATE TABLE client_cache2 (
id bigint PRIMARY KEY GENERATED ALWAYS AS IDENTITY
, request text COMPRESSION lz4 NOT NULL CHECK (length(request) <= 10240)
, request_body bytea COMPRESSION lz4 NOT NULL CHECK (length(request_body) <= 1048576)
, request_hash bigint GENERATED ALWAYS AS (hash_record_extended((request, request_body), 0)) STORED
);
相同数据库<>fiddle here
在 Postgres 14 或更高版本中开箱即用。
相关:
- Computed / calculated / virtual / derived columns in PostgreSQL
我尝试在 PostgreSQL 14.3 中添加以下 table:
CREATE TABLE client_cache (
id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
request VARCHAR COMPRESSION lz4 NOT NULL CHECK (LENGTH (request) <= 10240),
request_body BYTEA COMPRESSION lz4 NOT NULL CHECK (LENGTH (request_body) <= 1048576),
request_hash VARCHAR GENERATED ALWAYS AS (MD5(ROW(request::BYTEA, request_body)::VARCHAR)) STORED
);
但是 Postgres 抱怨:
[42P17] ERROR: generation expression is not immutable
我看过很多讨论如何创建包含单列 MD5
的 GENERATED
列的答案,但是一旦您添加 ROW()
来计算 MD5
在多个列上,表达式不再是 immutable.
我可以使用 ROW(MD5(A), MD5(B))
创建一个 GENERATED
列,但不能使用 MD5(ROW(A, B))
。
我该怎么做才能在不同类型的多个列(如上所示)上创建单个 MD5
值?
我知道我可以使用触发器创建视图或填充列,但如果可能的话,我真的很想坚持使用 GENERATED
列。
我想我找到了解决办法!
Postgres 不喜欢:
request_hash VARCHAR GENERATED ALWAYS AS (MD5(ROW(request, request_body)::VARCHAR)) STORED
但
request_hash VARCHAR GENERATED ALWAYS AS (MD5(request || request_body::VARCHAR)) STORED
工作正常。
万岁!
我建议使用 immutable 辅助函数:
CREATE OR REPLACE FUNCTION f_request_md5(_request text, _request_body bytea)
RETURNS uuid
LANGUAGE sql IMMUTABLE PARALLEL SAFE AS
'SELECT md5(textin(record_out((md5(_request_body), _request))))::uuid';
还有一个 table 这样的:
CREATE TABLE client_cache (
id bigint PRIMARY KEY GENERATED ALWAYS AS IDENTITY
, request text COMPRESSION lz4 NOT NULL CHECK (length(request) <= 10240)
, request_body bytea COMPRESSION lz4 NOT NULL CHECK (length(request_body) <= 1048576)
, request_hash uuid GENERATED ALWAYS AS (f_request_md5(request, request_body)) STORED
);
db<>fiddle here
注意效率更高的 uuid
而不是 varchar
。参见:
背景
在 Postgres 14(或任何支持的版本)中有两个 md5()
的重载变体:
test=> SELECT (proargtypes::regtype[])[0], prorettype::regtype, provolatile
test-> FROM pg_proc
test-> WHERE proname = 'md5';
proargtypes | prorettype | provolatile
-------------+------------+-------------
bytea | text | i
text | text | i
(2 rows)
一个bytea
,一个text
,都是IMMUTABLE
和returntext
。所以这个表达式是 immutable:
ROW(MD5(request), MD5(request_body))
但事实并非如此,就像您通过艰难的方式发现的那样:
MD5(ROW(A, B)::varchar)
record
的文本表示不是 immutable。原因有很多。手头案例的一个明显原因是:bytea
输出可以是(默认)hex
格式或过时的 escape
格式。一个普通的
SET bytea_output = 'escape';
... 会破坏您生成的列。
要获得 bytea
值的 immutable 文本表示,您需要 运行 通过 encode(request_body, 'hex')
。但是不要去那里。 md5(request_body)
为我们的目的提供了更快的 immutable 文本“表示”。
我们仍然无法录制。所以我创建了包装函数。请务必阅读此相关答案以获得更多解释:
就像那个答案中讨论的那样,新的 built-in 函数 hash_record_extended()
会 很多 更有效率为目的。因此,如果 bigint
足够好,请考虑以下:
CREATE TABLE client_cache2 (
id bigint PRIMARY KEY GENERATED ALWAYS AS IDENTITY
, request text COMPRESSION lz4 NOT NULL CHECK (length(request) <= 10240)
, request_body bytea COMPRESSION lz4 NOT NULL CHECK (length(request_body) <= 1048576)
, request_hash bigint GENERATED ALWAYS AS (hash_record_extended((request, request_body), 0)) STORED
);
相同数据库<>fiddle here
在 Postgres 14 或更高版本中开箱即用。
相关:
- Computed / calculated / virtual / derived columns in PostgreSQL