繁体   English   中英

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

[英]How to match elements in an array of composite type?

假设我们有两个表:

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表包含element_pk_t数组。

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

我试过的:

SELECT * 
FROM element 
WHERE (pk1, pk2, pk3) IN (SELECT unnest(elements) 
                          FROM collection 
                          WHERE id = 1);

我在IN子句中收到错误消息:

错误:子查询的列太少

但是,这有效:

SELECT * 
FROM element 
WHERE (pk1, pk2, pk3) IN ((1, 2, 3), (4, 5, 6));

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

这有效:

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知道两种不同的形式。 引用手册:

expression IN (subquery)

row_constructor IN (subquery)

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

这种形式的IN的左侧是一个行构造函数,如第 4.2.13 节所述。 右侧是一个带括号的子查询,它必须返回与左侧行中的表达式一样多的列。 左边的表达式被评估并与子查询结果的每一行逐行比较。 [...]

我的第一个和第二个查询通过分解运算符右侧的行类型来使其工作。 所以 Postgres 有 left 和 right 三个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[]);

也适用于(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 评论的那样:标准化的数据库设计可能会最好地扩展。

您需要进行额外级别的解包,以便使用 IN 子句使其工作。 需要从子查询返回一组常规字段以与外部行进行比较。

SELECT *
FROM element
WHERE row(pk1, pk2, pk3) IN (
  SELECT (x.el).pk1, (x.el).pk2, (x.el).pk3
  FROM collection
  CROSS JOIN LATERAL (SELECT unnest(elements) el FROM collection) x
  WHERE id = 1
)

清洁器将通过检查元素是否在连接子句中的数组中来连接。

SELECT e.*
FROM element e
INNER JOIN collection c ON row(e.pk1, e.pk2, e.pk3) = ANY(c.elements)
   AND c.id = 1

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM