🧚 お知らせ!Beekeeper Studioは高速でモダンなオープンソースのデータベースGUIです ダウンロード
2026年1月7日 著者: Matthew Rathbone

SQLで重複を削除するとは、次のどちらかを意味します。DISTINCTでクエリ結果から重複行を隠すか、テーブルから余分なコピーを削除して1件ずつ残すかです。例はPostgreSQL、MySQL、SQLite、SQL Serverで動作します。

SQLで重複を削除する方法

SELECT DISTINCTは指定した列の組み合わせを1回ずつ返し、テーブル自体は変更しません。テーブルそのものをクリーンアップするには、重複グループごとに1行(通常はidが最小の行)を残して、それ以外をすべて削除します。

SELECT DISTINCT column_list FROM table_name;

DELETE FROM table_name
WHERE id NOT IN (
  SELECT id FROM (
    SELECT MIN(id) AS id FROM table_name GROUP BY column_list
  ) AS keepers
);

この例では、Maria Lopezが3回登録されています。

-- Input: customers
-- id | name        | email             | phone
-- ---+-------------+-------------------+---------
-- 1  | Maria Lopez | maria@example.com | 555-0142
-- 2  | Sam Patel   | sam@example.com   | 555-0178
-- 3  | Maria Lopez | maria@example.com | 555-0142
-- 4  | Lena Novak  | lena@example.com  | 555-0121
-- 5  | Maria Lopez | maria@example.com | 555-0142

SELECT DISTINCT name, email, phone
FROM customers
ORDER BY name;

-- Output:
-- name        | email             | phone
-- ------------+-------------------+---------
-- Lena Novak  | lena@example.com  | 555-0121
-- Maria Lopez | maria@example.com | 555-0142
-- Sam Patel   | sam@example.com   | 555-0178

DELETE FROM customers
WHERE id NOT IN (
  SELECT id FROM (
    SELECT MIN(id) AS id FROM customers GROUP BY name, email, phone
  ) AS keepers
);

-- Output (customers after the DELETE):
-- id | name        | email             | phone
-- ---+-------------+-------------------+---------
-- 1  | Maria Lopez | maria@example.com | 555-0142
-- 2  | Sam Patel   | sam@example.com   | 555-0178
-- 4  | Lena Novak  | lena@example.com  | 555-0121

知っておくべきルール

  • 2つの行を重複とみなす列を指定します。idや、created_atタイムスタンプのように常に異なる値は除外してください。指定した列のいずれかが異なる行はすべて残ります。
  • SELECT id FROM (...) AS keepersのラッパーはMySQL用です。これがないと、MySQLはDELETEがサブクエリで自身のテーブルを読むことを拒否し、「You can’t specify target table ‘customers’ for update in FROM clause」というエラーになります。
  • 削除する前に確認しましょう。まずGROUP BYまたはROW_NUMBER()のクエリを実行して削除対象を確認し、ロールバックできるトランザクション内で削除を行ってください。
  • テーブルがきれいになったら、ユニークインデックスで新たな重複を防ぎます。
CREATE UNIQUE INDEX customers_email_key ON customers (email);

INSERT INTO customers VALUES (6, 'Maria Lopez', 'maria@example.com', '555-0142');
-- ERROR: duplicate key value violates unique constraint "customers_email_key"

別の構文

SQL Serverのコードでは、CTEでコピーに番号を振り、CTE経由で削除する書き方をよく見かけます。これはSQL Serverでのみ可能です。元のcustomersテーブルの場合:

-- SQL Server
WITH numbered AS (
  SELECT *, ROW_NUMBER() OVER (
    PARTITION BY name, email, phone ORDER BY id
  ) AS row_num
  FROM customers
)
DELETE FROM numbered WHERE row_num > 1;

-- Output (customers after the DELETE):
-- id | name        | email             | phone
-- ---+-------------+-------------------+---------
-- 1  | Maria Lopez | maria@example.com | 555-0142
-- 2  | Sam Patel   | sam@example.com   | 555-0178
-- 4  | Lena Novak  | lena@example.com  | 555-0121

2つのクエリを行の重複なしで結合するには、重複を残すUNION ALLではなく、重複を除去するUNIONを使います。

SELECT 'apple' AS fruit UNION ALL SELECT 'apple';

-- Output:
-- fruit
-- -----
-- apple
-- apple

SELECT 'apple' AS fruit UNION SELECT 'apple';
-- Output: apple

重複はいつ削除すべきか?

結果の重複を隠すだけで済む場合もあれば、テーブル自体のクリーンアップが必要な場合もあります。確認から削除までの一般的な手順を紹介します。

各値を1回ずつ表示する

レポートやドロップダウン、列の中身をざっと確認したいときは、SELECT DISTINCTで結果から重複を除けます。テーブルはそのままです。

削除前にコピーの数を数える

重複を定義する列でグループ化し、HAVING COUNT(*) > 1でグループを絞り込みます。これで、何かを削除する前に、何が何回重複しているかがわかります。確認方法の詳細はSQLで重複を見つけるガイドをご覧ください。

残すコピーを選ぶ

