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!
参见:
- Normalize array subscripts so they start with 1
- Why does PostgreSQL allow querying for array[0] even though it uses 1-based arrays?
而 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;
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 的)位置上取消嵌套 indkey
和 indoption
。参见:
关于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()
)。
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!
参见:
- Normalize array subscripts so they start with 1
- Why does PostgreSQL allow querying for array[0] even though it uses 1-based arrays?
而 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;
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 的)位置上取消嵌套 indkey
和 indoption
。参见:
关于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()
)。