sql >> データベース >  >> RDS >> Database

SQLテーブルの重複レコードを削除するためのストアドプロシージャ

    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タスクに関連するものと同様に、本番環境でトリガーを引く前に、必ずサンドボックス環境ですべてをテストしてください。その際は、焦点を当てているテーブルのバックアップを必ず取ってください。


    1. ORA-1620511.2.0.3へのアップグレード

    2. SQLクエリで(func())。*構文を使用して複数の関数評価を回避するにはどうすればよいですか?

    3. PostgreSQLはテーブル(フラグメント)の透過的な圧縮をサポートしていますか?

    4. 効率的なデータベースを管理するための10のヒントとコツ