How do I get constraint details from the name in Informix? - database

When programming a large transaction (lots of inserts, deletes, updates) and thereby violating a constraint in Informix (v10, but should apply to other versions too) I get a not very helpful message saying, for example, I violated constraint r190_710. How can I find out which table(s) and key(s) are covered by a certain constraint I know only the name of?

Tony Andrews suggested (pointing to a different end-point for the URL):
From Informix Guide to SQL: Reference it appears you should look at the system catalog tables SYSCONSTRAINTS and SYSINDICES.
The Informix system catalog is described in that manual.
The SysConstraints table is the starting point for analyzing a constraint, most certainly; you find the constraint name in that table, and from there you can find out the other details.
However, you also have to look at other tables, and not just (or even directly) SysIndices.
For example, I have a lot of NOT NULL constraints on the tables in my database. For those, the constraint type is 'N' and there is no need to look elsewhere for more information.
A constraint type of 'P' indicates a primary key; that would need more analysis via the SysIndexes view or SysIndices table. Similarly, a constraint type of 'U' indicates a unique constraint and needs extra information from the SysIndexes view or SysIndices table.
A constraint type of 'C' indicates a check constraint; the text (and binary compiled form) of the constraint is found in the SysChecks table (with types 'T' and 'B' for the data; the data is more or less encoded with Base-64, though without the '=' padding at the end and using different characters for 62 and 63).
Finally, a constraint type of 'R' indicates a referential integrity constraint. You use the SysReferences table to find out which table is referenced, and you use SysIndexes or SysIndices to establish which indexes on the referencing and referenced tables are used, and from that you can discover the relevant columns. This can get quite hairy!

Columns in a table with a constraint on them
SELECT
a.tabname, b.constrname, d.colname
FROM
systables a, sysconstraints b, sysindexes c, syscolumns d
WHERE
a.tabname = 'your_table_name_here'
AND
b.tabid = a.tabid
AND
c.idxname = b.idxname
AND
d.tabid = a.tabid
AND
(
d.colno = c.part1 or
d.colno = c.part2 or
d.colno = c.part3 or
d.colno = c.part4 or
d.colno = c.part5 or
d.colno = c.part6 or
d.colno = c.part7 or
d.colno = c.part8 or
d.colno = c.part9 or
d.colno = c.part10 or
d.colno = c.part11 or
d.colno = c.part12 or
d.colno = c.part13 or
d.colno = c.part14 or
d.colno = c.part15 or
d.colno = c.part16
)
ORDER BY
a.tabname,
b.constrname,
d.colname

I have been using the following query to get more information about the different types of constraints.
It's based on some spelunking in the system tables and several explanations about the system catalog.
sysconstraints.constrtype indicates the type of the constraint:
P = Primary key
U = Unique key / Alternate key
N = Not null
C = Check
R = Reference / Foreign key
select
tab.tabname,
constr.*,
chk.*,
c1.colname col1,
c2.colname col2,
c3.colname col3,
c4.colname col4,
c5.colname col5
from sysconstraints constr
join systables tab on tab.tabid = constr.tabid
left outer join syschecks chk on chk.constrid = constr.constrid and chk.type = 'T'
left outer join sysindexes i on i.idxname = constr.idxname
left outer join syscolumns c1 on c1.tabid = tab.tabid and c1.colno = abs(i.part1)
left outer join syscolumns c2 on c2.tabid = tab.tabid and c2.colno = abs(i.part2)
left outer join syscolumns c3 on c3.tabid = tab.tabid and c3.colno = abs(i.part3)
left outer join syscolumns c4 on c4.tabid = tab.tabid and c4.colno = abs(i.part4)
left outer join syscolumns c5 on c5.tabid = tab.tabid and c5.colno = abs(i.part5)
where constr.constrname = 'your constraint name'

to get the table affected by the constraint "r190_710":
select TABNAME from SYSTABLES where TABID IN
(select TABID from sysconstraints where CONSTRID IN
(select CONSTRID from sysreferences where PTABID IN
(select TABID from sysconstraints where CONSTRNAME= "r190_710" )
)
);

From Informix Guide to SQL: Reference it appears you should look at the system catalog tables SYSCONSTRAINTS and SYSINDICES.

