AskOverflow.Dev

AskOverflow.Dev Logo AskOverflow.Dev Logo

AskOverflow.Dev Navigation

  • 主页
  • 系统&网络
  • Ubuntu
  • Unix
  • DBA
  • Computer
  • Coding
  • LangChain

Mobile menu

Close
  • 主页
  • 系统&网络
    • 最新
    • 热门
    • 标签
  • Ubuntu
    • 最新
    • 热门
    • 标签
  • Unix
    • 最新
    • 标签
  • DBA
    • 最新
    • 标签
  • Computer
    • 最新
    • 标签
  • Coding
    • 最新
    • 标签
主页 / dba / 问题 / 101444
Accepted
Metaphor
Metaphor
Asked: 2015-05-14 10:48:13 +0800 CST2015-05-14 10:48:13 +0800 CST 2015-05-14 10:48:13 +0800 CST

查找以编程方式连接表所需的所有连接

  • 772

给定一个 SourceTable 和一个 TargetTable,我想以编程方式创建一个包含所有所需连接的字符串。

简而言之,我正在尝试找到一种方法来创建这样的字符串:

FROM SourceTable t
JOIN IntermediateTable t1 on t1.keycolumn = t.keycolumn
JOIN TargetTable t2 on t2.keycolumn = t1.keycolumn

我有一个查询,它返回给定表的所有外键,但是在尝试递归地运行所有这些以找到最佳连接路径并制作字符串时遇到了限制。

SELECT 
    p.name AS ParentTable
    ,pc.name AS ParentColumn
    ,r.name AS ChildTable
    ,rc.name AS ChildColumn
FROM sys.foreign_key_columns fk
JOIN sys.columns pc ON pc.object_id = fk.parent_object_id AND pc.column_id = fk.parent_column_id 
JOIN sys.columns rc ON rc.object_id = fk.referenced_object_id AND rc.column_id = fk.referenced_column_id
JOIN sys.tables p ON p.object_id = fk.parent_object_id
JOIN sys.tables r ON r.object_id = fk.referenced_object_id
WHERE fk.parent_object_id = OBJECT_ID('aTable')
ORDER BY ChildTable, fk.referenced_column_id

我确信这已经做过了,但我似乎找不到一个例子。

sql-server sql-server-2014
  • 2 2 个回答
  • 1768 Views

