この記事では、データベーステーブルで重複行を検索する方法を示します。これは非常に一般的な初心者の質問です。基本的なテクニックは簡単です。また、「2列の重複」(#mysql IRCチャネルに関する最近の質問)を見つける方法など、いくつかのバリエーションも示します。
重複行を見つける方法
最初のステップは、行を別の行の複製にするものを正確に定義することです。ほとんどの場合、これは簡単です。一部の列で同じ値を持ちます。これをこの記事の実用的な定義と見なしますが、可能性があります。 「重複」の概念がより複雑な場合は、以下のクエリを変更する必要があります。
この記事では、次のサンプルデータを使用します。
最初の2行は同じです。 day
列の値なので、それらが重複していると思われる場合は、次のクエリでそれらを見つけます。クエリはGROUP BY
句を使用して、同じday
値を持つすべての行を1つの「グループ」に入れ、サイズをカウントします。グループ:
重複した行の数が1より大きい。重複した行のみを表示する場合は、
句(WHERE
句ではありません):
これが基本的な手法です。重複を含む列でグループ化し、複数の行を持つグループのみを表示します。
WHERE句を使用できないのはなぜですか?
A WHERE
句は、グループ化される前に行をフィルタリングします。HAVING
句は、グループ化後に行をフィルタリングします。そのため、<を使用することはできません。上記のクエリのdivid = "8c41817077">
句。
重複行を削除する方法
関連する質問は、「重複」行を削除する方法です。それらを見つけます。一般的なt不良データをクリーンアップするときに尋ねるのは、重複の1つを除くすべてを削除することです。これにより、テーブルに適切なインデックスと主キーを配置し、重複がテーブルに再び入るのを防ぐことができます。
繰り返しますが、最初のやるべきことは、あなたの定義が明確であることを確認することです。正確にどの行を保持しますか?最初の1つ?ある列の値が最大のもの?この記事では、「最初の」行、つまりid
列の値が最も小さい行を保持することを想定しています。つまり、1行おきに削除する必要があります。
おそらくこれを行う最も簡単な方法は、一時テーブルを使用することです。特にMySQLでは、テーブルからの選択と同じクエリでの更新に関していくつかの制限があります。 MySQLの更新ターゲットから選択する方法の記事で説明しているように、これらを回避できますが、これらの複雑さを回避し、一時テーブルを使用します。
タスクの正確な定義は次のとおりです。そのグループのid
の最小値を持つ行を除いて、重複するすべての行を削除します。したがって、グループ内に複数の行がある行だけでなく、保持する行も見つける必要があります。これは、MIN()
関数を使用して実行できます。一時テーブルを作成し、DELETE
を実行するために必要なデータを見つけるためのクエリを次に示します。
これでこのデータが得られたので、削除に進むことができます。 「悪い」行。これを行うには多くの方法があり、いくつかは他よりも優れています(SQLの多対1の問題に関する私の記事を参照)が、ここでも細かい点を避けて、で機能するはずの標準構文を示しますサブクエリをサポートするRDBMS:
RDBMSがサブクエリをサポートしていない場合、またはより効率的な場合は、複数テーブルの削除を行うことをお勧めします。この構文はシステムによって異なるため、システムのドキュメントを参照する必要があります。懸念がある場合は、作業中に他のユーザーがデータを変更しないように、トランザクションでこれらすべてを実行する必要がある場合もあります。
複数の列で重複を見つける方法
最近、誰かが#mysqlIRCチャネルでこれに似た質問をしました:
列が
b
とc
は、他の2つのテーブルb
とc
をリンクします。b
またはc
のいずれかで重複しているすべての行を検索します。
これが何を意味するのかを正確に理解することは困難でしたが、いくつかの会話の後で私はそれを理解しました:その人は列b
と
個別に。
上で示したように、いずれかの列で値が重複している行を見つけるのは非常に簡単です。その列でグループ化するだけです。 mnし、グループサイズを数えます。また、他の行と完全に重複している行全体を簡単に見つけることができます。必要な数の列でグループ化するだけです。ただし、b
の値が重複している行またはc
の値が重複している行を特定するのは困難です。次のサンプルテーブルを見てください。これは大まかにその人が説明したものです。
これで、このテーブルにいくつかの「重複」行があることが簡単にわかりますが、実際には2つの行に同じタプルがありません{b, c}
。そのため、これを解決するのは少し難しいです。
機能しないクエリ
2つの列でグループ化すると、グループ化の方法に応じてさまざまな結果が得られます。とカウントします。これは、IRCユーザーが困惑していた場所です。クエリによって重複が見つかる場合がありますが、他の重複は見つかりません。この人が試したことのいくつかを次に示します。
このクエリは、テーブル内のすべての行をCOUNT(*)
of 1、これは間違った動作のようですが、実際にはそうではありません。どうして? > 1
はCOUNT()
内にあるためです。見逃しがちですが、このクエリは実際には
と同じです。なぜですか? (b > 1)
はブール式であるためです。それはあなたが望んでいることではありません。必要な
もちろん、重複する{b, c}
タプルがないため、これはゼロ行を返します。その人は、HAVING
句とORおよびANDのその他の多くの組み合わせを試し、1つの列でグループ化し、他の列を数えるなどしました。
ただし、すべての重複は見つかりませんでした。私が最も苛立たしいと思うのは、それが部分的に機能し、それがほぼ正しいクエリであると人に思わせることです…おそらく別のバリエーションでそれが得られるでしょう…
実際、このタイプのクエリでは不可能です単純なGROUP BY
クエリ。どうしてこれなの?これは、1つの列でグループ化すると、他の列の値と同じように複数のグループに分散されるためです。これらの列で並べ替えることで、これを視覚的に確認できます。これは、グループ化が行うことです。まず、列b
で並べ替えて、それらがどのようにグループ化されているかを確認します。
a | b | c |
---|---|---|
7 | 1 | 1 |
8 | 1 | 2 |
9 | 1 | 3 |
10 | 2 | 1 |
11 | 2 | 2 |
12 | 2 | 3 |
13 | 3 | 1 |
14 | 3 | 2 |
15 | 3 | 3 |
列b
で(グループ化)注文すると、列c
はさまざまなグループに分散されているため、その人がやろうとしていたようにCOUNT(DISTINCT c)
でそれらを数えることはできません。 COUNT()
などの集計関数はグループ内でのみ動作し、他のグループに配置されている行にはアクセスできません。同様に、c
で注文すると、列b
の重複する値が異なるグループに分散されます。このクエリで目的の処理を実行することはできません。
いくつかの正しい解決策
おそらく最も簡単な解決策は、各列の重複を個別に見つけて次のように一緒に:
出力のwhat_col
列は、重複する値が見つかった列を示します。別のアプローチは、サブクエリ:
これは、おそらくUNION
アプローチよりもはるかに効率が低く、重複する値だけでなく、重複するすべての行が表示されます。さらに別のアプローチは、FROM
句でグループ化されたサブクエリに対して自己結合を行うことです。これは正しく書き込むのがより複雑ですが、一部の複雑なデータや効率のために必要になる場合があります。
これらのクエリはどれでも実行でき、他の方法もあると確信しています。 UNION
を使用できる場合は、おそらく最も簡単です。