如何匹配复合类型数组中的元素?

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 仅分解第一级并以单个复合类型结束 - 匹配右侧的单个复合类型。

请注意,我们要包装的单个字段需要关键字 ROWThe 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_tROW(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 评论的那样:规范化的数据库设计可能最适合扩展。