From surfing at www.iiug.org (International Informix Users Group) i found the not-so-easy solution.
(1) Get referential constraint data from the constraint name (you can get all constraints for a table by replacing "AND sc.constrname = ?" by "AND st.tabname MATCHES ?"). This statement selects some more fields than necessary here because they might be interesting in other situations.
SELECT si.part1, si.part2, si.part3, si.part4, si.part5,
si.part6, si.part7, si.part8, si.part9, si.part10,
si.part11, si.part12, si.part13, si.part14, si.part15, si.part16,
st.tabname, rt.tabname as reftable, sr.primary as primconstr,
sr.delrule, sc.constrid, sc.constrname, sc.constrtype,
si.idxname, si.tabid as tabid, rc.tabid as rtabid
FROM 'informix'.systables st, 'informix'.sysconstraints sc,
'informix'.sysindexes si, 'informix'.sysreferences sr,
'informix'.systables rt, 'informix'.sysconstraints rc
WHERE st.tabid = sc.tabid
AND st.tabtype != 'Q'
AND st.tabname NOT MATCHES 'cdr_deltab_[0-9][0-9][0-9][0-9][0-9][0-9]*'
AND rt.tabid = sr.ptabid
AND rc.tabid = sr.ptabid
AND sc.constrid = sr.constrid
AND sc.tabid = si.tabid
AND sc.idxname = si.idxname
AND sc.constrtype = 'R'
AND sc.constrname = ?
AND sr.primary = rc.constrid
ORDER BY si.tabid, sc.constrname
(2) Use the part1-part16 to determine which column is affected by the constraint: the part[n] containing a value different from 0 contains the column number of the used column. Use (3) to find the name of the column.
If constrtype is 'R' (referencing) use the following statement to find the parts of the referencing table:
SELECT part1, part2, part3, part4, part5, part6, part7, part8,
part9, part10, part11, part12, part13, part14, part15, part16
FROM 'informix'.sysindexes si, 'informix'.sysconstraints sc
WHERE si.tabid = sc.tabid
AND si.idxname = sc.idxname
AND sc.constrid = ? -- primconstr from (1)
(3) the tabid and rtabid (for referencing constraints) from (1) can now be used to get the columns of the tables like that:
SELECT colno, colname
FROM 'informix'.syscolumns
WHERE tabid = ? -- tabid(for referenced) or rtabid(for referencing) from (1)
AND colno = ? -- via parts from (1) and (2)
ORDER BY colno
(4) If the constrtype is 'C', then get the check information like this:
SELECT type, seqno, checktext
FROM 'informix'.syschecks
WHERE constrid = ? -- constrid from (1)
Quite hairy indeed

If your constraint is named constraint_c6, here's how to dump its definition (well sort-of, you still need to concatenate the rows, as they'll be separated by whitespace):
OUTPUT TO '/tmp/constraint_c6.sql' WITHOUT HEADINGS
SELECT ch.checktext
FROM syschecks ch, sysconstraints co
WHERE ch.constrid = co.constrid
AND ch.type = 'T' -- text lines only
AND co.constrname = 'constraint_c6'
ORDER BY ch.seqno;

Related

get columns other than primary key from CHANGETABLE function sql-server

