Ở đây tôi điều chỉnh câu trả lời cũ của mình từ một câu hỏi SO tương tự Làm thế nào để tìm tất cả các sơ đồ con được kết nối của một đồ thị không xác định .
Biến thể này không sử dụng con trỏ hoặc vòng lặp rõ ràng, nhưng sử dụng một truy vấn đệ quy duy nhất.
Về cơ bản, nó xử lý dữ liệu dưới dạng các cạnh trong biểu đồ và đi qua đệ quy tất cả các cạnh của biểu đồ, dừng lại khi phát hiện vòng lặp. Sau đó, nó đặt tất cả các vòng tìm thấy trong các nhóm và cung cấp cho mỗi nhóm một số.
Xem các giải thích chi tiết về cách thức hoạt động dưới đây. Tôi khuyên bạn nên chạy truy vấn CTE-by-CTE và kiểm tra từng kết quả trung gian để hiểu những gì nó làm.
Dữ liệu mẫu
DECLARE @T TABLE (ID1 int NOT NULL, ID2 int NOT NULL);
INSERT INTO @T (ID1, ID2) VALUES
(10, 20),
(10, 30),
(30, 30),
(10, 40),
(50, 70),
(60, 50),
(70, 70),
(4457745, 255714),
(4457745,2540222),
(2540222,4457745),
( 255714,4457745);
Truy vấn
WITH
CTE_Ids
AS
(
SELECT ID1 AS ID
FROM @T
UNION
SELECT ID2 AS ID
FROM @T
)
,CTE_Pairs
AS
(
SELECT ID1, ID2
FROM @T
WHERE ID1 <> ID2
UNION
SELECT ID2 AS ID1, ID1 AS ID2
FROM @T
WHERE ID1 <> ID2
)
,CTE_Recursive
AS
(
SELECT
CAST(CTE_Ids.ID AS varchar(8000)) AS AnchorID
,ID1
,ID2
,CAST(
',' + CAST(ID1 AS varchar(8000)) +
',' + CAST(ID2 AS varchar(8000)) +
',' AS varchar(8000)) AS IdPath
,1 AS Lvl
FROM
CTE_Pairs
INNER JOIN CTE_Ids ON CTE_Ids.ID = CTE_Pairs.ID1
UNION ALL
SELECT
CTE_Recursive.AnchorID
,CTE_Pairs.ID1
,CTE_Pairs.ID2
,CAST(
CTE_Recursive.IdPath +
CAST(CTE_Pairs.ID2 AS varchar(8000)) +
',' AS varchar(8000)) AS IdPath
,CTE_Recursive.Lvl + 1 AS Lvl
FROM
CTE_Pairs
INNER JOIN CTE_Recursive ON CTE_Recursive.ID2 = CTE_Pairs.ID1
WHERE
CTE_Recursive.IdPath NOT LIKE
CAST('%,' + CAST(CTE_Pairs.ID2 AS varchar(8000)) + ',%' AS varchar(8000))
)
,CTE_RecursionResult
AS
(
SELECT AnchorID, ID1, ID2
FROM CTE_Recursive
)
,CTE_CleanResult
AS
(
SELECT AnchorID, ID1 AS ID
FROM CTE_RecursionResult
UNION
SELECT AnchorID, ID2 AS ID
FROM CTE_RecursionResult
)
SELECT
DENSE_RANK() OVER (ORDER BY CA_Data.XML_Value) AS [NewID]
,CTE_Ids.ID AS [OldID]
,CA_Data.XML_Value AS GroupMembers
FROM
CTE_Ids
CROSS APPLY
(
SELECT CAST(CTE_CleanResult.ID AS nvarchar(max)) + ','
FROM CTE_CleanResult
WHERE CTE_CleanResult.AnchorID = CTE_Ids.ID
ORDER BY CTE_CleanResult.ID FOR XML PATH(''), TYPE
) AS CA_XML(XML_Value)
CROSS APPLY
(
SELECT CA_XML.XML_Value.value('.', 'NVARCHAR(MAX)')
) AS CA_Data(XML_Value)
ORDER BY [NewID], [OldID];
Kết quả
+-------+---------+-------------------------+
| NewID | OldID | GroupMembers |
+-------+---------+-------------------------+
| 1 | 10 | 10,20,30,40, |
| 1 | 20 | 10,20,30,40, |
| 1 | 30 | 10,20,30,40, |
| 1 | 40 | 10,20,30,40, |
| 2 | 255714 | 255714,2540222,4457745, |
| 2 | 2540222 | 255714,2540222,4457745, |
| 2 | 4457745 | 255714,2540222,4457745, |
| 3 | 50 | 50,60,70, |
| 3 | 60 | 50,60,70, |
| 3 | 70 | 50,60,70, |
+-------+---------+-------------------------+
Làm thế nào nó hoạt động
CTE_Ids
CTE_Idsđưa ra danh sách tất cả các Định danh xuất hiện trong cả hai ID1và ID2cột. Vì chúng có thể xuất hiện theo bất kỳ thứ tự nào, UNIONcả hai cột cùng nhau. UNIONcũng loại bỏ bất kỳ bản sao.
+---------+
| ID |
+---------+
| 10 |
| 20 |
| 30 |
| 40 |
| 50 |
| 60 |
| 70 |
| 255714 |
| 2540222 |
| 4457745 |
+---------+
CTE_Pairs
CTE_Pairsđưa ra danh sách tất cả các cạnh của đồ thị theo cả hai hướng. Một lần nữa, UNIONđược sử dụng để loại bỏ bất kỳ bản sao.
+---------+---------+
| ID1 | ID2 |
+---------+---------+
| 10 | 20 |
| 10 | 30 |
| 10 | 40 |
| 20 | 10 |
| 30 | 10 |
| 40 | 10 |
| 50 | 60 |
| 50 | 70 |
| 60 | 50 |
| 70 | 50 |
| 255714 | 4457745 |
| 2540222 | 4457745 |
| 4457745 | 255714 |
| 4457745 | 2540222 |
+---------+---------+
CTE_Recursive
CTE_Recursivelà phần chính của truy vấn đi qua đệ quy biểu đồ bắt đầu từ mỗi Mã định danh duy nhất. Những hàng bắt đầu được sản xuất bởi phần đầu tiên của UNION ALL. Phần thứ hai của UNION ALLđệ quy tham gia vào chính nó liên kết ID2đến ID1. Vì chúng tôi đã tạo sẵn CTE_Pairsvới tất cả các cạnh được viết theo cả hai hướng, chúng tôi luôn có thể chỉ liên kết ID2đến ID1và chúng tôi sẽ nhận được tất cả các đường dẫn trong biểu đồ. Đồng thời, truy vấn được xây dựng IdPath- một chuỗi các Định danh được phân cách bằng dấu phẩy đã được duyệt qua cho đến nay. Nó được sử dụng trong WHEREbộ lọc:
CTE_Recursive.IdPath NOT LIKE
CAST('%,' + CAST(CTE_Pairs.ID2 AS varchar(8000)) + ',%' AS varchar(8000))
Ngay khi chúng ta bắt gặp Mã định danh đã được đưa vào Đường dẫn trước đó, đệ quy dừng lại khi danh sách các nút được kết nối đã hết.
AnchorIDlà Định danh bắt đầu cho đệ quy, nó sẽ được sử dụng sau để nhóm kết quả.
Lvlkhông thực sự được sử dụng, tôi bao gồm nó để hiểu rõ hơn về những gì đang xảy ra.
+----------+---------+---------+--------------------------+-----+
| AnchorID | ID1 | ID2 | IdPath | Lvl |
+----------+---------+---------+--------------------------+-----+
| 10 | 10 | 20 | ,10,20, | 1 |
| 10 | 10 | 30 | ,10,30, | 1 |
| 10 | 10 | 40 | ,10,40, | 1 |
| 20 | 20 | 10 | ,20,10, | 1 |
| 30 | 30 | 10 | ,30,10, | 1 |
| 40 | 40 | 10 | ,40,10, | 1 |
| 50 | 50 | 60 | ,50,60, | 1 |
| 50 | 50 | 70 | ,50,70, | 1 |
| 60 | 60 | 50 | ,60,50, | 1 |
| 70 | 70 | 50 | ,70,50, | 1 |
| 255714 | 255714 | 4457745 | ,255714,4457745, | 1 |
| 2540222 | 2540222 | 4457745 | ,2540222,4457745, | 1 |
| 4457745 | 4457745 | 255714 | ,4457745,255714, | 1 |
| 4457745 | 4457745 | 2540222 | ,4457745,2540222, | 1 |
| 2540222 | 4457745 | 255714 | ,2540222,4457745,255714, | 2 |
| 255714 | 4457745 | 2540222 | ,255714,4457745,2540222, | 2 |
| 70 | 50 | 60 | ,70,50,60, | 2 |
| 60 | 50 | 70 | ,60,50,70, | 2 |
| 40 | 10 | 20 | ,40,10,20, | 2 |
| 40 | 10 | 30 | ,40,10,30, | 2 |
| 30 | 10 | 20 | ,30,10,20, | 2 |
| 30 | 10 | 40 | ,30,10,40, | 2 |
| 20 | 10 | 30 | ,20,10,30, | 2 |
| 20 | 10 | 40 | ,20,10,40, | 2 |
+----------+---------+---------+--------------------------+-----+
CTE_CleanResult
CTE_CleanResultchỉ để lại các phần có liên quan từ CTE_Recursivevà một lần nữa hợp nhất cả hai ID1và ID2sử dụng UNION.
+----------+---------+
| AnchorID | ID |
+----------+---------+
| 10 | 10 |
| 10 | 20 |
| 10 | 30 |
| 10 | 40 |
| 20 | 10 |
| 20 | 20 |
| 20 | 30 |
| 20 | 40 |
| 2540222 | 255714 |
| 2540222 | 2540222 |
| 2540222 | 4457745 |
| 255714 | 255714 |
| 255714 | 2540222 |
| 255714 | 4457745 |
| 30 | 10 |
| 30 | 20 |
| 30 | 30 |
| 30 | 40 |
| 40 | 10 |
| 40 | 20 |
| 40 | 30 |
| 40 | 40 |
| 4457745 | 255714 |
| 4457745 | 2540222 |
| 4457745 | 4457745 |
| 50 | 50 |
| 50 | 60 |
| 50 | 70 |
| 60 | 50 |
| 60 | 60 |
| 60 | 70 |
| 70 | 50 |
| 70 | 60 |
| 70 | 70 |
+----------+---------+
CHỌN cuối cùng
Bây giờ chúng ta cần xây dựng một chuỗi các IDgiá trị được phân tách bằng dấu phẩy cho mỗi giá trị AnchorID.
CROSS APPLYvới FOR XMLnó
DENSE_RANK()tính toán các NewIDcon số cho mỗi AnchorID.
Truy vấn này xây dựng một đường dẫn đầy đủ cho mỗi định danh, điều này không thực sự cần thiết.
Có thể làm cho giải pháp tổng thể hiệu quả hơn bằng cách chạy truy vấn này một lần cho mỗi nhóm / sơ đồ con, trong một vòng lặp.
Chọn một Mã định danh bắt đầu (thêm WHERE ID = 10vào CTE_Idsvà CTE_Pairs), sau đó xóa khỏi bảng nguồn tất cả các mã định danh được gắn vào nhóm này. Lặp lại cho đến khi bàn lớn trống rỗng.