Tìm tất cả các tham gia cần thiết để lập trình tham gia một bảng


8

Đưa ra một SourceTable và TargetTable, tôi muốn lập trình tạo một chuỗi với tất cả các phép nối được yêu cầu.

Nói tóm lại, tôi đang cố gắng tìm cách tạo ra một chuỗi như thế này:

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

Tôi có một truy vấn trả về tất cả các khóa ngoại cho một bảng đã cho, nhưng đang gặp phải những hạn chế trong việc cố gắng chạy qua tất cả các đệ quy này để tìm đường dẫn tham gia tối ưu và tạo chuỗi.

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

Tôi chắc chắn điều này đã được thực hiện trước đây, nhưng tôi dường như không thể tìm thấy một ví dụ.


2
Điều gì nếu có 2 hoặc nhiều đường dẫn từ nguồn đến đích?
ypercubeᵀᴹ

2
Vâng, tôi lo ngại về nhiều đường dẫn tiềm năng và cả về một đường dẫn dài hơn 2 bước. Ngoài ra, các khóa bao gồm nhiều hơn một cột. Những kịch bản đó sẽ ném cờ lê vào bất kỳ giải pháp tự động nào.
Aaron Bertrand

Lưu ý rằng thậm chí một khóa ngoại duy nhất giữa hai bảng sẽ cho phép 2 hoặc nhiều đường dẫn (thực tế là không giới hạn số lượng đường dẫn có độ dài tùy ý). Xem xét truy vấn "tìm tất cả các mục đã được đặt ít nhất một lần theo cùng thứ tự với mục X". Bạn sẽ cần phải tham gia OrderItemsvới Ordersvà trở lại với OrderItems.
ypercubeᵀᴹ

2
@ypercube Đúng vậy, chính xác thì "con đường tối ưu" nghĩa là gì?
Aaron Bertrand

"Đường dẫn THAM GIA tối ưu" có nghĩa là "chuỗi liên kết ngắn nhất sẽ tham gia bảng Target vào bảng Nguồn." Nếu T1 được tham chiếu trong T2 và T3, thì T2 được tham chiếu trong T4 và T3 được tham chiếu trong T4. Đường dẫn tối ưu từ T1 đến T3 là T1, T2, T3. Đường dẫn T1, T2, T4, T3 sẽ không tối ưu vì nó dài hơn.
Ẩn dụ

Câu trả lời:


4

Tôi đã có một kịch bản thực hiện một phiên bản thô sơ của truyền tải khóa ngoại. Tôi đã điều chỉnh nó một cách nhanh chóng (xem bên dưới) và bạn có thể sử dụng nó như một điểm khởi đầu.

Đưa ra một bảng mục tiêu, tập lệnh cố gắng in chuỗi tham gia cho đường dẫn ngắn nhất (hoặc một trong số chúng trong trường hợp quan hệ) cho tất cả các bảng nguồn có thể sao cho các khóa ngoại cột đơn có thể đi qua để đến bảng đích. Kịch bản có vẻ hoạt động tốt trên cơ sở dữ liệu với vài nghìn bảng và nhiều kết nối FK mà tôi đã thử.

Như những người khác đề cập trong các bình luận, bạn cần làm cho vấn đề này phức tạp hơn nếu bạn cần xử lý các khóa ngoại nhiều cột. Ngoài ra, xin lưu ý rằng đây không phải là mã được kiểm tra đầy đủ, sẵn sàng sản xuất. Hy vọng nó là điểm khởi đầu hữu ích nếu bạn quyết định xây dựng chức năng này!

-- 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

0

Bạn có thể đặt danh sách các khóa của bảng có hai trường TAB_NAME, KEY_NAME cho tất cả các bảng bạn muốn kết nối.

Ví dụ, cho bảng City

  • Thành phố | City_name
  • Thành phố | Quốc gia tên
  • Thành phố | Tỉnh_name
  • Thành phố | Thành phố

tương tự như vậy ProvinceCountry.

Thu thập dữ liệu cho các bảng và đặt vào một bảng duy nhất (Ví dụ: bảng Siêu dữ liệu)

Bây giờ hãy phác thảo truy vấn như bên dưới

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

Điều này sẽ giúp bạn làm thế nào bạn có thể liên kết các bảng dựa trên các khóa khớp (cùng tên khóa)

Nếu bạn nghĩ tên khóa có thể không khớp, bạn có thể bao gồm trường khóa thay thế và thử sử dụng nó trong điều kiện vị trí.


Lưu ý rằng người hỏi muốn sử dụng các sysbảng hiện có trong SQL Server mô tả các cột trong bảng, cách các bảng được liên kết với nhau, v.v ... Tất cả những điều đó đã tồn tại. Xây dựng các bảng của riêng bạn xác định cấu trúc bảng của bạn để đáp ứng một nhu cầu cụ thể có thể là vị trí dự phòng, nhưng câu trả lời ưa thích sẽ sử dụng những gì đã tồn tại, như câu trả lời được chấp nhận .
RDFozz
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.