5

I have to retrieve the order of the columns involved in the index. Using the function pg_get_indexdef() I could get the definition of the index as shown below,

"CREATE INDEX test ON ravi1.table_with_index USING btree ("Column1" DESC, "Column3" DESC, "Column4") WITH (fillfactor=60)"

Here the definition says the Column1 and Column3 is in Descending order and Column4 is in Ascending order.

With this data in String, I have to do parsing to get the column sort order.

Is there any alternative way, so that I would be able to get the values ie., the Columns order.

Right now am getting the columns associated with individual indexes using the below query

SELECT ARRAY(SELECT pg_get_indexdef(idx.indexrelid, k + 1, true) FROM
generate_subscripts(idx.indkey, 1) as k ORDER BY k ) as index_members,
idx.indexprs IS NOT NULL as indexprs
FROM pg_index as idx
JOIN pg_class as i ON i.oid = idx.indexrelid
JOIN pg_namespace as ns ON ns.oid = i.relnamespace
JOIN pg_class as t ON t.oid = idx.indrelid
where ns.nspname = 'schema' and t.relname ='table' and i.relname ='index'

In the same query, is the way to look out for the column order as well ?

This will be of a great help it worked out, otherwise i have to write some parsers to get the values from pg_get_indexdef() function.

Thanks,

Ravi

Craig Ringer
  • 307,061
  • 76
  • 688
  • 778
Ravikumar S
  • 185
  • 3
  • 12
  • I'm not surprised you're finding this hard. There's no index summary info in `information_schema` and the flexible, customisable nature of Pg's indexes makes such a seemingly simple question quite hard to answer from the system catalogs. – Craig Ringer Aug 08 '13 at 08:53
  • is there any option or parsing from index definition would be the answer ? – Ravikumar S Aug 08 '13 at 09:47
  • @CraigRinger: a quick look in the JDBC driver's source reveals, that there is a "simple" query to get this information. –  Aug 08 '13 at 14:19
  • @a_horse_with_no_name Looks useful. It can sometimes have some ... "interesting" ... queries, but is usually a good source. Sensible thought. More sensible than DIY. – Craig Ringer Aug 08 '13 at 15:10
  • https://stackoverflow.com/a/18128457 solves the problem better than answears on this page. – Petr Apr 20 '18 at 10:38

3 Answers3

8

The JDBC driver uses a much simpler query and it does return whether the column is defined as ASC or DESC

The following is more or less a verbatim copy of the source code of the driver. I removed some JDBC only columns to make it a bit more "general".

SELECT ct.relname AS TABLE_NAME, 
       i.indisunique, 
       ci.relname AS INDEX_NAME, 
       (i.keys).n AS ORDINAL_POSITION, 
       pg_catalog.pg_get_indexdef(ci.oid, (i.keys).n, false) AS COLUMN_NAME, 
       CASE am.amcanorder 
         WHEN true THEN CASE i.indoption[(i.keys).n - 1] & 1 
           WHEN 1 THEN 'DESC' 
           ELSE 'ASC' 
         END 
         ELSE NULL 
       END AS ASC_OR_DESC,
      pg_catalog.pg_get_expr(i.indpred, i.indrelid) AS FILTER_CONDITION 
FROM pg_catalog.pg_class ct 
  JOIN pg_catalog.pg_namespace n ON (ct.relnamespace = n.oid) 
  JOIN (SELECT i.indexrelid, i.indrelid, i.indoption, 
          i.indisunique, i.indisclustered, i.indpred, 
          i.indexprs, 
          information_schema._pg_expandarray(i.indkey) AS keys 
        FROM pg_catalog.pg_index i) i 
    ON (ct.oid = i.indrelid) 
  JOIN pg_catalog.pg_class ci ON (ci.oid = i.indexrelid) 
  JOIN pg_catalog.pg_am am ON (ci.relam = am.oid) 
WHERE n.nspname = 'some_schema'
AND ct.relname = 'some_table'

Obsolete Warning: As of PostgreSQL 9.6, the columns on pg_am are no longer available.

Jonathan Allen
  • 68,373
  • 70
  • 259
  • 447
  • Huh, interesting that it sneakily uses `information_schema._pg_expandarray(i.indkey)`. I guess it wants to be portable back before `unnest`. Looking at `select * from pg_proc where proname = '_pg_expandarray'` it turns out to just be a simple sql function anyway, not a c-level unnest-with-ordinal cleverly hidden from the world. – Craig Ringer Aug 08 '13 at 15:21
  • @ a_horse_with_no_name. Thanks a lot. It works and saved me lot of time and effort. – Ravikumar S Aug 12 '13 at 09:39
5

Putting such a query in your app is a great way to guarantee that a future maintainer will hate you forever. If you must do it, define a view in the DB that can at least be changed easily - and please propose a new entry in information_schema for this so it can be made accessible in a nice sane way in future.

I'm not surprised you're having trouble. After re- reading the docs on pg_index, pg_am, etc I thought it'd be an indoption. That was easy to confirm by creating two identical indexes, one asc, one desc. Making sure to interpret them correctly though...

