Here's a simple query to match up foreign keys to their referenced tables/columns:
SELECT
o1.name AS FK_table,
c1.name AS FK_column,
fk.name AS FK_name,
o2.name AS PK_table,
c2.name AS PK_column,
pk.name AS PK_name,
fk.delete_referential_action_desc AS Delete_Action,
fk.update_referential_action_desc AS Update_Action
FROM sys.objects o1
INNER JOIN sys.foreign_keys fk
ON o1.object_id = fk.parent_object_id
INNER JOIN sys.foreign_key_columns fkc
ON fk.object_id = fkc.constraint_object_id
INNER JOIN sys.columns c1
ON fkc.parent_object_id = c1.object_id
AND fkc.parent_column_id = c1.column_id
INNER JOIN sys.columns c2
ON fkc.referenced_object_id = c2.object_id
AND fkc.referenced_column_id = c2.column_id
INNER JOIN sys.objects o2
ON fk.referenced_object_id = o2.object_id
INNER JOIN sys.key_constraints pk
ON fk.referenced_object_id = pk.parent_object_id
AND fk.key_index_id = pk.unique_index_id
ORDER BY o1.name, o2.name, fkc.constraint_column_id
The output has eight columns: the table and column names for the foreign keys (FK_table, FK_column), the names of the foreign-key constraints (FK_name), the referenced PK or unique index table and column names (PK_table, PK_column), the name of the referenced PK or unique index (PK_name), and the update/delete cascade actions (Delete_Action, Update_Action).
(Edited to add some more output columns.)
EDIT: I'm back 6 years later with an improved version of this. I realized that the original query doesn't really handle multi-column foreign keys well, and I also wanted to be able to quickly identify disabled, untrusted, or unindexed foreign keys. So here's the new version that corrects all of that.
Multi-column keys are shown as comma-separated lists in FK_columns
and PK_columns
, using the traditional FOR XML
/STUFF
abuse. The FK_indexes
column shows the names of any indexes on the foreign-key table that could potentially be used to satisfy seeks using the foreign-key columns (mainly for optimizing deletes or updates to the primary key table). If it's NULL
, then you've got an unindexed foreign key. You can tweak the ORDER BY
, or add a WHERE
clause (commented out below) if you want to sort by the PK table name, filter for specific PK/FK tables, etc.
SELECT
fk.is_disabled,
fk.is_not_trusted,
OBJECT_SCHEMA_NAME(o1.object_id) AS FK_schema,
o1.name AS FK_table,
--Generate list of columns in referring side of foreign key
STUFF(
(
SELECT ', ' + c1.name AS [text()]
FROM sys.columns c1 INNER
JOIN sys.foreign_key_columns fkc
ON c1.object_id = fkc.parent_object_id
AND c1.column_id = fkc.parent_column_id
WHERE fkc.constraint_object_id = fk.object_id
FOR XML PATH('')
), 1, 2, '') AS FK_columns,
--Look for any indexes that will fully satisfy the foreign key columns
STUFF(
(
SELECT ', ' + i.name AS [text()]
FROM sys.indexes i
WHERE i.object_id = o1.object_id
AND NOT EXISTS ( --Find foreign key columns that don't match the index key columns
SELECT fkc.constraint_column_id, fkc.parent_column_id
FROM sys.foreign_key_columns fkc
WHERE fkc.constraint_object_id = fk.object_id
EXCEPT
SELECT ic.key_ordinal, ic.column_id
FROM sys.index_columns ic
WHERE ic.object_id = i.object_id AND ic.index_id = i.index_id
)
FOR XML PATH('')
), 1, 2, '') AS FK_indexes,
fk.name AS FK_name,
OBJECT_SCHEMA_NAME(o2.object_id) AS PK_schema,
o2.name AS PK_table,
--Generate list of columns in referenced (i.e. PK) side of foreign key
STUFF(
(
SELECT ', ' + c2.name AS [text()]
FROM sys.columns c2
INNER JOIN sys.foreign_key_columns fkc
ON c2.object_id = fkc.referenced_object_id
AND c2.column_id = fkc.referenced_column_id
WHERE fkc.constraint_object_id = fk.object_id
FOR XML PATH('')
), 1, 2, '') AS PK_columns,
pk.name AS PK_name,
fk.delete_referential_action_desc AS Delete_Action,
fk.update_referential_action_desc AS Update_Action
FROM sys.objects o1
INNER JOIN sys.foreign_keys fk
ON o1.object_id = fk.parent_object_id
INNER JOIN sys.objects o2
ON fk.referenced_object_id = o2.object_id
INNER JOIN sys.key_constraints pk
ON fk.referenced_object_id = pk.parent_object_id
AND fk.key_index_id = pk.unique_index_id
--WHERE o2.name = 'Company_Address'
ORDER BY o1.name, o2.name
So you look up other tables in a CHECK
constraint.
CHECK
constraints are supposed to run IMMUTABLE
checks. What passes OK for a row at one time should pass OK at any time. That's how CHECK
constraints are defined in the SQL standard. That's also the reason for this restriction in the manual:
Currently, CHECK
expressions cannot contain subqueries nor refer to
variables other than columns of the current row.
Still, expressions in CHECK
constraints are allowed to use functions, even user-defined functions. Those should be IMMUTABLE
, but Postgres does not currently enforce this. According to this related discussion on pgsql-hackers, one reason is to allow references to the current time, which is not IMMUTABLE
by nature.
But you are looking up rows of another table, which is completely in violation of how CHECK
constraints are supposed to work. I am not surprised that pg_dump
fails to provide for this.
Move your check in another table to a trigger (which is the right tool), and it should work with modern versions of Postgres.
PostgreSQL 9.2 or later
While the above is true for any version of Postgres, several tools have been introduced with Postgres 9.2 to help with your situation:
pg_dump option --exclude-table-data
A simple solution would be to dump the db without data for the violating table with:
--exclude-table-data=my_schema.my_tbl
Then append just the data for this table at the end of the dump with:
--data-only --table=my_schema.my_tbl
But complications with other constraints on the same table might ensue. There is an even better solution:
Solution: NOT VALID
Up to Postgres 9.1, the NOT VALID
modifier was only available for FK constraints. This was extended to CHECK
constraints in Postgres 9.2. The manual:
If the constraint is marked NOT VALID
, the potentially-lengthy
initial check to verify that all rows in the table satisfy the
constraint is skipped. The constraint will still be enforced against
subsequent inserts or updates [...]
A plain Postgres dump file consists of three "sections":
Postgres 9.2 also introduced an option to dump sections separately with -- section=sectionname
, but that's not helping with the problem at hand.
Here is where it gets interesting. The manual:
Post-data items include definitions of indexes, triggers, rules, and
constraints other than validated check constraints. Pre-data items
include all other data definition items.
Bold emphasis mine.
You can change the offending CHECK
constraint to NOT VALID
, which moves the constraint to the post-data
section. Drop and recreate:
ALTER TABLE a
DROP CONSTRAINT a_constr_1
, ADD CONSTRAINT a_constr_1 CHECK (fail_if_b_empty()) NOT VALID;
A single statement is fastest and rules out race conditions with concurrent transactions. (Two commands in a single transaction would work, too.)
This should solve your problem. You can even leave the constraint in that state, since that better reflects what it actually does: check new rows, but give no guarantees for existing data. There is nothing wrong with a NOT VALID
check constraint. If you prefer, you can validate it later:
ALTER TABLE a VALIDATE CONSTRAINT a_constr_1;
But then you are back to the status quo ante.
Best Answer
Does the following query work for you?