Position of the column in the index - postgresql

How to get the position of a column in the index in PostgreSQL database? indkey in pg_index catalog table gives the position of that column in it's table, but i want the position of the column in it's containing index.

Here's one way. It might not be the best way.
SELECT c.relname, a.attname, a.attnum
FROM pg_attribute a
INNER JOIN pg_class c on c.oid = a.attrelid
WHERE c.relkind = 'i'
AND c.relname = 'beds_pkey'
AND a.attnum > 0
where beds_pkey is the name of the index.

Here is a query that retrieves the position on the index when you search by table:
select
c.relname as tabela,
a.relname as indexname,
d.attname as coluna,
(
select
temp.i + 1
from
(
SELECT generate_series(array_lower(b.indkey,1),array_upper(b.indkey,1)) as i
) temp
where
b.indkey[i] = d.attnum
) as posicao
from
pg_class a
inner join
pg_index b
on
a.oid = b.indexrelid
inner join
pg_class c
on
b.indrelid = c.oid
inner join
pg_attribute d
on
c.oid = d.attrelid and
d.attnum = any(b.indkey)
where
b.indisprimary != true and
a.relname not like 'pg_%'
order by
tabela, indexname, posicao

indkey is an array and the order of the entries in that array determine the order of the index columns.
So if indkey contains {2,4} then the second column of the table comes first in the index, and the table's fourth column is the second column in the index.
If indkey contains {4,3} then the table's fourth column is the first column in the index and the table's third column is the index' second column.

Just use array_position() function to get desired column index within indkey array:
select c.relname, a.attname,
array_position(i.indkey, a.attnum)
from pg_index i
join pg_class c
on c.oid = i.indexrelid
and c.relkind = 'i'
join pg_attribute a
on a.attrelid = 'your_table'::regclass::oid
and a.attnum = any(i.indkey)
where indrelid = 'your_table'::regclass::oid
order by 1, 2;

Related

How to list unused indices and drop them in postgres?

Unused indexes take lot of space and slow down data modifications. How can I check if my indexes are really useful and delete unused ones?
Make sure your applications have run long enough since the last stat reset. Think especially of periodic jobs that runs once every week and may require an index.
This is the code to make a human readable view with usefull indices information. The indices on partitionned table are aggregated. If you have templates tables for your partitions, make sure the filters for template tables names are accurate.
CREATE VIEW stats_index_aggregate as
-- Index for partitionned tables
select
'partitioned index' as indextype,
nsp.nspname as schemaname,
table_class.relname as tablename,
parent_class.relname as indexname,
index_columns.idx_columns as idx_columns,
seek_childs.nb_child_index,
seek_childs.nb_scans,
seek_childs.index_size
from pg_class parent_class
join pg_index parent_index on parent_index.indexrelid = parent_class.oid
join pg_namespace nsp on nsp.oid = parent_class.relnamespace -- to get schemaname
join pg_class table_class on table_class.oid = parent_index.indrelid
, lateral (
select count(stats_child.idx_scan) as nb_child_index,
sum(stats_child.idx_scan) as nb_scans,
sum(pg_relation_size(stats_child.indexrelid)) as index_size
from pg_catalog.pg_stat_user_indexes stats_child
join pg_inherits pi on pi.inhrelid = stats_child.indexrelid
where pi.inhparent = parent_class.oid
) seek_childs
, LATERAL (
SELECT string_agg(attname, ', ' order by attnum) AS idx_columns
FROM pg_attribute
WHERE attrelid = parent_class.oid
) index_columns
where parent_class.relkind = 'I'
AND 0 <>ALL (parent_index.indkey) -- no index column is an expression
AND NOT parent_index.indisunique -- is not a UNIQUE index
AND NOT EXISTS -- does not enforce a constraint
(SELECT 1 FROM pg_catalog.pg_constraint cc WHERE cc.conindid = parent_index.indexrelid)
and table_class.relname not like '%template' -- filter for template tables
union
-- Index for regular tables
select
'regular index' as indextype,
stats_child.schemaname,
stats_child.relname AS tablename,
c.relname as indexname,
index_columns.idx_columns as idx_columns,
null as nb_child_index,
stats_child.idx_scan as id_scan_count,
pg_relation_size(stats_child.indexrelid) as index_size
from pg_class c
join pg_index idx_parent on idx_parent.indexrelid = c.oid
join pg_catalog.pg_stat_user_indexes stats_child on c.oid = stats_child.indexrelid
, LATERAL (
SELECT string_agg(attname, ', ' order by attnum) AS idx_columns
FROM pg_attribute
WHERE attrelid = c.oid
) index_columns
where c.relkind = 'i'
AND 0 <>ALL (idx_parent.indkey) -- no index column is an expression
AND NOT idx_parent.indisunique -- is not a UNIQUE index
AND NOT EXISTS -- does not enforce a constraint
(SELECT 1 FROM pg_catalog.pg_constraint cc
WHERE cc.conindid = idx_parent.indexrelid)
AND NOT EXISTS -- is not a child index
(SELECT 1 FROM pg_inherits pi
where pi.inhrelid = c.oid)
and stats_child.relname not like '%template'; -- filter for template tables
Then you can check how much space you can save with this summary :
select count(*) || ' indices are not used, you can drop them and save ' || pg_size_pretty(sum(index_size)) as pretty_size from stats_index_aggregate where nb_scans = 0;
Execute this query output to clean your database :
select
case when nb_scans = 0 then 'DROP INDEX ' || indexname || ';' else '' end as drop_command, -- make sure we don't print drop command for used index
*, pg_size_pretty(index_size) as pretty_size
from stats_index_aggregate;