I landed up looking at the source code, src/backend/utils/adt/ruleutils.c function pg_get_indexdef_worker.

This shows that it first tests whether pg_am.amcanorder is true, and if so decodes the bits in indoption.

This will get you the reloptions for columns that are orderable, assuming you want indexes for a table named blah2:

SELECT
  i.relname, i.indrelid, k AS ordinalpos, i.indoption[k-1]
FROM (
  SELECT 
    pg_class.relname, 
    pg_index.indrelid, pg_index.indclass, pg_index.indoption,
    unnest(indkey) as k
  FROM pg_index
  INNER JOIN pg_class ON pg_index.indexrelid = pg_class.oid
  WHERE pg_index.indrelid = 'blah2'::regclass
) i
INNER JOIN pg_opclass on (pg_opclass.oid = i.indclass[k-1]) 
INNER JOIN pg_am ON (pg_opclass.opcmethod = pg_am.oid)
WHERE pg_am.amcanorder;

The definition of the index option bits is in src/include/catalog/pg_index.h:

/*
 * Index AMs that support ordered scans must support these two indoption
 * bits.  Otherwise, the content of the per-column indoption fields is
 * open for future definition.
 */
#define INDOPTION_DESC                  0x0001  /* values are in reverse order */
#define INDOPTION_NULLS_FIRST   0x0002  /* NULLs are first instead of last */

Because they are not exposed at the SQL level you cannot rely on this not changing. Using this information might cause your app to stop working after a PostgreSQL upgrade. Though the JDBC driver uses them as horse points out, so they're not likely to be changed without a lot of thought.

You can decode the asc/desc bit like this:

CASE WHEN i.indoption[k-1] & 1 = 1 THEN 'DESC' ELSE 'ASC' END AS descasc,

but you must also handle the nulls first/last bit, whose meaning flips based on whether it's an ascending or descending index:

CASE WHEN (i.indoption[k-1] & 2 = 2) THEN 'NULLS FIRST' ELSE 'NULLS LAST' END

but then it gets messy once you start thinking about other index access methods / opclasses, non-orderable indexes (so you can't just inner join and filter), etc. Eventually I landed up at:

SELECT
      t.relname AS tablename,
      i.relname AS indexname, pg_attribute.attname AS colname,
      k AS col_order,
      CASE WHEN NOT amcanorder THEN '' WHEN i.indoption[k-1] & 1 = 1 THEN 'DESC' ELSE 'ASC' END AS descasc,
      CASE WHEN NOT amcanorder THEN '' WHEN (i.indoption[k-1] & 2 = 2) THEN 'NULLS FIRST' ELSE 'NULLS LAST' END AS nulls
    FROM (
      SELECT
        pg_class.relname,
        pg_index.indrelid, pg_index.indclass, pg_index.indoption,
        unnest(pg_index.indkey) AS k
      FROM pg_index
      INNER JOIN pg_class ON pg_index.indexrelid = pg_class.oid
      WHERE pg_index.indrelid = 'blah2'::regclass
    ) i
    INNER JOIN pg_opclass on (pg_opclass.oid = i.indclass[k-1])
    INNER JOIN pg_am ON (pg_opclass.opcmethod = pg_am.oid)
    INNER JOIN pg_class t ON i.indrelid = t.oid
    INNER JOIN pg_attribute ON (pg_attribute.attrelid = i.indrelid AND pg_attribute.attnum = k);

... but haven't comprehensively tested it against GiST, GIN, custom index methods, all index definition variations, etc. It certainly doesn't deal with:

  • unique indexes
  • custom collations
  • partial indexes
  • omitting defaults (ASC, NULLS LAST for ASC, and NULLS FIRST for DESC)

and probably more. Of course you'll want to parameterize the table name filter too.

@a_horsE_with_no_name likely has the right idea: crib the JDBC driver's query and be done with it.

Craig Ringer
  • 307,061
  • 76
  • 688
  • 778
  • Was a new entry proposed in `information_schema` as you have suggested in your answer ? Any way to track and help solving this issue in postgresql itself ? I ran into the same issue as the OP and have been able to find this question/answer after a lot of efforts/googling. – akki Jul 25 '16 at 09:09
  • I don't know if the original person followed up. Try posting on pgsql-general or even pgsql-hackers to suggest the new entry. – Craig Ringer Jul 25 '16 at 14:10
  • What is pgsql-general and pgsql-hackers ? Sorry, I am new to this world. – akki Jul 25 '16 at 14:21
0

Everything you will probably ever need about an index. Works for version >=9.1 & <=9.5.

Credits - RhodiumToad on IRC. :)

