Thinking on this for a moment, perhaps better than `pg_get_indexdef` is pulling column ordinals out of the `indexprs` column noted in the first answer, and specifically the `varattno` field. `regexp_matches` over the 'indexprs' (converting `pg_tree_node` to `text`) for `varattno`) I threw together the following, going back to the definition in the `columns` table of `information_schema`: ``` SELECT (SELECT JSON_AGG(columns.column_name) FROM information_schema.columns WHERE table_schema = 'public' AND table_name = i.indrelid::regclass::text AND ordinal_position IN (SELECT matches[1]::INTEGER FROM regexp_matches(i.indexprs::TEXT, 'varattno (\d)', 'g') as matches)) AS argument_columns , pg_get_indexdef(att.attrelid, att.attnum, true) , i.indrelid::regclass::text AS table , c.relname AS index_nameq , att.attname as column_names FROM pg_catalog.pg_namespace n JOIN pg_catalog.pg_class c ON c.relnamespace = n.oid JOIN pg_catalog.pg_attribute att ON att.attrelid = c.oid JOIN pg_catalog.pg_index i ON i.indexrelid = c.oid WHERE n.nspname !~ '^pg_' AND c.relkind IN ('r', 't', 'i') ``` and that produces: | argument\_columns |pg\_get\_indexdef |table |index\_nameq |column\_names | | --- | --- | --- | --- | --- | | *NULL* |content\_tsv |file\_lookup\_16k |fl\_16k\_tsv\_idx |content\_tsv | | *NULL* |content\_tsv |file\_lookup\_4k |fl\_4k\_tsv\_idx |content\_tsv | | *NULL* |file\_id |file\_lookup\_4k |date\_index |file\_id | | \["file\_id"\] |abs\(file\_id\) |file\_lookup\_4k |date2\_index |abs | | \["sequence\_no"\] |round\(sequence\_no::double precision\) |file\_lookup\_4k |date3\_index |round | | \["sequence\_no"\] |round\(sequence\_no::double precision\) |file\_lookup\_4k |date4\_index |round | | \["content"\] |"substring"\(content, 1, 5\) |file\_lookup\_4k |date5\_index |substring | | \["file\_id", "sequence\_no"\] |abs\(file\_id \- sequence\_no\) |file\_lookup\_4k |date6\_index |abs | | *NULL* |content\_tsv |file\_lookup\_8k |fl\_8k\_tsv\_idx |content\_tsv | I feel that this is far more in line with what we would both want. Note that: - I joined only to the `attname` table, because that is really all I need - `JSON_AGG(columns.column_name)` can be replaced with `JSON_AGG(columns.*)` or the like to select the entire row for the columns. - `ANY (ARRAY['r', 't', 'i']` was updated to `IN ANY ('r', 't', 'i')` --------------------------- EDIT: In order to bring this into a single column, Let's `COALESCE`, and whenever the new function returns `NULL`, we will insert an array containing the result of the `pg_get_indexdef`, and prefer `ARRAY_AGG` to `JSON_AGG`. All together: ``` SELECT COALESCE((SELECT ARRAY_AGG(columns.column_name) FROM information_schema.columns WHERE table_schema = 'public' AND table_name = i.indrelid::regclass::text AND ordinal_position IN (SELECT matches[1]::INTEGER FROM regexp_matches(i.indexprs::TEXT, 'varattno (\d)', 'g') as matches)), ARRAY[(pg_get_indexdef(att.attrelid, att.attnum, true))]) AS argument_columns , i.indrelid::regclass::text AS table , c.relname AS index_nameq , att.attname as column_names FROM pg_catalog.pg_namespace n JOIN pg_catalog.pg_class c ON c.relnamespace = n.oid JOIN pg_catalog.pg_attribute att ON att.attrelid = c.oid JOIN pg_catalog.pg_index i ON i.indexrelid = c.oid WHERE n.nspname !~ '^pg_' AND c.relkind IN ('r', 't', 'i'); ``` This produces: | argument\_columns |table |index\_nameq |column\_names | | --- | --- | --- | --- | | \{file\_id\} |file\_lookup\_4k |date\_index |file\_id | | \{file\_id\} |file\_lookup\_4k |date2\_index |abs | | \{sequence\_no\} |file\_lookup\_4k |date3\_index |round | | \{sequence\_no\} |file\_lookup\_4k |date4\_index |round | | \{content\} |file\_lookup\_4k |date5\_index |substring | | \{file\_id,sequence\_no\} |file\_lookup\_4k |date6\_index |abs |