How to get column name from pg_constraint

I need to get a list of constraints from db and match them with columns they are related to
For this task I'm going to use pg_constraint. Here is the query that I use
select conrelid::regclass AS table_from, conname, pg_get_constraintdef(c.oid)
from pg_constraint c
join pg_namespace n ON n.oid = c.connamespace
where conrelid::regclass::text = 'test' order by contype;
As you can see I'm interested in constraints for table called test
Here is the output
table_from | conname | pg_get_constraintdef
------------+----------------+----------------------
test | test_age_check | CHECK ((age > 0))
But I want to get a column name for this constraint using query above (In this case column is age). According to doc Column constraints are not treated specially. But maybe there are some ways I can use to retrieve column name from pg_constraint ?
pg_constrint.conkey holds an array of the columns ordinals in the table. You can use them to join pg_attribute. Here the ordinal is in the column attnum.
SELECT c.conrelid::regclass AS table_from,
c.conname,
pg_get_constraintdef(c.oid),
a.attname
FROM pg_constraint c
INNER JOIN pg_namespace n
ON n.oid = c.connamespace
CROSS JOIN LATERAL unnest(c.conkey) ak(k)
INNER JOIN pg_attribute a
ON a.attrelid = c.conrelid
AND a.attnum = ak.k
WHERE c.conrelid::regclass::text = 'test'
ORDER BY c.contype;

Get information about table partitions

