指定列の値が重複しているレコード単位から先頭1件のみを抽出する
The page you are currently viewing does not support the selected display language.
通常重複レコードから1件のみを抽出する場合、ぱっと頭に思いつくのは distinct
だと思いますが、
distinct
では重複列の指定とほかの取得したい列を同時に指定することはできません。
また、distinct
は重複していないレコードも抽出対象に含まれてしまうので今回の要件とは一致しません。
タイトルだけだとやりたいことがいまいちわからないかと思いますので、サンプルレコードを例にしてみます。 やりたいことは以下のような抽出結果です。
ID | Name |
---|---|
1 | 名前1 |
2 | 名前2 |
3 | 名前3 |
3 | 名前3@example.com |
4 | 名前4名前4@example.com |
4 | 名前4@example.com |
4 | 名前4@example.com |
これを以下のように取得します。
ID | Name |
---|---|
3 | 名前3@example.com |
4 | 名前4@example.com |
ID 1 と ID 2 は重複していないので取得対象外です。 ID 3 と ID 4 は重複しているのでその中から1件取得しています。ただ今回取得するのは DB 格納順の先頭レコードではなく、 さらに「Name に @ マークが含まれている」「Name の文字数が少ない」という条件で優先的に並べてその先頭1行が欲しいものとします。
上記の条件は以下のような SQL で取得することが可能です。 ただ、サブクエリなどを使用しておりパフォーマンスまでは確認していません。
select
*
from
(select *, row_number() over(order by ID asc) as RowNum from SampleTable) tableMain
where tableMain.RowNum =
(
select
top(1) RowNum
from
(select *, row_number() over(order by ID asc) as RowNum from SampleTable) tableSub
where
tableMain.ID = tableSub.ID
and exists (
select
*
from
SampleTable
where
tableSub.ID = ID
having
count(*) >= 2
)
order by
case when charindex(N'@', Name) >= 1 then 0 else 1 end, len(Name)
)
重複行は having
と count
でチェックしています。group by
は使わず外からチェック対象のキー列、ここでは ID
列を where
で絞っています。
先頭1行を取得するための優先順位は order by
で指定します。ここでは charindex
と len
で「指定文字を含むか」「文字列の長さ」を確認しています。
後は top
で各々の重複レコードの先頭1行を取得すればよいです。
本来これだけでいいのですが、「ID:4, Name:名前4@example.com」のように完全に同じ値を持つレコードが2つ以上いるとどうしても2件取得してしまいます。
そのため row_number
で行ごとに識別できるように付加し全く同じ値のレコードでも1件だけ取得できるようにしています。
もし完全に同じレコードが存在しないことが判明しているのであれば row_number
は付加せずに既存のレコードの値同士でチェックしたほうがパフォーマンスはいいかと思います。
重複チェック対象のキーが複数ある場合は各々のキー列の数を増やせばいいです。 例えば以下のレコードがあるとします。重複チェックする列は「ID1」「ID2」です。
ID1 | ID2 | Name |
---|---|---|
1 | 1 | 名前1 |
2 | 1 | 名前2 |
3 | 1 | 名前31 |
3 | 2 | 名前32 |
4 | 1 | 名前41 |
4 | 2 | 名前42 |
4 | 2 | 名前4@example.com |
5 | 1 | 名前555@example.com |
5 | 1 | 名前55@example.com |
5 | 2 | 名前5@example.com |
6 | 1 | 名前6 |
6 | 1 | 名前66@example.com |
6 | 2 | 名前6 |
6 | 2 | 名前6 |
取得結果は以下のようになります。
ID1 | ID2 | Name |
---|---|---|
4 | 2 | 名前4@example.com |
5 | 1 | 名前55@example.com |
6 | 1 | 名前66@example.com |
6 | 2 | 名前6 |
SQL は以下のようになります。キー列を指定していた部分をそのまま増やせばいいです。
select
*
from
(select *, row_number() over(order by ID1 asc, ID2 asc) as RowNum from SampleTable) tableMain
where tableMain.RowNum =
(
select
top(1) RowNum
from
(select *, row_number() over(order by ID1 asc, ID2 asc) as RowNum from SampleTable) tableSub
where
tableMain.ID1 = tableSub.ID1
and tableMain.ID2 = tableSub.ID2
and exists (
select
*
from
SampleTable
where
tableSub.ID1 = ID1
and tableSub.ID2 = ID2
having
count(*) >= 2
)
order by
case when charindex(N'@', Name) >= 1 then 0 else 1 end, len(Name)
)