Sql-Server

合併同一張表中的兩條記錄,保持外鍵關係不變

  • April 3, 2015

我有一個名為 SITES 的表,它有三列,可以說它看起來像這樣:

ID  Name        Path
1   Google      http://www.google.com/
2   Microsoft   http://www.microsoft.com/

我還有一個名為 Logs 的相關表,如下所示:

ID  SiteID   LogData
1   1        --data--
2   1        --more--
3   2        --other--
...

SITES 表既由系統使用者填充,有時也由一些批處理程序填充。在批處理過程中,我並不總是有可用的“名稱”,因此在 SITES 中創建了一個看起來像這樣的記錄(當按路徑搜尋時沒有結果),因為批處理過程最感興趣的是在日誌表。

ID  Name        Path
99              http://arstechnica.com/

問題情況是這樣出現的:

  1. 使用者創建了新的 SITE,但沒有輸入路徑(不知道,或者在某些情況下它還不存在),因此 SITES 表如下所示:
ID  Name        Path
1   Google      http://www.google.com/
2   Microsoft   http://www.microsoft.com/
3   Yahoo

2)批處理來了,需要為http://yahoo.com添加一個日誌,通過Path搜尋SITES並沒有找到它,所以它自己製作,結果是這樣的:

ID  Name        Path
1   Google      http://www.google.com/
2   Microsoft   http://www.microsoft.com/
3   Yahoo
4               http://yahoo.com

我的問題是,如何合併記錄 3 和記錄 4,同時保留兩個記錄的引用完整性?假設為了爭論,每條記錄在 Log 表以及其他一些表中都有幾條相關記錄。

我知道這將是辨識需要更新的記錄的手動過程,因此讓任何解決方案都假設我已經審核了列表並找到了所有“重複”記錄。

這是一個非常直接的數據清理任務。您的解決方案很好(為了安全起見,我建議使用如下的事務構造)——我採用了相同的方法,只是我使用了基於集合的操作,因此一旦你想出它就可以擴大規模和自動化一種比我提供的基本算法更好的重複檢測算法。

/* Test setup */
CREATE TABLE [dbo].[Sites]
(
   Id int NOT NULL PRIMARY KEY,
   Name nvarchar(50) NULL,
   Path nvarchar(100) NULL
);

CREATE TABLE [dbo].[Logs]
(
   Id int NOT NULL PRIMARY KEY,
   SiteId int NOT NULL FOREIGN KEY REFERENCES [dbo].[Sites](Id),
   LogData nvarchar(MAX) NULL
);

INSERT INTO [dbo].[Sites](Id, Name, Path)
   VALUES
       (1, N'Google', N'http://www.google.com'),
       (2, N'Microsoft', N'http://www.microsoft.com'),
       (3, N'Yahoo', NULL),
       (4, NULL, N'http://www.yahoo.com');

INSERT INTO [dbo].[Logs](Id, SiteId)
   VALUES (1, 1), (2, 1), (3, 2), (4, 3), (5, 3), (6, 4);


/* Identify potential duplicates (note: very rough) */
SELECT
   s1.Id AS SourceId, s1.Name AS SourceName, s1.Path AS SourcePath,
   s2.Id AS DuplId, s2.Name AS DuplName, s2.Path AS DuplPath
   FROM [dbo].[Sites] s1
   INNER JOIN [dbo].[Sites] s2 ON
       (s2.Path LIKE (N'%' + s1.Name + N'%')) AND
       (s2.Id > s1.Id);


/* Merge duplicates */
DECLARE @duplicates table
(
   SourceId int NOT NULL,
   TargetId int NOT NULL,
   PRIMARY KEY (SourceId, TargetId),
   CHECK (SourceId != TargetId)
);

INSERT INTO @duplicates(SourceId, TargetId)
   VALUES ; /* Edit me! */


SET XACT_ABORT ON;

BEGIN TRANSACTION;

   UPDATE l
       SET SiteId = d.TargetId
       FROM @duplicates d
       INNER JOIN [dbo].[Logs] l ON l.SiteId = d.SourceId;

   UPDATE st
       SET
           Name = COALESCE(ss.Name, st.Name),
           Path = COALESCE(ss.Path, st.Path)
       FROM @duplicates d
       INNER JOIN [dbo].[Sites] ss ON ss.Id = d.SourceId
       INNER JOIN [dbo].[Sites] st ON st.Id = d.TargetId;

   DELETE s
       FROM @duplicates d
       INNER JOIN [dbo].[Sites] s ON s.Id = d.SourceId;

COMMIT TRANSACTION;


/* Validate the site attributes were merged correctly */
SELECT
   s.*
   FROM @duplicates d
   INNER JOIN [dbo].[Sites] s ON s.Id = d.TargetId;

所以,在 Jimbo 的建議下,我寫了一個快速查詢來做這件事。如果有人對以下查詢有更好的解決方案或改進,請進行編輯或發布新答案。

DECLARE @keeperID int;
DECLARE @gonerID int;
DECLARE @newPath nvarchar(max);

SET @keeperID = 3;
SET @gonerID = 4;
SET @newPath = (SELECT Path FROM [SITES] WHERE [ID] = @gonerID);

UPDATE [SITE]
SET [Path] = @newPath 
WHERE [ID] = @keeperID;

UPDATE [LOG]
SET [SiteID] = @keeperID
WHERE [SiteID] = @gonerID;

DELETE FROM [SITES] WHERE [ID] = @gonerID;

引用自:https://dba.stackexchange.com/questions/27635