DBAとして実行しているときに、重複するレコードがロードされているテーブルが少なくとも1つある場合があります。テーブルに主キー(ほとんどの場合、自動増分キー)がある場合でも、残りのフィールドの値が重複している可能性があります。
ただし、SQL Serverでは、これらの重複レコードを取り除くためのさまざまな方法が可能です(たとえば、CTE、SQLランク関数、Group Byを使用したサブクエリなど)。
ある時、インタビュー中に、テーブル内の重複するレコードを1つずつ残して削除する方法を尋ねられたことを覚えています。当時は答えられませんでしたが、とても興味深かったです。少し調べてみたところ、この問題を解決するための選択肢がたくさん見つかりました。
さて、数年後、「SQLテーブルの重複レコードを削除する方法」という質問に答えることを目的としたストアドプロシージャを紹介します。すべてのDBAは、これを使用するだけで、あまり心配することなくハウスキーピングを行うことができます。
ストアドプロシージャの作成:最初の考慮事項
使用するアカウントには、目的のデータベースにストアドプロシージャを作成するための十分な権限が必要です。
このストアドプロシージャを実行するアカウントには、ターゲットデータベーステーブルに対してSELECTおよびDELETE操作を実行するための十分な権限が必要です。
このストアドプロシージャは、主キー(またはUNIQUE制約)が定義されていないデータベーステーブルを対象としています。ただし、テーブルに主キーがある場合、ストアドプロシージャはそれらのフィールドを考慮しません。残りのフィールドに基づいてルックアップと削除を実行します(したがって、この場合は慎重に使用してください)。
ストアドプロシージャの使用方法 SQLで
この記事で利用可能なSPT-SQLコードをコピーして貼り付けます。 SPは3つのパラメータを想定しています:
@schemaName –該当する場合は、データベーステーブルスキーマの名前。そうでない場合– dboを使用します 。
@tableName –重複する値が格納されているデータベーステーブルの名前。
@displayOnly – 1に設定されている場合 、実際の重複レコードは削除されません 、ただし、代わりにのみ表示されます(存在する場合)。デフォルトでは、この値は 0に設定されています 実際の削除が行われることを意味します 重複が存在する場合。
SQLServerストアドプロシージャ実行テスト
ストアドプロシージャを示すために、2つの異なるテーブルを作成しました。1つは主キーなし、もう1つは主キーありです。これらのテーブルにいくつかのダミーレコードを挿入しました。ストアドプロシージャの実行前/実行後にどのような結果が得られるかを確認しましょう。
主キー付きのSQLテーブル
CREATE TABLE [dbo].[test](
[column1] [varchar](16) NOT NULL,
[column2] [varchar](16) NOT NULL,
[column3] [varchar](16) NOT NULL,
CONSTRAINT [PK_Test] PRIMARY KEY CLUSTERED
(
[column1] ASC,
[column2] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
SQLストアドプロシージャサンプルレコード
INSERT INTO test VALUES('A','A',1),('A','B',1),('A','C',1),('B','A',2),('B','B',3),('B','C',4)
表示のみでストアドプロシージャを実行する
EXEC DBA_DeleteDuplicates @schemaName = 'dbo',@tableName = 'test',@displayOnly = 1
column1とcolumn2は主キーを形成するため、重複は非主キー列(この場合はcolumn3)に対して評価されます。結果は正しいです。
表示のみでストアドプロシージャを実行する
EXEC DBA_DeleteDuplicates @schemaName = 'dbo',@tableName = 'test',@displayOnly = 0
> 重複したレコードはなくなりました。
ただし、レコードの最初の出現がカットされるものであるため、このアプローチには注意する必要があります。したがって、何らかの理由で特定のレコードを削除する必要がある場合は、特定のケースに個別に取り組む必要があります。
SQL主キーのないテーブル
CREATE TABLE [dbo].[duplicates](
[column1] [varchar](16) NOT NULL,
[column2] [varchar](16) NOT NULL,
[column3] [varchar](16) NOT NULL
) ON [PRIMARY]
GO
SQLストアドプロシージャサンプルレコード
INSERT INTO duplicates VALUES
('John','Smith','Y'),
('John','Smith','Y'),
('John','Smith','N'),
('Peter','Parker','N'),
('Bruce','Wayne','Y'),
('Steve','Rogers','Y'),
('Steve','Rogers','Y'),
('Tony','Stark','N')
表示のみでストアドプロシージャを実行する
EXEC DBA_DeleteDuplicates @schemaName = 'dbo',@tableName = 'duplicates',@displayOnly = 1
出力は正しいです。これらはテーブル内の重複レコードです。
表示のみでストアドプロシージャを実行する
EXEC DBA_DeleteDuplicates @schemaName = 'dbo',@tableName = 'duplicates',@displayOnly = 0
ストアドプロシージャは期待どおりに機能し、重複は正常にクリーンアップされました。
特別な場合 このストアドプロシージャのSQL
指定しているスキーマまたはテーブルがデータベース内に存在しない場合、ストアドプロシージャが通知し、スクリプトはその実行を終了します。
スキーマ名を空白のままにすると、スクリプトが通知して実行を終了します。
テーブル名を空白のままにすると、スクリプトが通知して実行を終了します。
重複がないテーブルに対してストアドプロシージャを実行し、@displayOnlyビットをアクティブ化した場合 、空の結果セットが表示されます。
SQL Serverストアドプロシージャ:完全なコード
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
-- =============================================
-- Author : Alejandro Cobar
-- Create date: 2021-06-01
-- Description: SP to delete duplicate rows in a table
-- =============================================
CREATE PROCEDURE DBA_DeleteDuplicates
@schemaName VARCHAR(128),
@tableName VARCHAR(128),
@displayOnly BIT = 0
AS
BEGIN
SET NOCOUNT ON;
IF LEN(@schemaName) = 0
BEGIN
PRINT 'You must specify the schema of the table!'
RETURN
END
IF LEN(@tableName) = 0
BEGIN
PRINT 'You must specify the name of the table!'
RETURN
END
IF EXISTS (SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = @schemaName AND TABLE_NAME = @tableName)
BEGIN
DECLARE @pkColumnName VARCHAR(128);
DECLARE @columnName VARCHAR(128);
DECLARE @sqlCommand VARCHAR(MAX);
DECLARE @columnsList VARCHAR(MAX);
DECLARE @pkColumnsList VARCHAR(MAX);
DECLARE @pkColumns TABLE(pkColumn VARCHAR(128));
DECLARE @limit INT;
INSERT INTO @pkColumns
SELECT K.COLUMN_NAME
FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS AS C
JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE AS K ON C.TABLE_NAME = K.TABLE_NAME AND C.CONSTRAINT_SCHEMA = K.CONSTRAINT_SCHEMA
WHERE C.CONSTRAINT_TYPE = 'PRIMARY KEY'
AND C.CONSTRAINT_SCHEMA = @schemaName AND C.TABLE_NAME = @tableName
IF((SELECT COUNT(*) FROM @pkColumns) > 0)
BEGIN
DECLARE pk_cursor CURSOR FOR
SELECT * FROM @pkColumns
OPEN pk_cursor
FETCH NEXT FROM pk_cursor INTO @pkColumnName
WHILE @@FETCH_STATUS = 0
BEGIN
SET @pkColumnsList = CONCAT(@pkColumnsList,'',@pkColumnName,',')
FETCH NEXT FROM pk_cursor INTO @pkColumnName
END
CLOSE pk_cursor
DEALLOCATE pk_cursor
SET @pkColumnsList = SUBSTRING(@pkColumnsList,1,LEN(@pkColumnsList)-1)
END
DECLARE columns_cursor CURSOR FOR
SELECT COLUMN_NAME
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = @schemaName AND TABLE_NAME = @tableName AND COLUMN_NAME NOT IN (SELECT pkColumn FROM @pkColumns)
ORDER BY ORDINAL_POSITION;
OPEN columns_cursor
FETCH NEXT FROM columns_cursor INTO @columnName
WHILE @@FETCH_STATUS = 0
BEGIN
SET @columnsList = CONCAT(@columnsList,'',@columnName,',')
FETCH NEXT FROM columns_cursor INTO @columnName
END
CLOSE columns_cursor
DEALLOCATE columns_cursor
SET @columnsList = SUBSTRING(@columnsList,1,LEN(@columnsList)-1)
IF((SELECT COUNT(*) FROM @pkColumns) > 0)
BEGIN
IF(CHARINDEX(',',@columnsList) = 0)
SET @limit = LEN(@columnsList)+1
ELSE
SET @limit = CHARINDEX(',',@columnsList)
SET @sqlCommand = CONCAT('WITH CTE (',@columnsList,',DuplicateCount',')
AS (SELECT ',@columnsList,',',
'ROW_NUMBER() OVER(PARTITION BY ',@columnsList,' ',
'ORDER BY ',SUBSTRING(@columnsList,1,@limit-1),') AS DuplicateCount
FROM [',@schemaName,'].[',@tableName,'])
')
IF @displayOnly = 0
SET @sqlCommand = CONCAT(@sqlCommand,'DELETE FROM CTE WHERE DuplicateCount > 1;')
IF @displayOnly = 1
SET @sqlCommand = CONCAT(@sqlCommand,'SELECT ',@columnsList,',MAX(DuplicateCount) AS DuplicateCount FROM CTE WHERE DuplicateCount > 1 GROUP BY ',@columnsList)
END
ELSE
BEGIN
SET @sqlCommand = CONCAT('WITH CTE (',@columnsList,',DuplicateCount',')
AS (SELECT ',@columnsList,',',
'ROW_NUMBER() OVER(PARTITION BY ',@columnsList,' ',
'ORDER BY ',SUBSTRING(@columnsList,1,CHARINDEX(',',@columnsList)-1),') AS DuplicateCount
FROM [',@schemaName,'].[',@tableName,'])
')
IF @displayOnly = 0
SET @sqlCommand = CONCAT(@sqlCommand,'DELETE FROM CTE WHERE DuplicateCount > 1;')
IF @displayOnly = 1
SET @sqlCommand = CONCAT(@sqlCommand,'SELECT * FROM CTE WHERE DuplicateCount > 1;')
END
EXEC (@sqlCommand)
END
ELSE
BEGIN
PRINT 'Table doesn't exist within this database!'
RETURN
END
END
GO
結論
SQLテーブル内の重複レコードを削除する方法がわからない場合は、このようなツールが役立ちます。すべてのDBAは、主キー(または一意の制約)を持たないデータベーステーブルがあるかどうかを確認できます。これにより、時間の経過とともに不要なレコードが蓄積される可能性があります(ストレージを浪費する可能性があります)。ストアドプロシージャをプラグアンドプレイするだけで、準備完了です。
もう少し進んで、特定のテーブルに重複がある場合に通知するアラートメカニズムを構築できます(もちろん、このツールを使用して少し自動化を実装した後)。これは非常に便利です。
DBAタスクに関連するものと同様に、本番環境でトリガーを引く前に、必ずサンドボックス環境ですべてをテストしてください。その際は、焦点を当てているテーブルのバックアップを必ず取ってください。