I have two table (InvoiceRequests, InvoiceRequestsLineItems), InvoiceRequestsLineItems has InvoiceRequestId as forign key, Qauntity and Rate columns.
My both tables has change tracking enabled.
I'm writing a query that returns the Only the InvoiceRequests that has been changed with there respective Amount(calculated from Qauntity and Rate columns of InvoiceRequestsLineItems).
the scenario is when I update/insert into LineItems I Join the LineItems table and ChangeTable to get the InvoiceRequestId. So I shows customers that 'InvoiceRequest' Amount changed.
But when I delete any LineItems I lost the InvoiceRequestId(foreign key) from that row. Then I cant able to tell the amount has been changed for perticular InvoiceRequests.
Is there any way that ChangeTable function can return other columns(InvoiceRequestId) apart from PrimaryKey(InvoiceRequestLineItemId).
Solution like adding trigger and keep the deleted record in separate table will increase lot of overhead.
Please provide any suggestion, that I can do with minimal changes. Thanks
SELECT
CTIRL.INVOICEREQUESTSID [InvoiceRequestId],
IR.STATUS [Status],
ERIL.[InvoiceRequestAmount],
CAST(0 as BIT) [Deleted]
FROM
(SELECT [IRS].INVOICEREQUESTSID, CTL.SYS_CHANGE_COLUMNS, CTL.sys_change_operation
FROM INVOICEREQUESTLINEITEMS [IRS]
JOIN CHANGETABLE( CHANGES dbo.INVOICEREQUESTLINEITEMS, #ctversion) CTL
ON CTL.INVOICEREQUESTLINEITEMSID = [IRS].INVOICEREQUESTLINEITEMSID
) AS CTIRL
LEFT JOIN dbo.INVOICEREQUESTS IR
ON CTIRL.INVOICEREQUESTSID = IR.INVOICEREQUESTSID
LEFT JOIN (
SELECT
IRLI.INVOICEREQUESTSID,
SUM(
IRLI.QUANTITY * IRLI.RATE + COALESCE(IRLI.TAXAMOUNT, 0)
) [InvoiceRequestAmount]
FROM
INVOICEREQUESTLINEITEMS IRLI
GROUP BY
IRLI.INVOICEREQUESTSID
) AS ERIL
ON ERIL.INVOICEREQUESTSID = RES.InvoiceRequestId
WHERE
(
CHANGE_TRACKING_IS_COLUMN_IN_MASK(5, CTIRL.SYS_CHANGE_COLUMNS) = 1
OR CHANGE_TRACKING_IS_COLUMN_IN_MASK(7, CTIRL.SYS_CHANGE_COLUMNS) = 1
OR CHANGE_TRACKING_IS_COLUMN_IN_MASK(11, CTIRL.SYS_CHANGE_COLUMNS) = 1
OR CTIRL.sys_change_operation = 'D'
OR CTIRL.sys_change_operation = 'I'
)

Why joining same table twice returns different column values?

I have a stored procedure that was not written by me.
I cannot understand how they are joining the same table tblUsers twice and it returns different column values:
select distinct
usr.Name_FirstLast AS AssignedTo,
usr1.Name_FirstLast as AssignedBy
from
dbo.tblNoteStore nt_str
join
dbo.tblNoteEntities entit ON nt_str.ID = entit.NoteGUID
join
dbo.tblNoteDiaries nt_dia ON nt_str.ID = nt_dia.NoteGUID
join
dbo.tblNoteEntries entri on nt_str.ID = entri.NoteGUID
and nt_dia.EntryGUID = entri.ID
and entit.NoteGUID = entri.NoteGUID
left join
dbo.tblNoteRecipients recip ON entri.ID = recip.EntryGUID
--this is where the same table used twice
left join
dbo.tblUsers usr ON recip.UserGUID = usr.UserGUID -- returns AssignedTo column
left join
dbo.tblUsers usr1 ON usr1.UserGuid = entri.UserGUID -- returns AssignedBy column
where
usr.UserGUID = '55610B2F-1997-40C0-9F01-EED3ED2939F9'
The result is what I need, but why it happens that way?
dbo.tblUsers has primary key UserGUID
dbo.tblNoteEntries has a foreign key UserGUID
dbo.tblNoteRecipients has a foreign key UserGUID
Also, do I have to use all those tables in JOIN in order to receive the result?
LEFT JOIN dbo.tblUsers usr ON recip.UserGUID = usr.UserGUID -- > gives me AssignedTo column
LEFT JOIN dbo.tblUsers usr1 ON usr1.UserGuid = entri.UserGUID--gives me AssignedBy column
The UserGUID column of the 2 dbo.tblUsers table references are correlated using different columns (recip.UserGUID and entri.UserGUID) so different rows from dbo.tblUsers may be returned.

Constraints / Foreign keys information in Oracle

I have to get the Table and Column name of the Foreign keys on Oracle, can anybody confirm the following statement?
SELECT a.table_name AS TableWithForeignKey, b.column_name AS ForeignKeyColumn
FROM user_constraints a INNER JOIN user_cons_columns b
ON (a.constraint_name = b.constraint_name) AND (a.table_name = b.table_name)
and a.constraint_type = 'R'
The Part I'm not sure about is the INNER JOIN Part (after ON):
(a.constraint_name = b.constraint_name) AND (a.table_name = b.table_name)
As I couldn't find something like constraints_ID, is this enought to match 1:1 rows from both tables user_constraints and user_cons_columns
Thank you.
I use the
a.constraint_name = b.constraint_name
for joining the 2 views
so, I think it's OK

How to find foreign-key dependencies pointing to one record in Oracle?

I have a very large Oracle database, with many many tables and millions of rows. I need to delete one of them, but want to make sure that dropping it will not break any other dependent rows that point to it as a foreign key record. Is there a way to get a list of all the other records, or at least table schemas, that point to this row? I know that I could just try to delete it myself, and catch the exception, but I won't be running the script myself and need it to run clean the first time through.
I have the tools SQL Developer from Oracle, and PL/SQL Developer from AllRoundAutomations at my disposal.
Thanks in advance!
Here is my solution to list all references to a table:
select
src_cc.owner as src_owner,
src_cc.table_name as src_table,
src_cc.column_name as src_column,
dest_cc.owner as dest_owner,
dest_cc.table_name as dest_table,
dest_cc.column_name as dest_column,
c.constraint_name
from
all_constraints c
inner join all_cons_columns dest_cc on
c.r_constraint_name = dest_cc.constraint_name
and c.r_owner = dest_cc.owner
inner join all_cons_columns src_cc on
c.constraint_name = src_cc.constraint_name
and c.owner = src_cc.owner
where
c.constraint_type = 'R'
and dest_cc.owner = 'MY_TARGET_SCHEMA'
and dest_cc.table_name = 'MY_TARGET_TABLE'
--and dest_cc.column_name = 'MY_OPTIONNAL_TARGET_COLUMN'
;
With this solution you also have the information of which column of which table is referencing which column of your target table (and you can filter on it).
I always look at the Foreign keys for the starting table and work my way back. The DB tools usually have a dependencies or constraints node. I know PL/SQL Developer has a way to see FK's, but it's been a while since I have used it, so I can't explain it...
just replace XXXXXXXXXXXX with a table name...
/* The following query lists all relationships */
select
a.owner||'.'||a.table_name "Referenced Table"
,b.owner||'.'||b.table_name "Referenced by"
,b.constraint_name "Foreign Key"
from all_constraints a, all_constraints b
where
b.constraint_type = 'R'
and a.constraint_name = b.r_constraint_name
and b.table_name='XXXXXXXXXXXX' -- Table name
order by a.owner||'.'||a.table_name
I had a similar problem recently, but experienced soon, that finding the direct dependencies is not enough. So I wrote a query to show a tree of multilevel foreign key dependencies:
SELECT LPAD(' ',4*(LEVEL-1)) || table1 || ' <-- ' || table2 tables, table2_fkey
FROM
(SELECT a.table_name table1, b.table_name table2, b.constraint_name table2_fkey
FROM user_constraints a, user_constraints b
WHERE a.constraint_type IN('P', 'U')
AND b.constraint_type = 'R'
AND a.constraint_name = b.r_constraint_name
AND a.table_name != b.table_name
AND b.table_name <> 'MYTABLE')
CONNECT BY PRIOR table2 = table1 AND LEVEL <= 5
START WITH table1 = 'MYTABLE';
It gives a result like this, when using SHIPMENT as MYTABLE in my database:
SHIPMENT <-- ADDRESS
SHIPMENT <-- PACKING_LIST
PACKING_LIST <-- PACKING_LIST_DETAILS
PACKING_LIST <-- PACKING_UNIT
PACKING_UNIT <-- PACKING_LIST_ITEM
PACKING_LIST <-- PO_PACKING_LIST
...
We can use the data dictionary to identify the tables which reference the primary key of the table in question. From that we can generate some dynamic SQL to query those tables for the value we want to zap:
SQL> declare
2 n pls_integer;
3 tot pls_integer := 0;
4 begin
5 for lrec in ( select table_name from user_constraints
6 where r_constraint_name = 'T23_PK' )
7 loop
8 execute immediate 'select count(*) from '||lrec.table_name
9 ||' where col2 = :1' into n using &&target_val;
10 if n = 0 then
11 dbms_output.put_line('No impact on '||lrec.table_name);
12 else
13 dbms_output.put_line('Uh oh! '||lrec.table_name||' has '||n||' hits!');
14 end if;
15 tot := tot + n;
16 end loop;
17 if tot = 0
18 then
19 delete from t23 where col2 = &&target_val;
20 dbms_output.put_line('row deleted!');
21 else
22 dbms_output.put_line('delete aborted!');
23 end if;
24 end;
25 /
Enter value for target_val: 6
No impact on T34
Uh oh! T42 has 2 hits!
No impact on T69
delete aborted!
PL/SQL procedure successfully completed.
SQL>
This example cheats a bit. The name of the target primary key is hardcoded, and the referencing column has the same name on all the dependent tables. Fixing these issues is left as an exercise for the reader ;)
Had a similar situation. In my case I had a couple of records which had ended up with the same ID differing only by case. Wanted to check what dependent records exists for each to know which was easiest to delete/update
The following prints out all child records pointing to the given record, per child table with a count for each table/master record combination
declare
--
-- Finds and prints out how many children there are per table and value for each value of a given field
--
-- Name of the table to base the query on
cTable constant varchar2(20) := 'FOO';
-- Name of the column to base the query on
cCol constant varchar2(10) := 'ID';
-- Cursor to find interesting values (e.g. duplicates) in master table
cursor cVals is
select id
from foo f
where exists ( select 1 from foo f2
where upper(f.id) = upper(f2.id)
and f.rowid != f2.rowid );
-- Everything below here should just work
vNum number(18,0);
vSql varchar2(4000);
cOutColSize number(2,0) := 30;
cursor cReferencingTables is
select
consChild.table_name,
consChild.constraint_name,
colChild.column_name
from user_constraints consMast
inner join user_constraints consChild on consMast.constraint_name = consChild.r_constraint_name
inner join USER_CONS_COLUMNS colChild on consChild.CONSTRAINT_NAME = colChild.CONSTRAINT_NAME
inner join USER_CONS_COLUMNS colMast on colMast.CONSTRAINT_NAME = consMast.CONSTRAINT_NAME
where consChild.constraint_type = 'R'
and consMast.table_name = cTable
and colMast.column_name = cCol
order by consMast.table_name, consChild.table_name;
begin
dbms_output.put_line(
rpad('Table', cOutColSize) ||
rpad('Column', cOutColSize) ||
rpad('Value', cOutColSize) ||
rpad('Number', cOutColSize)
);
for rRef in cReferencingTables loop
for rVals in cVals loop
vSql := 'select count(1) from ' || rRef.table_name || ' where ' || rRef.column_name || ' = ''' || rVals.id || '''';
execute immediate vSql into vNum;
if vNum > 0 then
dbms_output.put_line(
rpad(rRef.table_name, cOutColSize) ||
rpad(rRef.column_name, cOutColSize) ||
rpad(rVals.id, cOutColSize) ||
rpad(vNum, cOutColSize) );
end if;
end loop;
end loop;
end;
select c.owner, a.table_name, a.column_name, a.constraint_name,
c.r_owner as ref_owner, cpk.table_name as ref_table,
cpk.constraint_name as ref_pk
from all_cons_columns a
join all_constraints c on a.owner = c.owner
and a.constraint_name = c.constraint_name
join all_constraints cpk on c.r_owner = cpk.owner
and c.r_constraint_name = cpk.constraint_name
where c.constraint_type = 'r' and c.table_name= 'table_name';
I was surprised at how difficult it was to find the dependency order of tables based on foreign key relationships. I needed it because I wanted to delete the data from all tables and import it again. Here is the query I wrote to list the tables in dependency order. I was able to script the deletes using the query below, and import again using the results of the query in reverse order.
SELECT referenced_table
,MAX(lvl) for_deleting
,MIN(lvl) for_inserting
FROM
( -- Hierarchy of dependencies
SELECT LEVEL lvl
,t.table_name referenced_table
,b.table_name referenced_by
FROM user_constraints A
JOIN user_constraints b
ON A.constraint_name = b.r_constraint_name
and b.constraint_type = 'R'
RIGHT JOIN user_tables t
ON t.table_name = A.table_name
START WITH b.table_name IS NULL
CONNECT BY b.table_name = PRIOR t.table_name
)
GROUP BY referenced_table
ORDER BY for_deleting, for_inserting;
Oracle constraints uses Table Indexes to reference data.
To find out what tables are referencing one table, just look for index in reverse order.
/* Toggle ENABLED and DISABLE status for any referencing constraint: */
select 'ALTER TABLE '||b.owner||'.'||b.table_name||' '||
decode(b.status, 'ENABLED', 'DISABLE ', 'ENABLE ')||
'CONSTRAINT '||b.constraint_name||';'
from all_indexes a,
all_constraints b
where a.table_name='XXXXXXXXXXXX' -- Table name
and a.index_name = b.r_constraint_name;
Obs.: Disabling references improves considerably the time of DML commands (update, delete and insert).
This can help a lot in bulk operations, where you know that all data is consistent.
/* List which columns are referenced in each constraint */
select ' TABLE "'||b.owner||'.'||b.table_name||'"'||
'('||listagg (c.column_name, ',') within group (order by c.column_name)||')'||
' FK "'||b.constraint_name||'" -> '||a.table_name||
' INDEX "'||a.index_name||'"'
"REFERENCES"
from all_indexes a,
all_constraints b,
all_cons_columns c
where rtrim(a.table_name) like 'XXXXXXXXXXXX' -- Table name
and a.index_name = b.r_constraint_name
and c.constraint_name = b.constraint_name
group by b.owner, b.table_name, b.constraint_name, a.table_name, a.index_name
order by 1;

sql:need to change constraint on rename table?

i have change name of table through procedure sp_rename.Do i need to change fk constraint of child table?
Constraints and indexes will be automatically renamed, but you will need to manually do rename work in stored procedures, triggers, user-defined functions, and views that reference the table. See the documentation on MSDN.
No, the table name change will have also updated the apporpriate Metadata in the system catalogs and so the constraint will still be referencing the correct table.
You can use the following script to identify all foreign keys in order to validate your change should you wish.
SELECT PKTABLE_QUALIFIER = CONVERT(SYSNAME,DB_NAME()),
PKTABLE_OWNER = CONVERT(SYSNAME,SCHEMA_NAME(O1.SCHEMA_ID)),
PKTABLE_NAME = CONVERT(SYSNAME,O1.NAME),
PKCOLUMN_NAME = CONVERT(SYSNAME,C1.NAME),
FKTABLE_QUALIFIER = CONVERT(SYSNAME,DB_NAME()),
FKTABLE_OWNER = CONVERT(SYSNAME,SCHEMA_NAME(O2.SCHEMA_ID)),
FKTABLE_NAME = CONVERT(SYSNAME,O2.NAME),
FKCOLUMN_NAME = CONVERT(SYSNAME,C2.NAME),
-- Force the column to be non-nullable (see SQL BU 325751)
--KEY_SEQ = isnull(convert(smallint,k.constraint_column_id), sysconv(smallint,0)),
UPDATE_RULE = CONVERT(SMALLINT,CASE OBJECTPROPERTY(F.OBJECT_ID,'CnstIsUpdateCascade')
WHEN 1 THEN 0
ELSE 1
END),
DELETE_RULE = CONVERT(SMALLINT,CASE OBJECTPROPERTY(F.OBJECT_ID,'CnstIsDeleteCascade')
WHEN 1 THEN 0
ELSE 1
END),
FK_NAME = CONVERT(SYSNAME,OBJECT_NAME(F.OBJECT_ID)),
PK_NAME = CONVERT(SYSNAME,I.NAME),
DEFERRABILITY = CONVERT(SMALLINT,7) -- SQL_NOT_DEFERRABLE
FROM SYS.ALL_OBJECTS O1,
SYS.ALL_OBJECTS O2,
SYS.ALL_COLUMNS C1,
SYS.ALL_COLUMNS C2,
SYS.FOREIGN_KEYS F
INNER JOIN SYS.FOREIGN_KEY_COLUMNS K
ON (K.CONSTRAINT_OBJECT_ID = F.OBJECT_ID)
INNER JOIN SYS.INDEXES I
ON (F.REFERENCED_OBJECT_ID = I.OBJECT_ID
AND F.KEY_INDEX_ID = I.INDEX_ID)
WHERE O1.OBJECT_ID = F.REFERENCED_OBJECT_ID
AND O2.OBJECT_ID = F.PARENT_OBJECT_ID
AND C1.OBJECT_ID = F.REFERENCED_OBJECT_ID
AND C2.OBJECT_ID = F.PARENT_OBJECT_ID
AND C1.COLUMN_ID = K.REFERENCED_COLUMN_ID
AND C2.COLUMN_ID = K.PARENT_COLUMN_ID
This script was sourced from: Identify all of your foreign keys in a SQL Server database

Resources