2 个回答

  • Voted
  1. Best Answer
    Geoff Patterson
    2015-05-15T12:38:28+08:002015-05-15T12:38:28+08:00

    我有一个脚本可以完成外键遍历的基本版本。我很快对其进行了调整(见下文),您也许可以将其用作起点。

    给定一个目标表,脚本尝试为所有可能的源表打印最短路径的连接字符串(或在 tie 的情况下其中一个),以便可以遍历单列外键以到达目标表。该脚本似乎在具有几千个表和许多我尝试过的 FK 连接的数据库上运行良好。

    正如其他人在评论中提到的那样,如果您需要处理多列外键,则需要使其更加复杂。另外,请注意,这绝不是生产就绪、经过全面测试的代码。如果您决定构建此功能,希望这是一个有用的起点!

    -- Drop temp tables that will be used below
    IF OBJECT_ID('tempdb..#paths') IS NOT NULL
        DROP TABLE #paths
    GO
    IF OBJECT_ID('tempdb..#shortestPaths') IS NOT NULL
        DROP TABLE #shortestPaths
    GO
    
    -- The table (e.g. "TargetTable") to start from (or end at, depending on your point of view)
    DECLARE @targetObjectName SYSNAME = 'TargetTable'
    
    -- Identify all paths from TargetTable to any other table on the database,
    -- counting all single-column foreign keys as a valid connection from one table to the next
    ;WITH singleColumnFkColumns AS (
        -- We limit the scope of this exercise to single column foreign keys
        -- We explicitly filter out any multi-column foreign keys to ensure that they aren't misinterpreted below
        SELECT fk1.*
        FROM sys.foreign_key_columns fk1
        LEFT JOIN sys.foreign_key_columns fk2 ON fk2.constraint_object_id = fk1.constraint_object_id AND fk2.constraint_column_id = 2
        WHERE fk1.constraint_column_id = 1
            AND fk2.constraint_object_id IS NULL
    )
    , parentCTE AS (
        -- Base case: Find all outgoing (pointing into another table) foreign keys for the specified table
        SELECT 
            p.object_id AS ParentId
            ,OBJECT_SCHEMA_NAME(p.object_id) + '.' + p.name AS ParentTable
            ,pc.column_id AS ParentColumnId
            ,pc.name AS ParentColumn
            ,r.object_id AS ChildId
            ,OBJECT_SCHEMA_NAME(r.object_id) + '.' + r.name AS ChildTable
            ,rc.column_id AS ChildColumnId
            ,rc.name AS ChildColumn
            ,1 AS depth
            -- Maintain the full traversal path that has been taken thus far
            -- We use "," to delimit each table, and each entry then has a
            -- "<object_id>_<parent_column_id>_<child_column_id>" format
            ,   ',' + CONVERT(VARCHAR(MAX), p.object_id) + '_NULL_' + CONVERT(VARCHAR(MAX), pc.column_id) +
                ',' + CONVERT(VARCHAR(MAX), r.object_id) + '_' + CONVERT(VARCHAR(MAX), pc.column_id) + '_' + CONVERT(VARCHAR(MAX), rc.column_id) AS TraversalPath
        FROM sys.foreign_key_columns fk
        JOIN sys.columns pc ON pc.object_id = fk.parent_object_id AND pc.column_id = fk.parent_column_id 
        JOIN sys.columns rc ON rc.object_id = fk.referenced_object_id AND rc.column_id = fk.referenced_column_id
        JOIN sys.tables p ON p.object_id = fk.parent_object_id
        JOIN sys.tables r ON r.object_id = fk.referenced_object_id
        WHERE fk.parent_object_id = OBJECT_ID(@targetObjectName)
            AND p.object_id <> r.object_id -- Ignore FKs from one column in the table to another
    
        UNION ALL
    
        -- Recursive case: Find all outgoing foreign keys for all tables
        -- on the current fringe of the recursion
        SELECT 
            p.object_id AS ParentId
            ,OBJECT_SCHEMA_NAME(p.object_id) + '.' + p.name AS ParentTable
            ,pc.column_id AS ParentColumnId
            ,pc.name AS ParentColumn
            ,r.object_id AS ChildId
            ,OBJECT_SCHEMA_NAME(r.object_id) + '.' + r.name AS ChildTable
            ,rc.column_id AS ChildColumnId
            ,rc.name AS ChildColumn
            ,cte.depth + 1 AS depth
            ,cte.TraversalPath + ',' + CONVERT(VARCHAR(MAX), r.object_id) + '_' + CONVERT(VARCHAR(MAX), pc.column_id) + '_' + CONVERT(VARCHAR(MAX), rc.column_id) AS TraversalPath
        FROM parentCTE cte
        JOIN singleColumnFkColumns fk
            ON fk.parent_object_id = cte.ChildId
            -- Optionally consider only a traversal of the same foreign key
            -- With this commented out, we can reach table A via column A1
            -- and leave table A via column A2.  If uncommented, we can only
            -- enter and leave a table via the same column
            --AND fk.parent_column_id = cte.ChildColumnId
        JOIN sys.columns pc ON pc.object_id = fk.parent_object_id AND pc.column_id = fk.parent_column_id 
        JOIN sys.columns rc ON rc.object_id = fk.referenced_object_id AND rc.column_id = fk.referenced_column_id
        JOIN sys.tables p ON p.object_id = fk.parent_object_id
        JOIN sys.tables r ON r.object_id = fk.referenced_object_id
        WHERE p.object_id <> r.object_id -- Ignore FKs from one column in the table to another
            -- If our path has already taken us to this table, avoid the cycle that would be created by returning to the same table
            AND cte.TraversalPath NOT LIKE ('%_' + CONVERT(VARCHAR(MAX), r.object_id) + '%')
    )
    SELECT *
    INTO #paths
    FROM parentCTE
    ORDER BY depth, ParentTable, ChildTable
    GO
    
    -- For each distinct table that can be reached by traversing foreign keys,
    -- record the shortest path to that table (or one of the shortest paths in
    -- case there are multiple paths of the same length)
    SELECT *
    INTO #shortestPaths
    FROM (
        SELECT *, ROW_NUMBER() OVER (PARTITION BY ChildTable ORDER BY depth ASC) AS rankToThisChild
        FROM #paths
    ) x
    WHERE rankToThisChild = 1
    ORDER BY ChildTable
    GO
    
    -- Traverse the shortest path, starting from the source the full path and working backwards,
    -- building up the desired join string as we go
    WITH joinCTE AS (
        -- Base case: Start with the from clause to the child table at the end of the traversal
        -- Note that the first step of the recursion will re-process this same row, but adding
        -- the ParentTable => ChildTable join
        SELECT p.ChildTable
            , p.TraversalPath AS ParentTraversalPath
            , NULL AS depth
            , CONVERT(VARCHAR(MAX), 'FROM ' + p.ChildTable + ' t' + CONVERT(VARCHAR(MAX), p.depth+1)) AS JoinString
        FROM #shortestPaths p
    
        UNION ALL
    
        -- Recursive case: Process the ParentTable => ChildTable join, then recurse to the
        -- previous table in the full traversal.  We'll end once we reach the root and the
        -- "ParentTraversalPath" is the empty string
        SELECT cte.ChildTable
            , REPLACE(p.TraversalPath, ',' + CONVERT(VARCHAR, p.ChildId) + '_' + CONVERT(VARCHAR, p.ParentColumnId)+ '_' + CONVERT(VARCHAR, p.ChildColumnId), '') AS TraversalPath
            , p.depth
            , cte.JoinString + '
    ' + CONVERT(VARCHAR(MAX), 'JOIN ' + p.ParentTable + ' t' + CONVERT(VARCHAR(MAX), p.depth) + ' ON t' + CONVERT(VARCHAR(MAX), p.depth) + '.' + p.ParentColumn + ' = t' + CONVERT(VARCHAR(MAX), p.depth+1) + '.' + p.ChildColumn) AS JoinString
        FROM joinCTE cte
        JOIN #paths p
            ON p.TraversalPath = cte.ParentTraversalPath
    )
    -- Select only the fully built strings that end at the root of the traversal
    -- (which should always be the specific table name, e.g. "TargetTable")
    SELECT ChildTable, 'SELECT TOP 100 * 
    ' +JoinString
    FROM joinCTE
    WHERE depth = 1
    ORDER BY ChildTable
    GO
    
    • 4
  2. Muthukrishnan Senthilkumaran
    2018-08-22T06:46:57+08:002018-08-22T06:46:57+08:00

    您可以为要连接的所有表放置包含两个字段 TAB_NAME、KEY_NAME 的表的键列表。

    例如,对于表City

    • 城市|城市名称
    • 城市|国家名称
    • 城市|省名
    • 城市|城市_代码

    同样Province和Country。

    收集表的数据并放入单个表(例如元数据表)

    现在起草如下查询

    select * from
    (Select Table_name,Key_name from Meta_Data 
    where Table_name in ('City','Province','Country')) A,
    (Select Table_name,Key_name from Meta_Data 
    where Table_name in ('City','Province','Country')) B,
    (Select Table_name,Key_name from Meta_Data 
    where Table_name in ('City','Province','Country')) C
    
    where
    
    A.Table_Name <> B.Table_name and
    B.Table_name <> C.Table_name and
    C.Table_name <> A.Table_name and
    A.Column_name = B.Column_name and
    B.Column_name = C.Column_name
    

    这将使您了解如何根据匹配的键(相同的键名)链接表

    如果您认为键名可能不匹配,您可以包含一个备用键字段并尝试在 where 条件中使用它。

    • 0

