是否有一种简单的方法(或工具)可以在大型数据库中查找从一个表到另一个表的连接路径?
我目前正在开发一个数据库有超过 150 个表的项目。下面是我正在尝试做的一些用例。
用例:
输入
- 选择表 A
- 选择表 B
输出
- 打印出表之间的所有可用路径。
- 打印出最有效的路线。
是否有一种简单的方法(或工具)可以在大型数据库中查找从一个表到另一个表的连接路径?
我目前正在开发一个数据库有超过 150 个表的项目。下面是我正在尝试做的一些用例。
用例:
输入
输出
由于我假设您有一些通过数据库的非常复杂的路径,您将无法仅使用一个查询甚至几个查询来完成它。我已经完成了(通过我继承的一个项目),并学到了一些有趣的东西。但我必须编写一个程序来做到这一点。
我所做的是使用 Diego 在他的回答中引用的模式视图,并应用一些方法来解决图论问题(因为这基本上就是你所拥有的,在这里,表是节点,外键链接在图中。)
基本上,如果我没记错的话(这是几年前的事),你从一个表开始,然后通过将每个引用的另一个表的名称放入队列来处理它的所有外键。检查自引用和循环(您需要一个哈希集或已处理表的列表。)然后将下一个表从队列中弹出并重复。最终,您将遇到另一张桌子,或者已经处理了您可以从原始桌子“到达”的每张桌子。
这应该可以帮助你。它将显示您的表、具有 FK 的列、引用的表和列以及 FK 名称。
SELECT
K_Table = FK.TABLE_NAME,
FK_Column = CU.COLUMN_NAME,
PK_Table = PK.TABLE_NAME,
PK_Column = PT.COLUMN_NAME,
Constraint_Name = C.CONSTRAINT_NAME
FROM INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS C
INNER JOIN INFORMATION_SCHEMA.TABLE_CONSTRAINTS FK ON C.CONSTRAINT_NAME = FK.CONSTRAINT_NAME
INNER JOIN INFORMATION_SCHEMA.TABLE_CONSTRAINTS PK ON C.UNIQUE_CONSTRAINT_NAME = PK.CONSTRAINT_NAME
INNER JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE CU ON C.CONSTRAINT_NAME = CU.CONSTRAINT_NAME
INNER JOIN (
SELECT i1.TABLE_NAME, i2.COLUMN_NAME
FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS i1
INNER JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE i2 ON i1.CONSTRAINT_NAME = i2.CONSTRAINT_NAME
WHERE i1.CONSTRAINT_TYPE = 'PRIMARY KEY'
) PT ON PT.TABLE_NAME = PK.TABLE_NAME
我从 Zen 和上面的代码中借用了代码并创建了这个。到目前为止,它似乎工作得很好。
IF OBJECT_ID('tempdb.dbo.#TableSchema') IS NOT NULL BEGIN DROP TABLE #TableSchema END
SELECT
PK_Table = PK.TABLE_NAME,
PK_Column = PT.COLUMN_NAME,
K_Table = FK.TABLE_NAME,
FK_Column = CU.COLUMN_NAME,
Constraint_Name = C.CONSTRAINT_NAME
INTO #TableSchema
FROM INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS C
INNER JOIN INFORMATION_SCHEMA.TABLE_CONSTRAINTS FK ON C.CONSTRAINT_NAME = FK.CONSTRAINT_NAME
INNER JOIN INFORMATION_SCHEMA.TABLE_CONSTRAINTS PK ON C.UNIQUE_CONSTRAINT_NAME = PK.CONSTRAINT_NAME
INNER JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE CU ON C.CONSTRAINT_NAME = CU.CONSTRAINT_NAME
INNER JOIN (
SELECT i1.TABLE_NAME, i2.COLUMN_NAME
FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS i1
INNER JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE i2 ON i1.CONSTRAINT_NAME = i2.CONSTRAINT_NAME
WHERE i1.CONSTRAINT_TYPE = 'PRIMARY KEY'
) PT ON PT.TABLE_NAME = PK.TABLE_NAME
CREATE TABLE [#TablesList] (
c_from NVARCHAR(450),
c_to NVARCHAR(450),
PRIMARY KEY (c_from, c_to)
);
INSERT INTO [#TablesList]
SELECT DISTINCT PK_Table,K_Table from #TableSchema
IF OBJECT_ID('__TablesLink__') IS NOT NULL BEGIN DROP TABLE [__TablesLink__] END
CREATE TABLE [__TablesLink__] (c_path NVARCHAR(MAX));-- PRIMARY KEY);
WITH PathCTE
AS
(SELECT c_from, c_to,
CAST('>' + CAST(c_from AS NVARCHAR(MAX)) + '>' +
CAST(c_to AS NVARCHAR(MAX)) + '>' AS NVARCHAR(MAX)) AS c_path
FROM [#TablesList] AS C1
UNION ALL
SELECT C.c_from, C.c_to,
CAST(P.c_path + C.c_to + '>' AS NVARCHAR(max))
FROM PathCTE AS P
JOIN [#TablesList] AS C
ON P.c_to = C.c_from
WHERE P.c_path NOT LIKE '%>' +
CAST(C.c_from AS NVARCHAR(max)) +
'>' +
CAST(C.c_to AS NVARCHAR(max)) +
'>%')
INSERT INTO [__TablesLink__]
SELECT c_path FROM PathCTE;
SELECT c_path
FROM [__TablesLink__]
WHERE c_path LIKE '>' + 'tableA' + '>%'
AND c_path LIKE '%>'+ 'tableZ' +'>';
我不同意上面推荐的答案,这实际上很容易通过几个递归 CTE 来实现。其中第一个可用于导航给定源表中的所有外键路径。如果存在路径,则此结果可用于定位目标表。
第二个递归 CTE 可用于备份结果链以查找两个表之间的选定路径信息。
请看下面...
DECLARE @SOURCE_TABLE_NAME NVARCHAR(200) = 'MYSOURCETABLE'
DECLARE @SOURCE_SCHEMA_NAME NVARCHAR(200) = 'MYSOURCESCHEMA'
DECLARE @TARGET_TABLE_NAME NVARCHAR(200) = 'MYTARGETTABLE'
DECLARE @TARGET_SCHEMA_NAME NVARCHAR(200) = 'MYTARGETSCHEMA'
-- holds the navigation of all foreign-key routes from the source table onwards.
--------------------------------------------------------------------------------
DECLARE @RESULTS AS TABLE
(
[ROW_ID] [UNIQUEIDENTIFIER] NOT NULL PRIMARY KEY,
[PARENT_ROW] [UNIQUEIDENTIFIER] NULL,
[CHILD_SCHEMA] [NVARCHAR](250) NULL,
[CHILD_TABLE] [NVARCHAR](250) NULL,
[CHILD_COLUMN] [NVARCHAR](250) NULL,
[PARENT_SCHEMA] [NVARCHAR](250) NULL,
[PARENT_TABLE] [NVARCHAR](250) NULL,
[PARENT_COLUMN] [NVARCHAR](250) NULL,
[CONSTRAINT_NAME] [NVARCHAR](250) NULL,
[PATH] [NVARCHAR](600) NULL,
[DEPTH] [INT] NOT NULL
)
;WITH [PARENT_RELATIONS]
AS (
-- return specific process
--------------------------
SELECT NEWID() AS [ROW_ID] ,
CAST(NULL AS UNIQUEIDENTIFIER) AS [PARENT_ROW] ,
[CHILD].[TABLE_SCHEMA] AS [CHILD_SCHEMA] ,
[CHILD].[TABLE_NAME] AS [CHILD_TABLE] ,
[CHILD_COLUMN].[COLUMN_NAME] AS [CHILD_COLUMN] ,
[PARENT].[TABLE_SCHEMA] AS [PARENT_SCHEMA] ,
[PARENT].[TABLE_NAME] AS [PARENT_TABLE] ,
[PARENT_COLUMN].[COLUMN_NAME] AS [PARENT_COLUMN] ,
[CONTRAINT].[CONSTRAINT_NAME] AS [CONSTRAINT_NAME] ,
CAST(UPPER(
'[' + [CHILD].[TABLE_SCHEMA] + '].[' + [CHILD].[TABLE_NAME] + '].[' + [CHILD_COLUMN].[COLUMN_NAME] + '] = ' +
'[' + [PARENT].[TABLE_SCHEMA] + '].[' + [PARENT].[TABLE_NAME] + '].[' + [PARENT_COLUMN].[COLUMN_NAME] + ']') AS [varchar](600)) AS [PATH] ,
0 AS [DEPTH]
FROM INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS [CONTRAINT]
INNER JOIN INFORMATION_SCHEMA.TABLE_CONSTRAINTS [CHILD]
ON [CONTRAINT].[CONSTRAINT_NAME] = [CHILD].[CONSTRAINT_NAME]
INNER JOIN INFORMATION_SCHEMA.TABLE_CONSTRAINTS [PARENT]
ON [PARENT].[CONSTRAINT_NAME] = [CONTRAINT].[UNIQUE_CONSTRAINT_NAME]
AND [PARENT].[CONSTRAINT_TYPE] = 'PRIMARY KEY'
INNER JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE [CHILD_COLUMN]
ON [CONTRAINT].[CONSTRAINT_NAME] = [CHILD_COLUMN].[CONSTRAINT_NAME]
INNER JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE AS [PARENT_COLUMN]
ON [PARENT_COLUMN].[CONSTRAINT_NAME] = [PARENT].[CONSTRAINT_NAME]
AND [PARENT_COLUMN].[TABLE_NAME] = [PARENT].[TABLE_NAME]
WHERE [CHILD].[TABLE_NAME] = @SOURCE_TABLE_NAME
AND [CHILD].[TABLE_SCHEMA] = @SOURCE_SCHEMA_NAME
UNION ALL
-- return related relationships
--------------------------
SELECT NEWID() AS [ROW_ID] ,
[PARENT_RELATIONS].[ROW_ID] AS [PARENT_ROW] ,
[CHILD].[TABLE_SCHEMA] AS [CHILD_SCHEMA] ,
[CHILD].[TABLE_NAME] AS [CHILD_TABLE] ,
[CHILD_COLUMN].[COLUMN_NAME] AS [CHILD_COLUMN] ,
[PARENT].[TABLE_SCHEMA] AS [PARENT_SCHEMA] ,
[PARENT].[TABLE_NAME] AS [PARENT_TABLE] ,
[PARENT_COLUMN].[COLUMN_NAME] AS [PARENT_COLUMN] ,
[CONTRAINT].[CONSTRAINT_NAME] AS [CONSTRAINT_NAME] ,
CAST([PARENT_RELATIONS].[PATH] + UPPER(' -->> ' +
'[' + [CHILD].[TABLE_SCHEMA] + '].[' + [CHILD].[TABLE_NAME] + '].[' + [CHILD_COLUMN].[COLUMN_NAME] + '] = ' +
'[' + [PARENT].[TABLE_SCHEMA] + '].[' + [PARENT].[TABLE_NAME] + '].[' + [PARENT_COLUMN].[COLUMN_NAME] + ']') AS [varchar](600))
AS [PATH] ,
[PARENT_RELATIONS].[DEPTH] + 1 AS [DEPTH]
FROM INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS [CONTRAINT]
INNER JOIN INFORMATION_SCHEMA.TABLE_CONSTRAINTS [CHILD]
ON [CONTRAINT].[CONSTRAINT_NAME] = [CHILD].[CONSTRAINT_NAME]
INNER JOIN INFORMATION_SCHEMA.TABLE_CONSTRAINTS [PARENT]
ON [PARENT].[CONSTRAINT_NAME] = [CONTRAINT].[UNIQUE_CONSTRAINT_NAME]
AND [PARENT].[CONSTRAINT_TYPE] = 'PRIMARY KEY'
INNER JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE [CHILD_COLUMN]
ON [CONTRAINT].[CONSTRAINT_NAME] = [CHILD_COLUMN].[CONSTRAINT_NAME]
INNER JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE AS [PARENT_COLUMN]
ON [PARENT_COLUMN].[CONSTRAINT_NAME] = [PARENT].[CONSTRAINT_NAME]
AND [PARENT_COLUMN].[TABLE_NAME] = [PARENT].[TABLE_NAME]
INNER JOIN [PARENT_RELATIONS] AS [PARENT_RELATIONS]
ON [PARENT_RELATIONS].[PARENT_TABLE] = [CHILD].[TABLE_NAME]
AND [PARENT_RELATIONS].[PARENT_SCHEMA] = [CHILD].[TABLE_SCHEMA]
)
-- inserts all possible results into local storage for iteration
----------------------------------------------------------------
INSERT INTO @RESULTS SELECT * FROM [PARENT_RELATIONS];
;WITH [KNOWN_PATH]
AS (
-- return the optimal root target row information
-------------------------------------------------
SELECT [FILTER].[PARENT_ROW] as [PARENT_ROW] ,
[FILTER].[CHILD_SCHEMA] as [CHILD_SCHEMA] ,
[FILTER].[CHILD_TABLE] as [CHILD_TABLE] ,
[FILTER].[CHILD_COLUMN] as [CHILD_COLUMN] ,
[FILTER].[PARENT_SCHEMA] as [PARENT_SCHEMA] ,
[FILTER].[PARENT_TABLE] as [PARENT_TABLE] ,
[FILTER].[PARENT_COLUMN] as [PARENT_COLUMN] ,
[FILTER].[DEPTH] as [DEPTH] ,
[FILTER].[PATH] as [PATH]
FROM (
SELECT ROW_NUMBER() OVER(ORDER BY [DEPTH]) AS [OPTIMAL],
[RESULTS].[PARENT_ROW] as [PARENT_ROW] ,
[RESULTS].[CHILD_SCHEMA] as [CHILD_SCHEMA] ,
[RESULTS].[CHILD_TABLE] as [CHILD_TABLE] ,
[RESULTS].[CHILD_COLUMN] as [CHILD_COLUMN] ,
[RESULTS].[PARENT_SCHEMA] as [PARENT_SCHEMA] ,
[RESULTS].[PARENT_TABLE] as [PARENT_TABLE] ,
[RESULTS].[PARENT_COLUMN] as [PARENT_COLUMN] ,
[RESULTS].[DEPTH] as [DEPTH] ,
[RESULTS].[PATH] as [PATH]
FROM @RESULTS AS [RESULTS]
WHERE [RESULTS].[PARENT_TABLE] = @TARGET_TABLE_NAME
AND [RESULTS].[PARENT_SCHEMA] = @TARGET_SCHEMA_NAME
) AS [FILTER]
WHERE [FILTER].[OPTIMAL] = 1
UNION ALL
-- return the parent of the target up the path
-------------------------------------------
SELECT [RESULTS].[PARENT_ROW] as [PARENT_ROW] ,
[RESULTS].[CHILD_SCHEMA] as [CHILD_SCHEMA] ,
[RESULTS].[CHILD_TABLE] as [CHILD_TABLE] ,
[RESULTS].[CHILD_COLUMN] as [CHILD_COLUMN] ,
[RESULTS].[PARENT_SCHEMA] as [PARENT_SCHEMA] ,
[RESULTS].[PARENT_TABLE] as [PARENT_TABLE] ,
[RESULTS].[PARENT_COLUMN] as [PARENT_COLUMN] ,
[RESULTS].[DEPTH] as [DEPTH] ,
[RESULTS].[PATH] as [PATH]
FROM @RESULTS AS [RESULTS]
INNER JOIN [KNOWN_PATH]
ON [RESULTS].[ROW_ID] = [KNOWN_PATH].[PARENT_ROW]
)
-- return the correct path from one-table to another
----------------------------------------------------
SELECT [DEPTH] ,
[PATH] ,
[CHILD_SCHEMA] ,
[CHILD_TABLE] ,
[CHILD_COLUMN] ,
[PARENT_SCHEMA] ,
[PARENT_TABLE] ,
[PARENT_COLUMN]
FROM [KNOWN_PATH] ORDER BY [DEPTH] ASC
注意:如果从源表到目的表存在多条路由,可以利用ROW_NUMBER()在二级CTE的primer语句中组织结果集,对结果进行过滤,只得到最优路径使用最低 DEPTH 结果。我现在已经编辑了 SQL 语句以包含它,因为这是请求者请求的。
亲切的问候,
蒂姆