79

Given a table name, how do I extract a list of primary key columns and their datatypes from a plpgsql function?

jsight
  • 27,819
  • 25
  • 107
  • 140

10 Answers10

52

The query above is very bad as it is really slow.

I would recommend this official version:

http://wiki.postgresql.org/wiki/Retrieve_primary_key_columns

if schema is needed the query is as follows

SELECT               
  pg_attribute.attname, 
  format_type(pg_attribute.atttypid, pg_attribute.atttypmod) 
FROM pg_index, pg_class, pg_attribute, pg_namespace 
WHERE 
  pg_class.oid = 'foo'::regclass AND 
  indrelid = pg_class.oid AND 
  nspname = 'public' AND 
  pg_class.relnamespace = pg_namespace.oid AND 
  pg_attribute.attrelid = pg_class.oid AND 
  pg_attribute.attnum = any(pg_index.indkey)
 AND indisprimary
user3094383
  • 544
  • 5
  • 3
  • I'm pretty sure you don't need to join with pg_namespace. The oid of the pg_class is unique so when you cast it to a regclass you are already implying the schema name. If you need the schema name just include it in the table name: 'public.foo'::regclass. – Doctor Eval Oct 03 '17 at 10:40
  • 24
    "The query above is very bad as it is really slow." Please avoid referring to the relative position on the page of other answers as this changes over time and by the selected sort method. Instead the most future-proof way of referring to another answer is to link to it. – faintsignal Jan 22 '18 at 19:34
  • So you know I tested this on postgres 9.6 and it does not work if the schema is not public – Daniel L. VanDenBosch Nov 20 '20 at 20:10
  • To clarify, foo is schema.table or to avoid repeating it I used ```pg_class.oid = (nspname || '.' || 'table-name')::regclass AND ``` – Federico Jun 22 '21 at 02:21
  • Thank you for this, but please use joins :). `SELECT nspname, relname, a.attname FROM pg_index i JOIN pg_class c ON c.oid = i.indrelid JOIN pg_attribute a ON a.attrelid = c.oid AND a.attnum = any(i.indkey) JOIN pg_namespace n ON n.oid = c.relnamespace WHERE relname = 'foo' AND nspname = 'bar' AND indisprimary;` – DB140141 Feb 25 '22 at 20:45
52

To provide a straight bit of SQL, you can list the primary key columns and their types with:

SELECT c.column_name, c.data_type
FROM information_schema.table_constraints tc 
JOIN information_schema.constraint_column_usage AS ccu USING (constraint_schema, constraint_name) 
JOIN information_schema.columns AS c ON c.table_schema = tc.constraint_schema
  AND tc.table_name = c.table_name AND ccu.column_name = c.column_name
WHERE constraint_type = 'PRIMARY KEY' and tc.table_name = 'mytable';
Dale K
  • 25,246
  • 15
  • 42
  • 71
Jamie Love
  • 5,418
  • 6
  • 30
  • 33
  • 6
    When I run this on my postgres server, I get an empty result set. Before you ask, I did replace `'mytable'` with the correct table name. Do I need to be in a specific context or scope for this to work? – 2mac Jul 22 '15 at 12:27
  • 4
    I know this is an old comment, but I wanted to point out that a lack of permissions to the table can cause an empty result. If the user you are connected as doesn’t have select permission any queries about the meta data for that table will return empty. Hope that helps someone. – Jon Jan 23 '20 at 16:51
41
\d tablename 

will give you the primary key info along with other table related information such as all columns, their types, associated indexes, constraints, rules, triggers etc. You probably don't need all that information, but it is the fastest way to get all details at a glance, see more details here.

It returns something like this:

    Table "public.tablename"
     Column |  Type   | Modifiers 
    --------+---------+-----------
     col1   | text    | not null
     col2   | numeric | 
     col3   | text    | 
     col4   | text    | 
     col5   | numeric | 
    Indexes:
        "tablename_pkey" PRIMARY KEY, btree (col1)
Kate Orlova
  • 3,225
  • 5
  • 11
  • 35
Konrad
  • 631
  • 6
  • 6
33

The following SQL statement works for me:

SELECT a.attname
FROM   pg_index i
JOIN   pg_attribute a ON a.attrelid = i.indrelid
                     AND a.attnum = ANY(i.indkey)
WHERE  i.indrelid = 'tablename'::regclass
AND    i.indisprimary;

It is directly taken from here.

cmaher
  • 5,100
  • 1
  • 22
  • 34
BullyWiiPlaza
  • 17,329
  • 10
  • 113
  • 185
  • 1
    This will not extract the data type. – Felipe Zavan Jun 21 '20 at 15:45
  • 3
    Perfect answer just for name of primary key. – Rushabh Sudame Jan 05 '21 at 18:04
  • Thanks for this. It was almost perfect, but didn't work for one of my tables. If the table has one of more columns defined as UNIQUE, that won't guarantee an index being created unless it is a formal constraint. Removing the 'i.indisprimary' will ensure that you can use uniqueness constraints without an accompanying index. – Jon M Mar 07 '21 at 14:27
  • This is the modified statement. Note the addition of i.indisprimary as well. SELECT a.attname, format_type(a.atttypid, a.atttypmod) AS data_type, i.indisunique FROM pg_index i JOIN pg_attribute a ON a.attrelid = i.indrelid AND a.attnum = ANY(i.indkey) AND i.indisunique = true WHERE i.indrelid = 'tablename'::regclass; – Jon M Mar 07 '21 at 16:05
