指定列の値が重複しているレコード単位から先頭1件のみを抽出する

صفحہ تخلیق تاریخ :

جو صفحہ آپ حالیہ منظر میں ہیں ، منتخب دکھائیں زبان کی معاونت نہیں کرتا ۔

通常重複レコードから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)
)

重複行は havingcount でチェックしています。group by は使わず外からチェック対象のキー列、ここでは ID 列を where で絞っています。

先頭1行を取得するための優先順位は order by で指定します。ここでは charindexlen で「指定文字を含むか」「文字列の長さ」を確認しています。 後は 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)
)