SELECT schemaname, tablename, indexname, amname, indisunique, indisprimary,
       array_agg(attname ORDER BY ord) AS columns,
       array_agg(coll ORDER BY ord) AS collations,
       array_agg(opclass ORDER BY ord) AS opclasses,
       array_agg(ordering ORDER BY ord) AS orderings,
       array_agg(expression ORDER BY ord) AS expressions,
       predicate
  FROM (SELECT n.nspname AS schemaname,
               ct.relname AS tablename,
               c.relname AS indexname,
               m.amname,
               s.indisunique, s.indisprimary, s.ord,
               a.attname,
               CASE WHEN con.nspname is not null
                    THEN format('%I.%I',con.nspname,co.collname)
               END AS coll,
               CASE WHEN oc.opcname is not null
                    THEN format('%I.%I',ocn.nspname,oc.opcname)
               END AS opclass,
               CASE WHEN m.amcanorder
                    THEN format('%s NULLS %s',
                           CASE (option & 1) WHEN 1 THEN 'DESC' ELSE 'ASC' END,
                           CASE (option & 2) WHEN 2 THEN 'FIRST' ELSE 'LAST' END)
               END AS ordering,
               pg_get_expr(s.indpred, s.indrelid) AS predicate,
               pg_get_indexdef(s.indexrelid, ord, false) AS expression
          FROM (SELECT *,
                       generate_series(1,array_length(i.indkey,1)) AS ord,
                       unnest(i.indkey) AS key,
                       unnest(i.indcollation) AS coll,
                       unnest(i.indclass) AS class,
                       unnest(i.indoption) AS option
                  FROM pg_index i) s
               JOIN pg_class c ON (c.oid=s.indexrelid)
               JOIN pg_class ct ON (ct.oid=s.indrelid)
               JOIN pg_namespace n ON (n.oid=c.relnamespace)
               JOIN pg_am m ON (m.oid=c.relam)
               LEFT JOIN pg_attribute a ON (a.attrelid=s.indrelid AND a.attnum=s.key)
               LEFT JOIN pg_collation co ON (co.oid=s.coll)
               LEFT JOIN pg_namespace con ON (con.oid=co.collnamespace)
               LEFT JOIN pg_opclass oc ON (oc.oid=s.class)
               LEFT JOIN pg_namespace ocn ON (ocn.oid=oc.opcnamespace)
       ) s2
 WHERE tablename = 'your_table_name'
 GROUP BY schemaname, tablename, indexname, amname, indisunique, indisprimary, predicate;

Output:

schemaname |         tablename          |            indexname            | amname | indisunique | indisprimary | columns |                     collations                      |                 opclasses                 |               orderings               | expressions | predicate 
------------+----------------------------+---------------------------------+--------+-------------+--------------+---------+-----------------------------------------------------+-------------------------------------------+---------------------------------------+-------------+-----------
 public     | ticket26180_indexes_spamin | test26180_indexes_spamin_a_hsh  | hash   | f           | f            | {a}     | {"pg_catalog.\"default\""}                          | {pg_catalog.text_ops}                     | {NULL}                                | {a}         | 
 public     | ticket26180_indexes_spamin | test26180_indexes_spamin_atpata | btree  | f           | f            | {a,b}   | {"pg_catalog.\"default\"","pg_catalog.\"default\""} | {pg_catalog.text_ops,pg_catalog.text_ops} | {"DESC NULLS FIRST","ASC NULLS LAST"} | {a,b}       | 
 public     | ticket26180_indexes_spamin | test26180_indexes_spamin_b_hsh  | hash   | f           | f            | {b}     | {"pg_catalog.\"default\""}                          | {pg_catalog.text_ops}                     | {NULL}                                | {b}         | 
 public     | ticket26180_indexes_spamin | ticket26180_a_6fe9a5_idx        | btree  | f           | f            | {a,b}   | {"pg_catalog.\"default\"","pg_catalog.\"default\""} | {pg_catalog.text_ops,pg_catalog.text_ops} | {"ASC NULLS LAST","ASC NULLS LAST"}   | {a,b}       | 
 public     | ticket26180_indexes_spamin | ticket26180_indexes_spamin_pkey | btree  | t           | t            | {id}    | {NULL}                                              | {pg_catalog.int4_ops}                     | {"ASC NULLS LAST"}                    | {id}        | 

Thought of posting it here so that anybody needing it doesn't have to put so much hard work searching for it as I had to.

akki
  • 2,021
  • 1
  • 24
  • 35
  • 1
    This is not working anymore: pg_am.amcanorder does not existing in PostgreSQL 9.6 database. This error code I got: ERROR: column m.amcanorder does not exist LINE 20: CASE WHEN m.amcanorder ^ – Jettero Mar 31 '18 at 20:49
  • @Jettero Do you know the alternative PostgreSQL has added for pg_am.amcanorder. Or maybe the fact that this doesn't work for >=9.6 can be updated in this answer. – akki Apr 05 '18 at 16:48
  • I've got a working solution for listing the parameters of INDEX on other thread. That is listing the columns with ASC; DESC; NULLS FIRST; NULLS LAST. I can emphasize that this solution with the using of pg_am.amcanorder is not working from 9.6 ! – Jettero Apr 06 '18 at 20:28
  • Lukas Eder's solution I'm using since he updated his to show these parameters. Link: https://stackoverflow.com/questions/6777456/list-all-index-names-column-names-and-its-table-name-of-a-postgresql-database/44460269?noredirect=1#comment86226090_44460269 – Jettero Apr 06 '18 at 20:33