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

Ziq*_*Liu 2 sql postgresql rowtype sql-in

假设我们有两个表:

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,
);
Run Code Online (Sandbox Code Playgroud)

具有element复合PK。自定义类型element_pk_t注册匹配的复合类型。该collection表包含数组element_pk_t

我想在单个查询中查询表中elementPK 与 selected 中的元素匹配的所有行。collection.elements

我尝试过的:

SELECT * 
FROM element 
WHERE (pk1, pk2, pk3) IN (SELECT unnest(elements) 
                          FROM collection 
                          WHERE id = 1);
Run Code Online (Sandbox Code Playgroud)

我在该条款中收到错误IN

错误:子查询的列太少

然而,这有效:

SELECT * 
FROM element 
WHERE (pk1, pk2, pk3) IN ((1, 2, 3), (4, 5, 6));
Run Code Online (Sandbox Code Playgroud)

所以看来问题是如何将自定义类型扩展element_pk_t为可以匹配的3列(pk1, pk2, pk3)

Erw*_*ter 5

这有效:

SELECT *
FROM   element 
WHERE  (pk1, pk2, pk3) IN (SELECT (unnest(elements)).*
                           FROM   collection
                           WHERE  id = 1);
Run Code Online (Sandbox Code Playgroud)

或者更详细,但更好

SELECT *
FROM   element 
WHERE  (pk1, pk2, pk3) IN (SELECT (e).*
                           FROM   collection c, unnest(c.elements) e
                           WHERE  c.id = 1);
Run Code Online (Sandbox Code Playgroud)

更稳健并避免unnest()多次评估。看:

这也有效:

SELECT *
FROM   element 
WHERE  ROW((pk1, pk2, pk3)) IN (SELECT unnest(elements)
                                FROM   collection
                                WHERE  id = 1);
Run Code Online (Sandbox Code Playgroud)

问题的核心是IN采用子查询知道两种不同的形式。引用手册:

expression IN (subquery)

row_constructor IN (subquery)

您失败的查询解析为第二种形式,而您(可以理解)期望第一种形式。但第二种形式是这样做的:

这种形式的左侧IN是行构造函数,如第 4.2.13 节中所述。右侧是带括号的子查询,它返回的列数必须与左侧行中的表达式的列数完全相同。左侧表达式被计算并逐行与子查询结果的每一行进行比较。[...]

我的第一个和第二个查询通过分解运算符右侧的行类型来使其工作。所以Postgres有bigint左右三个值并且满足。

我的第三个查询通过将行类型嵌套在另一个行构造函数的左侧来使其工作。Postgres 仅分解第一级并最终得到单个复合类型 - 将单个复合类型与右侧匹配。

ROW请注意,我们正在包装的单个字段需要关键字。手册:

ROW当列表中有多个表达式时,关键字是可选的。


您的工作查询略有不同,因为它提供了右侧的值列表而不是子查询set)。这是采用不同代码路径的不同实现。它甚至在手册中单独有一章。此变体对左侧的 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[]);
Run Code Online (Sandbox Code Playgroud)

也适用于(pk1, pk2, pk3)::element_pk_tROW(pk1, pk2, pk3)::element_pk_t

看:

由于您的源是一个array,丹尼尔的第二个查询 (e.pk1, e.pk2, e.pk3) = ANY(c.elements)自然就适合了。
但为了押注最快的查询,我的钱花在了第二个变体上,因为我希望它能够最佳地使用 PK 索引。

就像概念证明一样。就像 a_horse 评论的那样:标准化的数据库设计可能具有最佳的扩展性。