Query system tables for multiple cascade paths - sql-server

I use Entity Framework to generate my tables from my model, and every now and again I get a problem with multiple cascade paths. e.g:
Introducing FOREIGN KEY constraint
'FK_dbo.SurveyorSurveys_dbo.Surveys_Survey_ID' on table
'SurveyorSurveys' may cause cycles or multiple cascade paths.
I find it quite time consuming working out where the cascade paths are to this new table, so that I can remove one of the cascade deletes. I think it would be really useful if I could run a query on the system tables that showed me the full paths that were involved. Has anyone worked out how to do that?
For example I have this sql:
SELECT
ro.name referenced_object,
po.name parent_object,
fk.name foreign_key
FROM sys.foreign_keys fk
INNER JOIN sys.all_objects po
ON fk.parent_object_id = po.object_id
INNER JOIN sys.all_objects ro
ON fk.referenced_object_id = ro.object_id
WHERE delete_referential_action_desc = 'CASCADE'
and (po.name = 'Surveys' or po.name = 'Surveyors'')
ORDER BY ro.name
But this only takes me up one level in the hierarchy, it might be better to remove a cascade delete at a higher level, so I end up adjusting and re-running the sql until I work out the full path

This is typically something to do with a recursive query.
First a few words to clarify the column names in sys.all_objects:
parent_object_id refers to the object owning the foreign key, so it's not the parent in the relationship, it's the child.
referenced_object_id is the parent in the relationship.
Knowing this, we can write a recursive query that collects trees of foreign key relationships by connecting the child in the previous level with the parent in the subsequent level:
WITH [Cascades] AS(
SELECT
fk.referenced_object_id AS [RootId],
fk.parent_object_id AS [ChildId],
fk.referenced_object_id AS [ParentId],
fk.name foreign_key,
1 AS [Level]
FROM sys.foreign_keys fk
WHERE delete_referential_action_desc = 'CASCADE'
UNION ALL
SELECT [Cascades].[RootId],
fk.parent_object_id,
fk.referenced_object_id,
fk.name foreign_key,
[Cascades].[Level] + 1
FROM sys.foreign_keys fk
INNER JOIN [Cascades] ON [Cascades].[ChildId] = fk.referenced_object_id
WHERE delete_referential_action_desc = 'CASCADE'
)
SELECT [root].Name AS [Root],
po.Name AS [Parent],
co.Name AS [Child],
foreign_key,
cs.[Level]
FROM [Cascades] cs
INNER JOIN sys.all_objects [root] ON cs.[RootId] = [root].object_id
INNER JOIN sys.all_objects po ON cs.ParentId = po.object_id
INNER JOIN sys.all_objects co ON cs.ChildId = co.object_id
WHERE [root].name = 'Surveys'
ORDER BY [Root], [Parent], [Level], [Child]
If I execute the query in a simple tree-level hierarchy, the output is:
Root Parent Child foreign_key Level
-----------------------------------
A A B1 FK_B1_A 1
A A B2 FK_B2_A 1
A B1 C FK_C_B1 2

Alternative sql based on Gert's excellent answer. This one lets me specify the leaf and shows the full cascade path on each row
WITH Cascades
AS (SELECT
fk.parent_object_id,
fk.referenced_object_id,
fk.name foreign_keys,
CAST(ro.name + ' -> ' + po.name AS nvarchar(256)) path,
1 AS level
FROM sys.foreign_keys fk
INNER JOIN sys.all_objects po
ON fk.parent_object_id = po.object_id
INNER JOIN sys.all_objects ro
ON fk.referenced_object_id = ro.object_id
WHERE delete_referential_action_desc = 'CASCADE'
-- specifying leaf instead of root:
AND po.name IN ('Surveys', 'Surveyors', 'surveyorsurveys')
UNION ALL SELECT
fk.parent_object_id,
fk.referenced_object_id,
CAST(fk.name + ' -> ' + Cascades.foreign_keys as nvarchar(128)),
CAST(ro.name + ' -> ' + cascades.path AS nvarchar(256)),
cascades.level + 1
FROM sys.foreign_keys fk
INNER JOIN sys.all_objects po
ON fk.parent_object_id = po.object_id
INNER JOIN sys.all_objects ro
ON fk.referenced_object_id = ro.object_id
INNER JOIN cascades
ON cascades.referenced_object_id = fk.parent_object_id
WHERE delete_referential_action_desc = 'CASCADE')
SELECT
path,
foreign_keys,
level
FROM Cascades
ORDER BY path

Related

Determine SQL Server foreign key name

I'm trying to determine the name of the foreign key on a table but I'm getting wrong values. If I use this example SQL
CREATE TABLE Address
(
id INT NOT NULL PRIMARY KEY,
street VARCHAR NOT NULL
);
CREATE TABLE Person
(
id INT NOT NULL PRIMARY KEY,
orgId INT NOT NULL REFERENCES dbo.Organization (id)
);
SELECT tbl.name TableName, col.name ColumnName, fk.name ForeignKey
FROM sys.tables tbl
JOIN sys.columns col ON tbl.object_id = col.object_id
LEFT OUTER JOIN sys.foreign_keys fk ON tbl.object_id = fk.parent_object_id
WHERE tbl.name = 'Person'
it's telling me the ForeignKey name is the same thing for both the id and the orgId columns of the Person table. Clearly I'm missing an extra join condition, but I can't for the life of me figure out what it is.
your query is missing some part,to see which columns have FK you can use sys.foreign_key_columns view.
here is what you need:
SELECT
tbl.name TableName
, col.name ColumnName
, fk2.name ForeignKey
FROM
sys.tables tbl
JOIN sys.columns col
ON tbl.object_id = col.object_id
LEFT OUTER JOIN sys.foreign_key_columns AS fk
ON col.object_id = fk.parent_object_id
AND fk.parent_column_id = col.column_id
LEFT OUTER JOIN sys.foreign_keys AS fk2
ON fk2.object_id = fk.constraint_object_id
WHERE
tbl.name = 'Person';

Return Stored Procedures, tables and columns in reference to a particular database

I am looking to write a query that will return all of the stored procedures and views that reference a specific database that I am looking to rebuild. I also need the query to return the columns and table names that the returned stored procedures / views contain in reference to the database being rebuilt.
I have a query right now that will return the stored procs and views, but have been unsuccessful in returning the column and table names that reference the database.
SELECT Distinct so.Name, so.type--, sc.text
FROM sysobjects so(NOLOCK)
INNER JOIN syscomments sc (NOLOCK) on so.Id = sc.ID
WHERE so.Type in( 'p' , 'v')
AND sc.Text LIKE '%membership_dw.%'
ORDER BY so.Name
Membership_DW is the name of the database where the stored procedures and views are currently stored, and it is also the database that is being recreated.
Here is a quick and dirty query to do it
;WITH cte as(
select *
from sys.sysdepends t1
inner join sys.objects t2 on t1.depid = t2.object_id
where t2.type = 'U' and is_ms_shipped = 0)
SELECT t5.name as SP_NAME, t6.name as TBL_NAME, t9.name COLNAME from sys.objects t5
INNER JOIN cte t6 on t6.id = t5.object_id
INNER JOIN sys.columns t9 on t6.object_id = t9.object_id
where t5.is_ms_shipped = 0 and t6.name not like 'sys%'
order by sp_name, TBL_NAME

All columns that refer to a column as foreign key

How can I have a list of all columns in other tables/schemas that refer to a certain column A as foreign key ?
Something like this?
SELECT
fk.Name,
'Referenced table' = refTbl.Name,
'Parent table' = parentTbl.Name,
'Parent column' = c.name
FROM
-- FK constraint
sys.foreign_keys fk
INNER JOIN
-- Referenced table (where the PK resides)
sys.tables refTbl ON fk.referenced_object_id = refTbl.object_id
INNER JOIN
-- Parent table (which has the foreign key column)
sys.tables parentTbl ON fk.parent_object_id = parentTbl .object_id
INNER JOIN
-- link to the columns involved in the FK contraint
sys.foreign_key_columns fkc ON fkc.constraint_object_id = fk.object_id
INNER JOIN
-- column in the parent table that's part of the FK constraint
sys.columns c ON c.object_id = parentTbl.object_id AND c.column_id = fkc.parent_column_id
WHERE
refTbl.name = 'YourTableNameHere'
ORDER BY
fk.Name, parentTbl.name

Updating MS SQL objects with dependencies

I have sql scripts DROP ... CREATE... for views, procedures, triggers, functions (in files on disk). I need to run this scripts to update database structure.
But order of executing scripts is important. Because if view1 depends on view2, and I run scripts for view1 first, an error may occurs.
Let's suppose that there are no adscititious dependencies in my scripts, so database knows about all dependencies.
Is there a way to select all this objects names from sql server in dependency order? So I can run scripts in this order and not afraid about above errors.
I wrote this sql:
SET NOCOUNT ON
declare #deps TABLE (name nvarchar(512), dep_name nvarchar(512))
declare #ordered TABLE (name nvarchar(512), [level] INT)
insert #deps(name, dep_name)
SELECT DISTINCT CAST(OBJ.name as nvarchar(512)) AS ObjectName,
CAST(REFOBJ.name as nvarchar(512)) AS ReferencedObjectName
FROM sys.sql_dependencies AS DEP
INNER JOIN
sys.objects AS OBJ
ON DEP.object_id = OBJ.object_id
INNER JOIN
sys.schemas AS SCH
ON OBJ.schema_id = SCH.schema_id
INNER JOIN sys.objects AS REFOBJ
ON DEP.referenced_major_id = REFOBJ.object_id
INNER JOIN sys.schemas AS REFSCH
ON REFOBJ.schema_id = REFSCH.schema_id
LEFT JOIN sys.columns AS REFCOL
ON DEP.class IN (0, 1)
AND DEP.referenced_minor_id = REFCOL.column_id
AND DEP.referenced_major_id = REFCOL.object_id
WHERE OBJ.type_desc IN ('VIEW','SQL_STORED_PROCEDURE','SQL_INLINE_TABLE_VALUED_FUNCTION','SQL_TRIGGER','SQL_SCALAR_FUNCTION')
AND REFOBJ.type_desc IN ('VIEW','SQL_STORED_PROCEDURE','SQL_INLINE_TABLE_VALUED_FUNCTION','SQL_TRIGGER','SQL_SCALAR_FUNCTION')
insert #ordered(name, [level])
select distinct d1.dep_name, 1 as [level]
from #deps d1
LEFT JOIN #deps d2 ON d1.dep_name = d2.name
where d2.name IS NULL
WHILE EXISTS(select * from #deps)
BEGIN
delete d
FROM #deps d
JOIN #ordered o ON d.name = o.name
insert #ordered(name, [level])
SELECT DISTINCT d0.name, (select MAX([level]) + 1 FROM #ordered)
FROM #deps d0
LEFT JOIN (
SELECT d.name
from #deps d
LEFT JOIN #ordered o ON d.dep_name = o.name
WHERE o.name IS NULL
) dfilter ON d0.name = dfilter.name
WHERE dfilter.name is NULL
END
select * from #ordered
ORDER by level asc

How to find what table foreign key is from?

I have a table with a foreign key. How can I tell what table the FK is a primary key in? There's about 200 tables and I don't know how to find where that info is coming from/connected to.
Use this..
SELECT fk.name,
Object_name(fk.parent_object_id) [Parent table],
c1.name [Parent column]
FROM sys.foreign_keys fk
INNER JOIN sys.foreign_key_columns fkc
ON fkc.constraint_object_id = fk.object_id
INNER JOIN sys.columns c1
ON fkc.parent_column_id = c1.column_id
AND fkc.parent_object_id = c1.object_id
INNER JOIN sys.columns c2
ON fkc.referenced_column_id = c2.column_id
AND fkc.referenced_object_id = c2.object_id
WHERE Object_name(fk.referenced_object_id) = 'Tablename' -- Replace with your tablename
AND c2.name = 'Columname' -- Replace with your columname
Or simply use
sp_help Tablename or [Alt]+F1
This should help. Just run it in the DB you wish to query:
SELECT f.NAME AS ForeignKey
,SCHEMA_NAME(f.SCHEMA_ID) SchemaName
,OBJECT_NAME(f.parent_object_id) AS TableName
,COL_NAME(fc.parent_object_id, fc.parent_column_id) AS ColumnName
,SCHEMA_NAME(o.SCHEMA_ID) ReferenceSchemaName
,OBJECT_NAME(f.referenced_object_id) AS ReferenceTableName
,COL_NAME(fc.referenced_object_id, fc.referenced_column_id) AS ReferenceColumnName
FROM sys.foreign_keys AS f
INNER JOIN sys.foreign_key_columns AS fc ON f.OBJECT_ID = fc.constraint_object_id
INNER JOIN sys.objects AS o ON o.OBJECT_ID = fc.referenced_object_id
GO
Source: http://blog.sqlauthority.com/2009/02/26/sql-server-2008-find-relationship-of-foreign-key-and-primary-key-using-t-sql-find-tables-with-foreign-key-constraint-in-database/
Basically, the first column is the FK, followed by the FK schema and object. Following those are the PK column name, its schema and object.
Either of the answers by NoDisplayName or Kris G. should work, but if you want something easier to remember while you're in SSMS, just right click the Foreign Key and choose Script As>Create To>New Window.
You will then get a script that can be used to (re-)create the FK, and you will be able to see what column it references in what table by reading the script.

Resources