Menemukan semua gabungan yang diperlukan untuk secara terprogram bergabung dengan tabel


8

Diberikan SourceTable dan TargetTable, saya ingin secara terprogram membuat string dengan semua gabungan yang diperlukan.

Singkatnya, saya mencoba menemukan cara untuk membuat string seperti ini:

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

Saya memiliki kueri yang mengembalikan semua kunci asing untuk tabel yang diberikan, tetapi saya mengalami keterbatasan dalam mencoba menjalankan semua ini secara rekursif untuk menemukan jalur gabungan yang optimal dan membuat string.

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

Saya yakin ini telah dilakukan sebelumnya, tetapi sepertinya saya tidak dapat menemukan contoh.


2
Bagaimana jika ada 2 jalur atau lebih dari sumber ke target?
ypercubeᵀᴹ

2
Ya, saya akan khawatir tentang beberapa jalur potensial, dan juga tentang satu jalur yang lebih dari 2 langkah. Juga, kunci terdiri dari lebih dari satu kolom. Skenario-skenario itu semuanya akan melemparkan kunci pas dalam solusi otomatis apa pun.
Aaron Bertrand

Perhatikan bahwa bahkan satu kunci asing tunggal di antara dua tabel akan memungkinkan 2 jalur atau lebih (sebenarnya jumlah jalur tak terbatas dengan panjang sewenang-wenang). Pertimbangkan kueri "temukan semua item yang telah ditempatkan setidaknya satu kali dalam urutan yang sama dengan item X". Anda harus bergabung OrderItemsdengan Ordersdan kembali dengan OrderItems.
ypercubeᵀᴹ

2
@ ypercube Benar, juga, apa sebenarnya arti "jalur optimal"?
Aaron Bertrand

"Jalur JOIN Optimal" berarti "seri gabungan terpendek yang akan bergabung dengan tabel Target ke tabel Source." Jika T1 direferensikan dalam T2 dan T3, T2 dirujuk dalam T4 dan T3 direferensikan dalam T4. Jalur optimal dari T1 ke T3 adalah T1, T2, T3. Jalur T1, T2, T4, T3 tidak akan optimal karena lebih panjang.
Metafora

Jawaban:


4

Saya punya skrip yang melakukan versi dasar dari kunci asing. Saya mengadaptasinya dengan cepat (lihat di bawah), dan Anda mungkin dapat menggunakannya sebagai titik awal.

Diberikan tabel target, skrip mencoba untuk mencetak string bergabung untuk jalur terpendek (atau salah satu dari mereka dalam kasus ikatan) untuk semua tabel sumber yang mungkin sehingga kunci asing satu kolom dapat dilintasi untuk mencapai tabel target. Script tampaknya berfungsi dengan baik pada database dengan beberapa ribu tabel dan banyak koneksi FK yang saya coba.

Seperti yang disebutkan orang lain di komentar, Anda harus membuat ini lebih rumit jika Anda perlu menangani kunci asing multi-kolom. Perlu diketahui juga bahwa ini bukan kode yang siap produksi dan sudah teruji sepenuhnya. Semoga ini adalah titik awal yang bermanfaat jika Anda memutuskan untuk membangun fungsionalitas ini!

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

Anda dapat meletakkan daftar kunci tabel dengan dua bidang TAB_NAME, KEY_NAME untuk semua tabel yang ingin Anda sambungkan.

Contoh, untuk tabel City

  • City | City_name
  • City | Country_name
  • Kota | Nama_Anda
  • Kota | Kode Kota

demikian juga Provincedan Country.

Kumpulkan data untuk tabel dan masukkan ke dalam satu tabel (misalnya tabel Metadata)

Sekarang konsep permintaan seperti di bawah ini

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

Ini akan membantu Anda bagaimana Anda menautkan tabel berdasarkan kunci yang cocok (nama kunci yang sama)

Jika menurut Anda nama tombol mungkin tidak cocok, Anda dapat memasukkan bidang kunci alternatif dan mencoba menggunakannya dalam kondisi di mana.


Perhatikan bahwa penanya ingin menggunakan systabel yang ada di SQL Server yang menggambarkan kolom dalam tabel, bagaimana tabel dihubungkan bersama, dll. Semua yang sudah ada. Membangun tabel Anda sendiri yang menentukan struktur tabel Anda untuk memenuhi kebutuhan tertentu bisa menjadi posisi mundur, tetapi jawaban yang disukai akan menggunakan apa yang sudah ada, seperti jawaban yang diterima .
RDFozz
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.