I have a partitioned table out of main table using range.
CREATE TABLE public.partition1 PARTITION OF public.maintable
FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2050-01-01 00:00:00')
How can i get the Values range information using a query to postgres.
I have used a query that at least gives me information for the main and partitioned tables, but i cannot seem to find a way to access the value range
FROM ('2017-01-01 00:00:00') TO ('2050-01-01 00:00:00')
assigned to table partition1
Query used to get partition table information
WITH RECURSIVE partition_info
(relid,
relname,
relsize,
relispartition,
relkind) AS
(
(SELECT oid AS relid,
relname,
pg_relation_size(oid) AS relsize,
relispartition,
relkind
FROM pg_catalog.pg_class
WHERE relname = 'completedorders' AND
relkind = 'p')
UNION ALL
(SELECT
c.oid AS relid,
c.relname AS relname,
pg_relation_size(c.oid) AS relsize,
c.relispartition AS relispartition,
c.relkind AS relkind
FROM partition_info AS p,
pg_catalog.pg_inherits AS i,
pg_catalog.pg_class AS c
WHERE p.relid = i.inhparent AND
c.oid = i.inhrelid AND
c.relispartition = true)
)
SELECT * FROM partition_info;
The following query provides the information about partitions as well. From there on its just string manipulation in order to get further information.
Note: you will have to change the name of the table in the query.
with recursive inh as (
select i.inhrelid, null::text as parent
from pg_catalog.pg_inherits i
join pg_catalog.pg_class cl on i.inhparent = cl.oid
join pg_catalog.pg_namespace nsp on cl.relnamespace = nsp.oid
where nsp.nspname = 'public' ---<< change table schema here
and cl.relname = 'tablename' ---<< change table name here
union all
select i.inhrelid, (i.inhparent::regclass)::text
from inh
join pg_catalog.pg_inherits i on (inh.inhrelid = i.inhparent)
)
select c.relname as partition_name,
n.nspname as partition_schema,
pg_get_expr(c.relpartbound, c.oid, true) as partition_expression,
pg_get_expr(p.partexprs, c.oid, true) as sub_partition,
parent,
case p.partstrat
when 'l' then 'LIST'
when 'r' then 'RANGE'
end as sub_partition_strategy
from inh
join pg_catalog.pg_class c on inh.inhrelid = c.oid
join pg_catalog.pg_namespace n on c.relnamespace = n.oid
left join pg_partitioned_table p on p.partrelid = c.oid
order by n.nspname, c.relname

List columns with indexes in Amazon Redshift

