web-dev-qa-db-ja.com

1対多の関係テーブル全体で重複するレコードを見つける

1対多の関係を持つ2つのテーブル、PeopleとAttributesがあるとします。 first_name、last_nameに基づいて重複を見つけようとしています。すべての属性が正確に一致する必要があります。

CREATE TABLE People (Id int, 
                 first_name varchar(100), 
                 last_name varchar(100));

CREATE TABLE Attributes (Id int, 
                     person_id int, 
                     field varchar(100),
                     field_value varchar(100));

INSERT INTO People VALUES (1, 'John', 'Smith');
INSERT INTO People VALUES (2, 'John', 'Smith');
INSERT INTO People VALUES (3, 'John', 'Smith');

INSERT INTO Attributes VALUES (1, 1, 'HairColor', 'Brown');
INSERT INTO Attributes VALUES (2, 1, 'EyeColor', 'Blue');
INSERT INTO Attributes VALUES (3, 2, 'HairColor', 'Brown');
INSERT INTO Attributes VALUES (4, 2, 'EyeColor', 'Blue');
INSERT INTO Attributes VALUES (5, 3, 'HairColor', 'Blonde');

それは私たちに与えます:

 id | first_name | last_name
----+------------+-----------
  1 | John       | Smith
  2 | John       | Smith
  3 | John       | Smith

 id | person_id |   field   | field_value
----+-----------+-----------+-------------
  1 |         1 | HairColor | Brown
  2 |         1 | EyeColor  | Blue
  3 |         2 | HairColor | Brown
  4 |         2 | EyeColor  | Blue
  5 |         3 | HairColor | Blonde

PeopleテーブルからID 1および2を返すクエリが必要です。 1つのテーブル内で重複を見つけることができます。

select first_name,last_name,count(*) from People 
    group by first_name,last_name having ( count(*) > 1 );

しかし、私は1対多のテーブルを結合し、両方のテーブル間で重複を検出することに問題があります。 1対多の関係を持つテーブル間で重複を検出するにはどうすればよいですか?

4
Bill

これを行う1つの方法( SQLfiddle を確認してください):

select 
    p1.id as id1, 
    p2.id as id2
from people p1
  join people p2
    on  p1.first_name = p2.first_name
    and p1.last_name = p2.last_name
    and p1.id < p2.id
where not exists
    ( select 1
      from 
      ( select * 
        from attributes a1
        where a1.person_id = p1.id
      union all
        select * 
        from attributes a2
        where a2.person_id = p2.id
      ) g
      group by field, field_value
      having count(*) <> 2
   ) ;

そしてもう一つ:

select 
    p1.id as id1, 
    p2.id as id2
from people p1
  join people p2
    on  p1.first_name = p2.first_name
    and p1.last_name = p2.last_name
    and p1.id < p2.id
where not exists
    ( ( select field, field_value
        from attributes a1
        where a1.person_id = p1.id
      union 
        select field, field_value
        from attributes a2
       where a2.person_id = p2.id
      ) 
    except
      ( select field, field_value
        from attributes a1
        where a1.person_id = p1.id
      intersect
        select field, field_value
        from attributes a2
        where a2.person_id = p2.id
      )
    ) ;

少なくともPostgresとSQL Serverでは、Intersectがexcept/minusよりも優先されます。安全のために、かっこを使用して優先順位を確認できます。

4
ypercubeᵀᴹ

T-SQL構文を使用したテイクを次に示します。本当に標準の構文が必要な場合は、LIMIT 1ではなくTOP 1を使用するように変更できます。これが役に立てば幸いですが、それは ypercubeがカバーしています のように見えます。

SELECT TOP 1
            MIN(p.Id) AS id1,
            MAX(a.person_id) AS id2
    FROM    dbo.People AS p
            CROSS JOIN dbo.Attributes AS a
    WHERE   p.Id = a.person_id
    GROUP BY p.first_name ,
            p.last_name ,
            a.field ,
            a.field_value
    HAVING  COUNT(*) = ( SELECT COUNT(a2.person_id)
                         FROM   dbo.Attributes AS a2
                         WHERE  a2.field = a.field
                                AND a2.field_value = a.field_value
                       );
3
Erik Darling

属性が多かれ少なかれ修正されており、属性リストが変更されたときにコードリリースを実行してもかまわない場合は、JOINとCOUNTを使用してそれらをアンロールすると、それも実行されます。

select
    p.Id,
    a1.field_value,
    a2.field_value
from People as p
left outer join Attributes as a1
    on a1.person_id = p.Id
    and a1.field = 'HairColor'
left outer join Attributes as a2
    on a2.person_id = p.Id
    and a2.field = 'EyeColor'
group by
    p.Id,
    a1.field_value,
    a2.field_value
having
    count(*) > 1;

効果的ですが、エレガントではありません。多くの人と属性を持っている場合は遅くなる可能性があります。

0
Michael Green