views:

480

answers:

2

I'm trying to introspect a postgres 8.3 database to retrieve details of its foreign keys. Imagine I have the following schema:

CREATE TABLE "a" (
 "id" SERIAL PRIMARY KEY
);

CREATE TABLE "b" (
 "one" integer,
 "two" integer,
 "a_id" integer REFERENCES "a",
 PRIMARY KEY ("one", "two")
);

CREATE TABLE "c" (
 "id" SERIAL PRIMARY KEY,
 "a_id" integer REFERENCES "a",
 "b_one" integer,
 "b_two" integer,
 FOREIGN KEY ("b_one", "b_two") REFERENCES "b"
);

Then I'd like to run a query which producued the following:

table | columns        | foreign table | foreign columns
--------------------------------------------------------
 b    | {a_id}         | a             | {id}
 c    | {a_id}         | a             | {id}
 c    | {b_one, b_two} | b             | {one, two}

My first efforts have given me the query

SELECT conrelid::regclass as "table",
       conkey as columns, 
       confrelid::regclass as "foreign table", 
       confkey as "foreign columns"
  FROM pg_constraint
 WHERE contype = 'f' ;

 table | columns | foreign table | foreign columns 
-------+---------+---------------+-----------------
 b     | {3}     | a             | {1}
 c     | {2}     | a             | {1}
 c     | {3,4}   | b             | {1,2}

which is almost there. But my efforts to convert the column numbers to column names have not yet provided me with the desired outcome. Googling has given me the below which is again, not quite right.

SELECT conrelid::regclass as "table",
       a.attname as columns,
       confrelid::regclass as "foreign table",
       af.attname as "foreign columns"
  FROM pg_attribute AS af,
       pg_attribute AS a,
       ( SELECT conrelid,
                confrelid,
                conkey[i] AS conkey,
                confkey[i] as confkey
           FROM ( SELECT conrelid,
                         confrelid, 
                         conkey, 
                         confkey, 
                         generate_series(1, array_upper(conkey, 1)) AS i
                    FROM pg_constraint
     WHERE contype = 'f'
         ) AS ss
       ) AS ss2
 WHERE af.attnum = confkey
   AND af.attrelid = confrelid
   AND a.attnum = conkey
   AND a.attrelid = conrelid ;

 table | columns | foreign table | foreign columns 
-------+---------+---------------+-----------------
 b     | a_id    | a             | id
 c     | a_id    | a             | id
 c     | b_one   | b             | one
 c     | b_two   | b             | two

Can anyone help me take that final step?

+1  A: 
SELECT table, array_agg(columns), foreign_table, array_agg(foreign_columns) FROM (your query here) GROUP BY table, foreign_table;

array_agg requires PostgreSQL 8.4. For earlier versions you can define your own (look for array_accum in the documentation). Obviously, you can merge this query into your big query, but this should give you the general idea.

Peter Eisentraut
+1  A: 

Fleshing out Peter Eisentraut's answer; for postgresql 8.3 the array_agg function can be defined as

CREATE AGGREGATE array_accum (anyelement)
(
    sfunc = array_append,
    stype = anyarray,
    initcond = '{}'
);

and then the full query to get my desired answer becomes

SELECT "table",
       array_accum(columns) AS columns,
       "foreign table",
       array_accum("foreign columns") AS "foreign columns"
  FROM ( SELECT conrelid::regclass AS "table",
                a.attname as columns,
                confrelid::regclass as "foreign table",
                af.attname as "foreign columns"
           FROM pg_attribute AS af,
                pg_attribute AS a,
                ( SELECT conrelid,
                         confrelid,
                         conkey[i] AS conkey,
                         confkey[i] as confkey
                    FROM ( SELECT conrelid,
                                  confrelid, 
                                  conkey, 
                                  confkey, 
                                  generate_series(1, array_upper(conkey, 1)) AS i
                             FROM pg_constraint
              WHERE contype = 'f'
                  ) AS ss
                ) AS ss2
          WHERE af.attnum = confkey
            AND af.attrelid = confrelid
            AND a.attnum = conkey
            AND a.attrelid = conrelid
       ) AS ss3
  GROUP BY "table",
           "foreign table";

Forgive the non-standard way of commenting on his answer, I'm still learning how to use Stackoverflow, and not having created an account in the first instance hasn't helped things.

Ben Armston