ROW_NUMBER()は重複グループごとに行へ番号を振ります。OVER (...)内のORDER BYによって、どの行が1になって残るか(最も古い行、最も新しい行、最も情報が揃った行)が決まります。

余分なコピーを削除する

残す行が決まったら、それ以外を削除します。最も古い行ではなく最も新しい行を残すには、日付の新しい順に番号を振り、番号が1より大きい行をすべて削除します。

重複削除の実例

配送先のすべての国と配送業者

DISTINCTは選択した列の行全体に適用されるため、Canadaは配送業者ごとに1回表示されます。

-- Input: shipments
-- order_id | country | carrier
-- ---------+---------+--------
-- 1001     | Canada  | UPS
-- 1002     | Germany | DHL
-- 1003     | Canada  | FedEx
-- 1004     | Canada  | UPS
-- 1005     | Germany | DHL

SELECT DISTINCT country, carrier
FROM shipments
ORDER BY country, carrier;

-- Output:
-- country | carrier
-- --------+--------
-- Canada  | FedEx
-- Canada  | UPS
-- Germany | DHL

大文字・小文字だけが異なるメールアドレス

小文字化したメールでグループ化すると、Jon@Example.comとjon@example.comがコピーとして扱われます。(MySQLとSQL Serverはデフォルトで大文字・小文字を区別しませんが、LOWER()を使っても問題ありません。)

-- Input: accounts
-- id | email
-- ---+----------------------
-- 1  | Ana.Silva@example.com
-- 2  | jon@example.com
-- 3  | ana.silva@example.com
-- 4  | Jon@Example.com
-- 5  | mei@example.com

SELECT LOWER(email) AS email, COUNT(*) AS copies
FROM accounts
GROUP BY LOWER(email)
HAVING COUNT(*) > 1
ORDER BY email;

-- Output:
-- email                 | copies
-- ----------------------+-------
-- ana.silva@example.com | 2
-- jon@example.com       | 2

電話番号のあるコピーを残す

電話番号のある行を先に、次にid順で並べると、各グループで最も情報が揃った行が1番になります。

-- Input: contacts
-- id | email            | phone
-- ---+------------------+---------
-- 1  | ravi@example.com | NULL
-- 2  | ravi@example.com | 555-0110
-- 3  | ines@example.com | 555-0133
-- 4  | ines@example.com | NULL

SELECT
  id,
  email,
  phone,
  ROW_NUMBER() OVER (
    PARTITION BY email
    ORDER BY CASE WHEN phone IS NULL THEN 1 ELSE 0 END, id
  ) AS row_num
FROM contacts
ORDER BY email, row_num;

-- Output:
-- id | email            | phone    | row_num
-- ---+------------------+----------+--------
-- 3  | ines@example.com | 555-0133 | 1
-- 4  | ines@example.com | NULL     | 2
-- 2  | ravi@example.com | 555-0110 | 1
-- 1  | ravi@example.com | NULL     | 2

番号が2以上の行(id 1と4)を削除すると、メールごとに完全な連絡先が1件ずつ残ります。

idのないテーブルから完全なコピーを削除する

すべての列が同一のコピーでは、最小値を残すためのidがありません。重複を除いた行を新しいテーブルにコピーし、置き換えます。コピーではインデックスや制約は引き継がれないため、元のテーブルにあったものを追加し直してください。

-- Input: page_views
-- url      | viewed_at
-- ---------+--------------------
-- /pricing | 2026-10-01 09:00:00
-- /pricing | 2026-10-01 09:00:00
-- /docs    | 2026-10-01 09:05:00
-- /pricing | 2026-10-01 09:00:00

-- In SQL Server, use SELECT DISTINCT * INTO page_views_clean FROM page_views,
-- then EXEC sp_rename 'page_views_clean', 'page_views'
CREATE TABLE page_views_clean AS SELECT DISTINCT * FROM page_views;
DROP TABLE page_views;
ALTER TABLE page_views_clean RENAME TO page_views;

-- Output (page_views afterwards):
-- url      | viewed_at
-- ---------+--------------------
-- /pricing | 2026-10-01 09:00:00
-- /docs    | 2026-10-01 09:05:00

重複削除に関する関連記事

このブログの記事:

公式ドキュメント:

Beekeeper Studioは無料のオープンソースのデータベースGUIです

これまで使った中で最高のSQLクエリ&エディタツールです。データベースの管理に必要なものがすべて揃っています。 - ⭐⭐⭐⭐⭐ Mit

Beekeeper Studioは高速で直感的、そして使いやすいツールです。多数のデータベースに対応し、Windows、Mac、Linuxで快適に動作します。

BeekeeperのLinux版は100%フル機能。手抜きも機能の妥協も一切ありません。

Beekeeper Studio利用者の声

★★★★★
「Beekeeper Studioで以前のSQLワークフローを完全に置き換えました。高速で直感的で、データベース作業がまた楽しくなりました。」
— Alex K.、データベース開発者
★★★★★
「多くのデータベースGUIを試しましたが、Beekeeperは機能とシンプルさのバランスが完璧です。とにかくちゃんと動きます。」
— Sarah M.、フルスタックエンジニア