大量のデータの中からあるキーとなる値が重複するレコードを抽出したい時があります。
例えばキーとなる値が一意になるように作成したつもりのテストデータが、意図せずに重複している場合など…。
今回はSQLで重複しているレコードを抽出する方法の紹介です。
目次
サンプルデータの作成
重複するレコードを抽出するためのサンプルデータを作成します。
キーとなる値を保持する列として電話番号を持ち、電話番号の所有者を表す氏名の列も持つ電話番号リストテーブルを作成します。
テーブルのCREATEとデータのINSERT
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | -- テーブルの作成 CREATE TABLE TEL_LIST (   TEL NVARCHAR(20) NOT NULL,   FULL_NAME NVARCHAR(50) NOT NULL ); -- データの挿入 INSERT INTO TEL_LIST     (TEL, FULL_NAME) VALUES     ('09012345678', 'テスト 氏名1'),     ('09022222222', 'テスト 氏名2'),     ('09033333333', 'テスト 氏名3'),     ('09044444444', 'テスト 氏名4'),     ('09055555555', 'テスト 氏名5'),     ('09066666666', 'テスト 氏名6'),     ('09012345678', 'テスト 氏名7'),     ('09088888888', 'テスト 氏名8'); | 
データは「テスト 氏名1」と「テスト 氏名7」の電話番号を重複させておきます。
上記のTEL_LISTテーブルへのデータのインサートは、複数のレコードを1回のSQLで一括挿入する形式で記述しています。
1回のINSERTで複数行のレコードをテーブルに一括で挿入する方法については、以下の記事を参照してください。

重複レコードを抽出する
重複するレコードを抽出するにはGROUP BY句を使用します。
GROUP BY句にで一意となるキーを指定して取得します。
| 1 2 3 4 5 6 7 8 | -- 電話番号(TEL)をキーにグループ化 SELECT     TEL,     COUNT(TEL) FROM     TEL_LIST GROUP BY     TEL | 
上記のSQLを実行すると7件のレコードが取得できます。

COUNT関数で電話番号の件数を取得して「09012345678」のレコードが2件あることが確認します。
HAVING句を使用する方法
HAVING句を使用する場合はGROUP BY句でグループ化して、COUNT関数で件数を取得した結果が1より大きいレコード(2件以上あるレコード)をHAVING句の条件に指定します。
HAVING句を使用して重複するレコードを取得するSQLは以下のようになります。
| 1 2 3 4 5 6 7 8 9 10 | -- GROUP BYとHAVINGを使用して重複レコードを抽出 SELECT     TEL,     COUNT(TEL) FROM     TEL_LIST GROUP BY     TEL HAVING     COUNT(TEL) > 1 | 

HAVING句に指定した条件によって電話番号が「09012345678」の「テスト 氏名1」と「テスト 氏名7」の2件のレコードが対象になっているのがわかります。
サブクエリにする方法
重複しているレコードの取得はHAVING句を使わずにグループ化した結果をサブクエリとして、サブクエリに自分自身のテーブルを自己結合することで取得することもできます。
以下にGROUP BYの結果をサブクエリにして重複するレコードを取得するSQLを記載します。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | -- サブクエリで件数を取得してWHERE句で条件を指定 SELECT     TEL_LIST.TEL,     TEL_LIST.CNT FROM     (     SELECT         TEL,         COUNT(TEL) AS CNT     FROM         TEL_LIST     GROUP BY         TEL     ) AS TEL_LIST WHERE     TEL_LIST.CNT > 1 | 

上記のSQLではグループ化した結果をサブクエリにして(6~14行目)WHERE句の条件に件数が1より大きいレコード(2件以上あるレコード)を指定します。
自己結合して対象のレコードを全て取得
最後に取得した重複レコードの結果と自分自身のテーブル(TEL_LIST)を自己結合して電話番号が重複している人の氏名(FULL_NAME)を取得する例を記載しておきます。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | SELECT     TEL_LIST.TEL,     TEL_LIST.FULL_NAME FROM     -- HAVING句を使用した結果をサブクエリにする     (     -- GROUP BYとHAVINGを使用して重複レコードを抽出     SELECT         TEL,         COUNT(TEL) AS CNT     FROM         TEL_LIST     GROUP BY         TEL     HAVING         COUNT(TEL) > 1     ) AS TEL_CNT -- 自己結合 INNER JOIN     TEL_LIST ON     TEL_CNT.TEL = TEL_LIST.TEL | 
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | SELECT     TEL_LIST.TEL,     TEL_LIST.FULL_NAME FROM     -- サブクエリで件数を取得     (     SELECT         TEL,         COUNT(TEL) AS CNT     FROM         TEL_LIST     GROUP BY         TEL     ) AS TEL_CNT -- 自己結合 INNER JOIN     TEL_LIST ON     TEL_CNT.TEL = TEL_LIST.TEL AND     TEL_CNT.CNT > 1 | 
上記のSQLではグループ化したサブクエリの結果表に自分自身のテーブル(TEL_LIST)を電話番号(TEL)をキーにINNER JOINで内部結合しています。
自己結合することで重複している全てのレコードを結果表として取得できます。

自己結合した結果、「テスト 氏名1」と「テスト 氏名7」の2件のレコードが取得されます。
