postgresql 视图(和子查询)返回没有筛选的行

lnxxn5zx  于 2023-06-22  发布在  PostgreSQL
关注(0)|答案(1)|浏览(132)

Postgresql 14.
嗨!我有多个表,它们的名称类似于model_<uuid>,每个表的列的名称类似于element_<uuid>。所有这些表都在models模式中。我有一个查询,转换information_schema.columns视图在类似m2m表的东西。

SELECT
  SPLIT_PART(table_name, '_', 2)::UUID AS model_id,
  SPLIT_PART(column_name, '_', 2)::UUID AS element_id
FROM information_schema.columns
WHERE
  table_schema = 'models' AND
  table_name LIKE 'model\_%' AND
  column_name LIKE 'element\_%'

它工作正常。但是当我试图将其 Package 在视图中或将其用作子查询时,我得到了以下错误。
子查询示例:

SELECT model_id, ARRAY_AGG(element_id) AS element_ids
FROM (
  SELECT
    SPLIT_PART(table_name, '_', 2)::UUID AS model_id,
    SPLIT_PART(column_name, '_', 2)::UUID AS element_id
  FROM information_schema.columns
  WHERE
    table_schema = 'models' AND
    table_name LIKE 'model\_%' AND
    column_name LIKE 'element\_%'
) AS t
WHERE model_id = '2ba221e1-f2e3-4438-bafb-8e7bfefe161f'
GROUP BY model_id

错误:

ERROR: invalid input syntax for type uuid: "elements"

我有表elements,但它在public模式中,我有table_schema = 'models'过滤器。我认为这个过滤器不起作用,子查询试图将其转换为“uuid”。但是当我以普通(root?)查询时,所有行都取自models模式。
当我删除WHERE model_id = '2ba221e1-f2e3-4438-bafb-8e7bfefe161f'时,它又正常工作了。
怎么了?简直让我大吃一惊。
UPD:
好吧。我明白原因。错误的原因是查询优化器将查询筛选器model_id = '...'移动到子查询筛选器内部。在public.model_elements视图上引发错误。
我从查询中删除table_name中的所有UUID并运行以下查询:

EXPLAIN SELECT model_id, ARRAY_AGG(element_id)
FROM (
    SELECT
        table_name AS model_id,
        SPLIT_PART(column_name, '_', 2)::UUID AS element_id
    FROM information_schema.columns
    WHERE
        table_schema = 'models'
        AND table_name LIKE 'model\_%'
        AND column_name LIKE 'element\_%'
) AS t
WHERE model_id = 'abc'
GROUP BY model_id;

查询计划的片段:

->  Index Scan using pg_class_relname_nsp_index on pg_class c  (cost=0.28..4.31 rows=1 width=76)
    Index Cond: ((relname >= 'model_'::text) AND (relname < 'model`'::text) AND (relname = 'abc'::name))
    Filter: ((relname ~~ 'model\_%'::text) AND (relkind = ANY ('{r,v,f,p}'::""char""[])))"
->  Index Scan using pg_attribute_relid_attnam_index on pg_attribute a  (cost=0.29..4.32 rows=1 width=78)
    Index Cond: ((attrelid = c.oid) AND (attname >= 'element_'::text) AND (attname < 'element`'::text))
    Filter: ((NOT attisdropped) AND (attnum > 0) AND (attname ~~ 'element\_%'::text) AND (pg_has_role(c.relowner, 'USAGE'::text) OR has_column_privilege(c.oid, attnum, 'SELECT, INSERT, UPDATE, REFERENCES'::text)))"

如您所见,它通过table_name,组合了所有过滤器(根过滤器和子查询过滤器),包括SPLIT_PART(table_name, '_', 2)::UUID转换(如果我们考虑原始查询),并首先使用它们。也就是说,它首先尝试将所有表名转换为UUID,忽略那些过滤器过滤掉无法完成的表。
我发现了问题,但如何解决它?

zdwk9cvp

zdwk9cvp1#

PostgreSQL必须先计算SPLIT_PART(table_name, '_', 2)::UUID,然后才能与'2ba221e1-f2e3-4438-bafb-8e7bfefe161f'进行比较?只需避免转换为uuid并比较字符串。
此外,PostgreSQL可以自由地重新排列查询,只要它保持语义等价。在当前的例子中,它会拉取子查询并首先计算索引条件。如果您想阻止PostgreSQL拉取子查询,请在子查询的末尾添加OFFSET 0。这在语义上没有区别,但却是优化器的障碍。

相关问题