如何匹配复合类型数组中的元素?
How to match elements in an array of composite type?
假设我们有两个 tables:
CREATE TABLE element (
pk1 BIGINT NOT NULL,
pk2 BIGINT NOT NULL,
pk3 BIGINT NOT NULL,
-- other columns ...
PRIMARY KEY (pk1, pk2, pk3)
);
CREATE TYPE element_pk_t AS (
pk1 BIGINT,
pk2 BIGINT,
pk3 BIGINT
);
CREATE TABLE collection (
id BIGINT,
elements element_pk_t[] NOT NULL,
);
element
有复合PK。自定义类型 element_pk_t
注册了一个匹配的复合类型。 collection
table 包含 element_pk_t
.
数组
我想在单个查询中查询 table element
中 PK 与所选 collection.elements
中的元素匹配的所有行。
我尝试过的:
SELECT *
FROM element
WHERE (pk1, pk2, pk3) IN (SELECT unnest(elements)
FROM collection
WHERE id = 1);
我在 IN
子句中遇到错误:
ERROR: subquery has too few columns
但是,这有效:
SELECT *
FROM element
WHERE (pk1, pk2, pk3) IN ((1, 2, 3), (4, 5, 6));
看来问题是如何将自定义类型element_pk_t
扩展到3列可以匹配(pk1, pk2, pk3)
.
这个有效:
SELECT *
FROM element
WHERE (pk1, pk2, pk3) IN (SELECT (unnest(elements)).*
FROM collection
WHERE id = 1);
或更冗长,但 更可取:
SELECT *
FROM element
WHERE (pk1, pk2, pk3) IN (SELECT (e).*
FROM collection c, unnest(c.elements) e
WHERE c.id = 1);
更稳健,避免多次评估 unnest()
。参见:
这也有效:
SELECT *
FROM element
WHERE ROW((pk1, pk2, pk3)) IN (SELECT unnest(elements)
FROM collection
WHERE id = 1);
问题的核心是IN
采用子查询知道两种不同的形式。引用 the manual:
expression IN (subquery)
row_constructor IN (subquery)
您失败的查询 解析为第二种形式,而您(可以理解)期待第一种形式。但是第二种形式是这样做的:
The left-hand side of this form of IN
is a row constructor, as
described in Section 4.2.13. The right-hand side is a
parenthesized subquery, which must return exactly as many columns as
there are expressions in the left-hand row. The left-hand expressions
are evaluated and compared row-wise to each row of the subquery
result. [...]
我的第一个和第二个查询 使其在运算符右侧的decomposing the row type 处起作用。所以 Postgres 左右三个 bigint
值就满足了。
我的 第三个查询 通过将行类型嵌套在另一个 row constructor 的左侧使其工作。 Postgres 仅分解第一级并以单个复合类型结束 - 匹配右侧的单个复合类型。
请注意,我们要包装的单个字段需要关键字 ROW
。 The manual:
The key word ROW
is optional when there is more than one expression in the list.
您的工作查询[=87=] 略有不同,因为它为right 而不是 subquery (set)。这是采用不同代码路径的不同实现。它甚至得到 separate chapter in the manual。此变体对左侧的 ROW 构造函数没有特殊处理。所以它就像你预期的那样工作。
更多等效(有效)语法变体 = ANY
:
SELECT * FROM element
WHERE (pk1, pk2, pk3) = ANY ('{"(1,2,3)","(2,3,4)"}'::element_pk_t[]);
SELECT * FROM element
WHERE (pk1, pk2, pk3) = ANY (ARRAY[(1,2,3)::element_pk_t,(2,3,4)::element_pk_t]);
SELECT * FROM element
WHERE (pk1, pk2, pk3) = ANY (ARRAY[(1,2,3),(2,3,4)]::element[]);
对 (pk1, pk2, pk3)::element_pk_t
或 ROW(pk1, pk2, pk3)::element_pk_t
也有效
参见:
- IN vs ANY operator in PostgreSQL
由于您的来源是 数组,Daniel 使用 (e.pk1, e.pk2, e.pk3) = ANY(c.elements)
的第二个查询很自然。
但是为了打赌 最快的查询 ,我把钱花在了我的第二个变体上,因为我希望它能最佳地使用 PK 索引。
作为概念验证。就像 a_horse 评论的那样:规范化的数据库设计可能最适合扩展。
假设我们有两个 tables:
CREATE TABLE element (
pk1 BIGINT NOT NULL,
pk2 BIGINT NOT NULL,
pk3 BIGINT NOT NULL,
-- other columns ...
PRIMARY KEY (pk1, pk2, pk3)
);
CREATE TYPE element_pk_t AS (
pk1 BIGINT,
pk2 BIGINT,
pk3 BIGINT
);
CREATE TABLE collection (
id BIGINT,
elements element_pk_t[] NOT NULL,
);
element
有复合PK。自定义类型 element_pk_t
注册了一个匹配的复合类型。 collection
table 包含 element_pk_t
.
我想在单个查询中查询 table element
中 PK 与所选 collection.elements
中的元素匹配的所有行。
我尝试过的:
SELECT *
FROM element
WHERE (pk1, pk2, pk3) IN (SELECT unnest(elements)
FROM collection
WHERE id = 1);
我在 IN
子句中遇到错误:
ERROR: subquery has too few columns
但是,这有效:
SELECT *
FROM element
WHERE (pk1, pk2, pk3) IN ((1, 2, 3), (4, 5, 6));
看来问题是如何将自定义类型element_pk_t
扩展到3列可以匹配(pk1, pk2, pk3)
.
这个有效:
SELECT *
FROM element
WHERE (pk1, pk2, pk3) IN (SELECT (unnest(elements)).*
FROM collection
WHERE id = 1);
或更冗长,但 更可取:
SELECT *
FROM element
WHERE (pk1, pk2, pk3) IN (SELECT (e).*
FROM collection c, unnest(c.elements) e
WHERE c.id = 1);
更稳健,避免多次评估 unnest()
。参见:
这也有效:
SELECT *
FROM element
WHERE ROW((pk1, pk2, pk3)) IN (SELECT unnest(elements)
FROM collection
WHERE id = 1);
问题的核心是IN
采用子查询知道两种不同的形式。引用 the manual:
expression IN (subquery)
row_constructor IN (subquery)
您失败的查询 解析为第二种形式,而您(可以理解)期待第一种形式。但是第二种形式是这样做的:
The left-hand side of this form of
IN
is a row constructor, as described in Section 4.2.13. The right-hand side is a parenthesized subquery, which must return exactly as many columns as there are expressions in the left-hand row. The left-hand expressions are evaluated and compared row-wise to each row of the subquery result. [...]
我的第一个和第二个查询 使其在运算符右侧的decomposing the row type 处起作用。所以 Postgres 左右三个 bigint
值就满足了。
我的 第三个查询 通过将行类型嵌套在另一个 row constructor 的左侧使其工作。 Postgres 仅分解第一级并以单个复合类型结束 - 匹配右侧的单个复合类型。
请注意,我们要包装的单个字段需要关键字 ROW
。 The manual:
The key word
ROW
is optional when there is more than one expression in the list.
您的工作查询[=87=] 略有不同,因为它为right 而不是 subquery (set)。这是采用不同代码路径的不同实现。它甚至得到 separate chapter in the manual。此变体对左侧的 ROW 构造函数没有特殊处理。所以它就像你预期的那样工作。
更多等效(有效)语法变体 = ANY
:
SELECT * FROM element
WHERE (pk1, pk2, pk3) = ANY ('{"(1,2,3)","(2,3,4)"}'::element_pk_t[]);
SELECT * FROM element
WHERE (pk1, pk2, pk3) = ANY (ARRAY[(1,2,3)::element_pk_t,(2,3,4)::element_pk_t]);
SELECT * FROM element
WHERE (pk1, pk2, pk3) = ANY (ARRAY[(1,2,3),(2,3,4)]::element[]);
对 (pk1, pk2, pk3)::element_pk_t
或 ROW(pk1, pk2, pk3)::element_pk_t
参见:
- IN vs ANY operator in PostgreSQL
由于您的来源是 数组,Daniel 使用 (e.pk1, e.pk2, e.pk3) = ANY(c.elements)
的第二个查询很自然。
但是为了打赌 最快的查询 ,我把钱花在了我的第二个变体上,因为我希望它能最佳地使用 PK 索引。
作为概念验证。就像 a_horse 评论的那样:规范化的数据库设计可能最适合扩展。