相关问题

  • SQL Server - 使用聚集索引时如何存储数据页

  • 我需要为每种类型的查询使用单独的索引,还是一个多列索引可以工作?

  • 什么时候应该使用唯一约束而不是唯一索引?

  • 死锁的主要原因是什么,可以预防吗?

  • 如何确定是否需要或需要索引

Sidebar

Stats

  • 问题 205573
  • 回答 270741
  • 最佳答案 135370
  • 用户 68524
  • 热门
  • 回答
  • Marko Smith

    连接到 PostgreSQL 服务器:致命:主机没有 pg_hba.conf 条目

    • 12 个回答
  • Marko Smith

    如何让sqlplus的输出出现在一行中?

    • 3 个回答
  • Marko Smith

    选择具有最大日期或最晚日期的日期

    • 3 个回答
  • Marko Smith

    如何列出 PostgreSQL 中的所有模式?

    • 4 个回答
  • Marko Smith

    列出指定表的所有列

    • 5 个回答
  • Marko Smith

    如何在不修改我自己的 tnsnames.ora 的情况下使用 sqlplus 连接到位于另一台主机上的 Oracle 数据库

    • 4 个回答
  • Marko Smith

    你如何mysqldump特定的表?

    • 4 个回答
  • Marko Smith

    使用 psql 列出数据库权限

    • 10 个回答
  • Marko Smith

    如何从 PostgreSQL 中的选择查询中将值插入表中?

    • 4 个回答
  • Marko Smith

    如何使用 psql 列出所有数据库和表?

    • 7 个回答
  • Martin Hope
    Jin 连接到 PostgreSQL 服务器:致命:主机没有 pg_hba.conf 条目 2014-12-02 02:54:58 +0800 CST
  • Martin Hope
    Stéphane 如何列出 PostgreSQL 中的所有模式? 2013-04-16 11:19:16 +0800 CST
  • Martin Hope
    Mike Walsh 为什么事务日志不断增长或空间不足? 2012-12-05 18:11:22 +0800 CST
  • Martin Hope
    Stephane Rolland 列出指定表的所有列 2012-08-14 04:44:44 +0800 CST
  • Martin Hope
    haxney MySQL 能否合理地对数十亿行执行查询? 2012-07-03 11:36:13 +0800 CST
  • Martin Hope
    qazwsx 如何监控大型 .sql 文件的导入进度? 2012-05-03 08:54:41 +0800 CST
  • Martin Hope
    markdorison 你如何mysqldump特定的表? 2011-12-17 12:39:37 +0800 CST
  • Martin Hope
    Jonas 如何使用 psql 对 SQL 查询进行计时? 2011-06-04 02:22:54 +0800 CST
  • Martin Hope
    Jonas 如何从 PostgreSQL 中的选择查询中将值插入表中? 2011-05-28 00:33:05 +0800 CST
  • Martin Hope
    Jonas 如何使用 psql 列出所有数据库和表? 2011-02-18 00:45:49 +0800 CST

热门标签

sql-server mysql postgresql sql-server-2014 sql-server-2016 oracle sql-server-2008 database-design query-performance sql-server-2017

Explore

  • 主页
  • 问题
    • 最新
    • 热门
  • 标签
  • 帮助

Footer

AskOverflow.Dev

关于我们

  • 关于我们
  • 联系我们

Legal Stuff

  • Privacy Policy

Language

  • Pt
  • Server
  • Unix

© 2023 AskOverflow.DEV All Rights Reserve