7

You really only need 2 system tables for that:

  • pg_constraint - tells you which columns (by number) belong to the primary key
  • pg_attribute - translates the column numbers to column names

Note: the system tables might change between PostgreSQL versions, but it doesn't happen often (actually very rarely if at all). And unlike using the information_schema.table_constraints, you don't need any special permissions, just select on the table. (This was tested in Postgres 10.6)

SELECT string_agg(a.attname, ', ') AS pk
FROM
    pg_constraint AS c
    CROSS JOIN LATERAL UNNEST(c.conkey) AS cols(colnum) -- conkey is a list of the columns of the constraint; so we split it into rows so that we can join all column numbers onto their names in pg_attribute
    INNER JOIN pg_attribute AS a ON a.attrelid = c.conrelid AND cols.colnum = a.attnum
WHERE
    c.contype = 'p' -- p = primary key constraint
    AND c.conrelid = '<schemaname>.<tablename>'::REGCLASS; -- regclass will type the name of the object to its internal oid
Joe Samanek
  • 1,644
  • 12
  • 16
  • Just tested this on a small table and it took more than 15 sec to resolve. Not very optimal. – Kristian Barrett Sep 14 '22 at 08:41
  • this is the best answer imho. We can't rely on pg_index if pk was only created as a constraint without an index. it's also much more terse than other queries here, and doesn't require special permissions. – Jeremy Giaco Apr 21 '23 at 09:59
4

Take a look at pg_constraint system table. Or information_schema.table_constraints view if you prefer to stick close to the SQL standard.

For a complete example connect to a DB using psql with the "-E" option and type \d <some_table> - you'll see the actual queries used in describing a table.

Milen A. Radev
  • 60,241
  • 22
  • 105
  • 110
  • Additionally, combine this with the data from pg_indexes, and you should be pretty good. Really a primary key is just a unique index with not null on all of the fields. – Grant Johnson Aug 27 '09 at 20:16
3
SELECT a.attname AS name, format_type(a.atttypid, a.atttypmod) AS type
FROM
    pg_class AS c
    JOIN pg_index AS i ON c.oid = i.indrelid AND i.indisprimary
    JOIN pg_attribute AS a ON c.oid = a.attrelid AND a.attnum = ANY(i.indkey)
WHERE c.oid = 'example'::regclass

Output:

 name |  type  
------+--------
 id   | bigint
Paul Draper
  • 78,542
  • 46
  • 206
  • 285
2

Beware of indexes where the column order differs from the table's column order. (i.e. if the primary key used columns 3, 2, and 1)

The following query is much more complex, but returns the columns in the proper order. (Remove the 'indisprimary' clause to get the same info for all indexes on a table)

WITH ndx_list AS
(
    SELECT pg_index.indexrelid
      FROM pg_index, pg_class
     WHERE pg_class.relname = 'test_indices_table'
       AND pg_class.oid = pg_index.indrelid
       AND pg_index.indisprimary
), ndx_cols AS
(
   SELECT pg_class.relname AS index_name, UNNEST(i.indkey) AS col_ndx, i.indisunique, i.indisprimary
     FROM pg_class, pg_index i
    WHERE pg_class.oid = i.indexrelid
      AND pg_class.oid IN (SELECT indexrelid FROM ndx_list)
)
  SELECT ndx_cols.index_name, ndx_cols.indisunique, ndx_cols.indisprimary,
         a.attname, format_type(a.atttypid, a.atttypmod), a.attnum
    FROM pg_class c, pg_attribute a
    JOIN ndx_cols ON (a.attnum = ndx_cols.col_ndx)
   WHERE c.oid = 'test_indices_table'::regclass
     AND a.attrelid = c.oid
jamesvl
  • 1,649
  • 12
  • 8
2

Preserving column order using generate_subscripts (based on @Paul Draper's answer):

SELECT
  a.attname,
  format_type(a.atttypid, a.atttypmod) 
FROM
  pg_attribute a
  JOIN (SELECT *, GENERATE_SUBSCRIPTS(indkey, 1) AS indkey_subscript FROM pg_index) AS i
    ON
      i.indisprimary
      AND i.indrelid = a.attrelid
      AND a.attnum = i.indkey[i.indkey_subscript]
WHERE
  a.attrelid = 'your_table'::regclass
ORDER BY
  i.indkey_subscript
snipsnipsnip
  • 2,268
  • 2
  • 33
  • 34
1

This will give you the list of constraints along with the definition.

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
   contype IN ( 'f', 'p ' )
   AND conrelid::regclass::TEXT IN ( 'foo' )

ORDER BY
   conrelid::regclass::TEXT,
   contype DESC
snakecharmerb
  • 47,570
  • 11
  • 100
  • 153