Postgres array_position(array, element) 有时索引为 0?

Postgres array_position(array, element) sometimes 0-indexed?

Postgres 方法 array_position(array, element),与 SQL 中的其他方法一样,是基于 1 的。例如:

SELECT array_position(array[4,5,6], 5)    -- returns 2

但是,我正在执行以下查询以从 pg_catalog 中检索非唯一索引及其列:

SELECT non_unique_indexes.indname          AS index_name,
       non_unique_indexes.relname          AS table_name,
       columns.column_name                 AS column_name,
       array_position(non_unique_indexes.indkey, columns.ordinal_position::smallint) AS column_position,
       CASE non_unique_indexes.indoption[array_position(non_unique_indexes.indkey, columns.ordinal_position::smallint)]
           WHEN 1 THEN 'DESC'
           WHEN 3 THEN 'DESC'
           ELSE 'ASC'
       END AS direction
FROM (
    SELECT pg_namespace.nspname,
           pg_class_tables.relname,
           pg_class_indexes.relname AS indname,
           pg_index.indkey,
           pg_index.indoption,
           pg_tablespace.spcname
    FROM pg_catalog.pg_index
    INNER JOIN pg_catalog.pg_class pg_class_tables  ON pg_class_tables.oid  = pg_index.indrelid
    INNER JOIN pg_catalog.pg_class pg_class_indexes ON pg_class_indexes.oid = pg_index.indexrelid
    INNER JOIN pg_catalog.pg_tablespace ON pg_tablespace.oid = pg_class_indexes.reltablespace
    INNER JOIN pg_catalog.pg_namespace ON pg_namespace.oid = pg_class_indexes.relnamespace
    WHERE pg_index.indisunique = false
      AND pg_namespace.nspname = 'my_schema'
) non_unique_indexes
INNER JOIN information_schema.columns ON columns.table_schema     = non_unique_indexes.nspname
                                     AND columns.table_name       = non_unique_indexes.relname
                                     AND columns.ordinal_position = ANY(non_unique_indexes.indkey)
ORDER BY non_unique_indexes.relname,
         non_unique_indexes.indname,
         array_position(non_unique_indexes.indkey, columns.ordinal_position::smallint)

第 4 个 select 在 indkey 数组中搜索列的 ordinal_position,因此对于每一列,它都会在索引中找到它的位置。

有趣的是第一列的位置为 0,所以我必须添加 + 1 以使其基于 1。

随后的 CASE 表达式,其中使用完全相同的值作为检索 indoption 的第 n 个元素的索引,奇怪的是即使 [] 运算符是 1- 也能正常工作也基于:

SELECT (array[4,5,6])[2]    -- returns 5

这个怎么样?

我目前使用的是 PG 9.6。

数组下标

您说:

Postgres method array_position(array, element), like other things in SQL, is 1-based.

但这有点不正确。 Postgres 数组默认是从 1 开始的 。但是 Postgres 允许任何范围的整数作为索引。函数 array_position() 不是基于 anything 的。它只是 returns 找到的索引。

SELECT array_position('[7:9]={4,5,6}'::int[], 5);  -- returns 8!

参见:

pg_index.indkey 不是数组 开头。是int2vector类型,是内部类型,不通用,0基础!它允许下标(类似于数组)。转换为 int2[] 会保留从 0 开始的数组下标(索引)。

正确查询

不管怎样,您的查询似乎都不正确。

pg_tablespace 上的 INNER JOIN 消除了存储在默认 table 空间中的索引。 The manual on pg_class.reltablespace:

If zero, the database's default tablespace is implied.

但是 pg_tablespace 中没有 oid = 0 的条目,所以将其设为 LEFT JOIN.

如果您尝试手动提取部分索引定义,还有更多注意事项。 ASC / DESC 你所拥有的并没有完全解决它。参见:

  • How to get the Index column order(ASC, DESC, NULLS FIRST....) from Postgresql?

而你甚至没有考虑 NULLS FIRST | LAST。或者部分索引的可能 WHERE 条件,...

我强烈建议使用内置 System Catalog Information Function pg_get_indexdef():

这个简单、快速和可靠的替代方案
SELECT ci.relname AS index_name
     , ix.indrelid::regclass::text AS table_name
     , pg_get_indexdef (ix.indexrelid) AS idx_def
FROM   pg_catalog.pg_index     ix
JOIN   pg_catalog.pg_class     ci ON ci.oid = ix.indexrelid
JOIN   pg_catalog.pg_namespace ns ON ns.oid = ci.relnamespace
WHERE  ix.indisunique = false
AND    ns.nspname = 'my_schema'
ORDER  BY 2, 1;

The manual:

Reconstructs the creating command for an index. (This is a decompiled reconstruction, not the original text of the command.)

这使所有方面都正确并保持跨 Postgres 版本的工作。

您的查询

如果你坚持分解索引定义,这个查询应该基本上可以工作(从 Postgres 14 开始):

SELECT ci.relname AS index_name
     , ct.relname AS table_name
     , pg_get_indexdef (ix.indexrelid, pos::int, false) AS idx_expression
     , CASE WHEN ia.indopt & 1 = 1 THEN 'DESC' ELSE 'ASC' END AS direction
     , CASE WHEN ia.indopt & 2 = 2 THEN 'NULLS FIRST' ELSE 'NULLS LAST' END AS direction_nulls
     , pg_get_expr(ix.indpred, ix.indrelid) AS where_clause
     , ia.pos AS column_position
     , ix.indkey
     , ix.indoption
FROM   pg_catalog.pg_index     ix
JOIN   pg_catalog.pg_class     ct ON ct.oid = ix.indrelid
JOIN   pg_catalog.pg_class     ci ON ci.oid = ix.indexrelid
JOIN   pg_catalog.pg_namespace ns ON ns.oid = ci.relnamespace
LEFT   JOIN pg_catalog.pg_tablespace ts ON ts.oid = ci.reltablespace
CROSS  JOIN LATERAL unnest(ix.indkey, ix.indoption) WITH ORDINALITY AS ia(attnum, indopt, pos)
WHERE  ix.indisunique = false
AND    ns.nspname = 'my_schema'
ORDER  BY ct.relname, ci.relname, ia.pos;

但“正确的查询”更科学table并且更可靠。

特别是我将 unnest() 与多个参数一起使用,以在锁步和顺序(基于 1 的)位置上取消嵌套 indkeyindoption。参见:

关于WITH ORDINALITY

  • PostgreSQL unnest() with element number

我用pg_get_indexdef()重建每个索引字段。这也包括表达式,而不仅仅是普通的 table 列。

我加了direction_nulls表示NULLS FIRST | LAST,见:

  • Sort by column ASC, but NULL values first?

where_clause 带有部分索引的反编译 WHERE 子句(使用 pg_get_expr())。