I need to query Redshift metadata to get a list of table columns that includes information whether the column is part of primary key or not.
There is a post already List columns with indexes in PostgreSQL that has an answer for PostgreSQL, however unfortunately, it fails on Redshift with "ERROR: 42809: op ANY/ALL (array) requires array on right side"
I figured out how to do it with the help of this https://bitbucket.org/zzzeek/sqlalchemy/pull-request/6/sqlalchemy-to-support-postgresql-80/diff
SELECT attname column_name, attnotnull,
format_type(atttypid, atttypmod) as column_type, atttypmod,
i.indisprimary as primary_key,
col_description(attrelid, attnum) as description
FROM pg_attribute c
LEFT OUTER JOIN pg_index i
ON c.attrelid = i.indrelid AND i.indisprimary AND
c.attnum = ANY(string_to_array(textin(int2vectorout(i.indkey)), ' '))
where c.attnum > 0 AND NOT c.attisdropped AND c.attrelid = :tableOid
order by attnum
The following worked for me:
SELECT n.nspname as schema_name,
t.relname as table_name,
i.relname as index_name,
c.contype as index_type,
a.attname as column_name,
a.attnum AS column_position
FROM pg_class t
INNER JOIN pg_index AS ix ON t.oid = ix.indrelid
INNER JOIN pg_constraint AS c ON ix.indrelid = c.conrelid
INNER JOIN pg_class AS i ON i.oid = ix.indexrelid
INNER JOIN pg_attribute AS a ON a.attrelid = t.oid
AND a.attnum= ANY(string_to_array(textin(int2vectorout(ix.indkey)),' ')::int[])
INNER JOIN pg_namespace AS n ON n.oid = t.relnamespace;
You can leverage the table DDL view AWS published a few months ago (https://github.com/awslabs/amazon-redshift-utils/blob/master/src/AdminViews/v_generate_tbl_ddl.sql) by picking out the constraint component and parsing out the key columns:
select schemaname,tablename, substring(ddl,charindex('(',ddl)+1, charindex(')',ddl)-1-charindex('(',ddl))
from
(
SELECT
n.nspname AS schemaname
,c.relname AS tablename
,200000000 + CAST(con.oid AS INT) AS seq
,'\t,' + pg_get_constraintdef(con.oid) AS ddl
FROM
pg_constraint AS con
INNER JOIN pg_class AS c
ON c.relnamespace = con.connamespace
AND c.relfilenode = con.conrelid
INNER JOIN pg_namespace AS n
ON n.oid = c.relnamespace
WHERE c.relkind = 'r'
ORDER BY seq
)
Note that this query also gives you foreign key columns. It's easy enough to filter those out by appending the query with
where ddl like '%PRIMARY KEY%'
Use below query:
select * from pg_table_def where tablename = 'mytablename'
This will give you all columns for table along with their data type , encoding and if it has sort key or dist key.

List all index names, column names and its table name of a PostgreSQL database

What is the query to get the list all index names, its column name and its table name of a postgresql database?
I have tried to get the list of all indexes in a db by using this query but how to get the list of indexes, its column names and its table names?
SELECT *
FROM pg_class, pg_index
WHERE pg_class.oid = pg_index.indexrelid
AND pg_class.oid IN (
SELECT indexrelid
FROM pg_index, pg_class
WHERE pg_class.oid=pg_index.indrelid
AND indisunique != 't'
AND indisprimary != 't'
AND relname !~ '^pg_');
This will output all indexes with details (extracted from my view definitions):
SELECT i.relname as indname,
i.relowner as indowner,
idx.indrelid::regclass,
am.amname as indam,
idx.indkey,
ARRAY(
SELECT pg_get_indexdef(idx.indexrelid, k + 1, true)
FROM generate_subscripts(idx.indkey, 1) as k
ORDER BY k
) as indkey_names,
idx.indexprs IS NOT NULL as indexprs,
idx.indpred IS NOT NULL as indpred
FROM pg_index as idx
JOIN pg_class as i
ON i.oid = idx.indexrelid
JOIN pg_am as am
ON i.relam = am.oid;
Optionally add an extra join to the end so as to trim the namespaces:
SELECT i.relname as indname,
i.relowner as indowner,
idx.indrelid::regclass,
am.amname as indam,
idx.indkey,
ARRAY(
SELECT pg_get_indexdef(idx.indexrelid, k + 1, true)
FROM generate_subscripts(idx.indkey, 1) as k
ORDER BY k
) as indkey_names,
idx.indexprs IS NOT NULL as indexprs,
idx.indpred IS NOT NULL as indpred
FROM pg_index as idx
JOIN pg_class as i
ON i.oid = idx.indexrelid
JOIN pg_am as am
ON i.relam = am.oid
JOIN pg_namespace as ns
ON ns.oid = i.relnamespace
AND ns.nspname = ANY(current_schemas(false));
More human friendly version of #Denis solution:
SELECT
U.usename AS user_name,
ns.nspname AS schema_name,
idx.indrelid :: REGCLASS AS table_name,
i.relname AS index_name,
idx.indisunique AS is_unique,
idx.indisprimary AS is_primary,
am.amname AS index_type,
idx.indkey,
ARRAY(
SELECT pg_get_indexdef(idx.indexrelid, k + 1, TRUE)
FROM
generate_subscripts(idx.indkey, 1) AS k
ORDER BY k
) AS index_keys,
(idx.indexprs IS NOT NULL) OR (idx.indkey::int[] #> array[0]) AS is_functional,
idx.indpred IS NOT NULL AS is_partial
FROM pg_index AS idx
JOIN pg_class AS i
ON i.oid = idx.indexrelid
JOIN pg_am AS am
ON i.relam = am.oid
JOIN pg_namespace AS NS ON i.relnamespace = NS.OID
JOIN pg_user AS U ON i.relowner = U.usesysid
WHERE NOT nspname LIKE 'pg%'; -- Excluding system tables
The Query to list all the indexes of a database
SELECT
tablename,
indexes [1],
indexes [2],
indexes [3],
indexes [4],
indexes [5],
indexes [6],
indexes [7],
indexes [8],
indexes [9],
indexes [10]
FROM (SELECT
tablename,
array_agg(indexname) AS indexes
FROM pg_indexes
WHERE schemaname = 'public'
GROUP BY tablename) as sub;
Here's a version that simplifies things compared to other answers by
avoiding nested selects
avoiding built-in functions (maybe hard to remember)
using LATERAL and UNNEST(...) WITH ORDINALITY features available in later PostgreSQL versions (9.4+)
SELECT
tnsp.nspname AS schema_name,
trel.relname AS table_name,
irel.relname AS index_name,
array_agg (
a.attname
|| ' ' || CASE o.option & 1 WHEN 1 THEN 'DESC' ELSE 'ASC' END
|| ' ' || CASE o.option & 2 WHEN 2 THEN 'NULLS FIRST' ELSE 'NULLS LAST' END
ORDER BY c.ordinality
) AS columns
FROM pg_index AS i
JOIN pg_class AS trel ON trel.oid = i.indrelid
JOIN pg_namespace AS tnsp ON trel.relnamespace = tnsp.oid
JOIN pg_class AS irel ON irel.oid = i.indexrelid
CROSS JOIN LATERAL unnest (i.indkey) WITH ORDINALITY AS c (colnum, ordinality)
LEFT JOIN LATERAL unnest (i.indoption) WITH ORDINALITY AS o (option, ordinality)
ON c.ordinality = o.ordinality
JOIN pg_attribute AS a ON trel.oid = a.attrelid AND a.attnum = c.colnum
GROUP BY tnsp.nspname, trel.relname, irel.relname
If you are also interested in index size, you may use this query from the PostgreSQL Wiki.
SELECT
t.tablename,
indexname,
c.reltuples AS num_rows,
pg_size_pretty(pg_relation_size(quote_ident(t.tablename)::text)) AS table_size,
pg_size_pretty(pg_relation_size(quote_ident(indexrelname)::text)) AS index_size,
CASE WHEN indisunique THEN 'Y'
ELSE 'N'
END AS UNIQUE,
idx_scan AS number_of_scans,
idx_tup_read AS tuples_read,
idx_tup_fetch AS tuples_fetched
FROM pg_tables t
LEFT OUTER JOIN pg_class c ON t.tablename=c.relname
LEFT OUTER JOIN
( SELECT c.relname AS ctablename, ipg.relname AS indexname, x.indnatts AS number_of_columns, idx_scan, idx_tup_read, idx_tup_fetch, indexrelname, indisunique FROM pg_index x
JOIN pg_class c ON c.oid = x.indrelid
JOIN pg_class ipg ON ipg.oid = x.indexrelid
JOIN pg_stat_all_indexes psai ON x.indexrelid = psai.indexrelid )
AS foo
ON t.tablename = foo.ctablename
WHERE t.schemaname='public'
ORDER BY 1,2;
For Non-Composite Indexes
select t.relname,i.relname ,
STRING_AGG(pga.attname||'', ','order by i.relname,pga.attnum) as columnName
from pg_class t inner join pg_index ix
on t.oid = ix.indrelid
inner join pg_class i
on i.oid = ix.indexrelid
inner join pg_attribute pga
on
pga.attrelid = i.oid
inner join pg_indexes pgidx
on pgidx.indexname=i.relname
where
t.relkind = 'r'
and pgidx.schemaname='asit_cm'
and t.relname ='accessory'
group by t.relname,i.relname having count(*)=1
For Composite Indexes
select t.relname,i.relname ,
STRING_AGG(pga.attname||'', ','order by i.relname,pga.attnum) as columnName
from pg_class t inner join pg_index ix
on t.oid = ix.indrelid
inner join pg_class i
on i.oid = ix.indexrelid
inner join pg_attribute pga
on
pga.attrelid = i.oid
inner join pg_indexes pgidx
on pgidx.indexname=i.relname
where
t.relkind = 'r'
and pgidx.schemaname='asit_cm'
and t.relname ='accessory'
group by t.relname,i.relname having count(*)=1