Намиране на дублиращи се данни в Oracle

Имам таблица с 500 000+ записа и полета за ID, собствено име, фамилия и имейл адрес. Това, което се опитвам да направя, е да намеря редове, където и първото име, и фамилията са дубликати (тъй като в едно и също лице има два отделни идентификатора, имейл адреси или каквото и да е, те са в таблицата повече от веднъж). Мисля, че знам как да намеря дубликатите с помощта на GROUP BY, това е, което имам:

SELECT first_name, last_name, COUNT(*)
FROM person_table
GROUP BY first_name, last_name
HAVING COUNT(*) > 1

Проблемът е, че след това трябва да преместя целия ред с тези дублирани имена в друга таблица. Има ли начин да намерите дубликатите и да получите целия ред? Или поне да си вземем и документите за самоличност? Опитах да използвам самообединяване, но си върнах повече редове, отколкото бяха в таблицата в началото. Това би ли бил по-добър подход? Всяка помощ ще бъде високо оценена.


person Ben Hubbard    schedule 14.10.2011    source източник
comment
По-добре проверете дали това наистина са дубликати на едно и също лице. Няма един John Smith в света, нали знаеш :)   -  person ypercubeᵀᴹ    schedule 14.10.2011


Отговори (4)


Индекс на (first_name, last_name) или на (last_name, first_name) би помогнал:

SELECT t.*
FROM 
    person_table t
  JOIN      
      ( SELECT first_name, last_name
        FROM person_table
        GROUP BY first_name, last_name
        HAVING COUNT(*) > 1
      ) dup
    ON  dup.last_name = t.last_name
    AND dup.first_name = t.first_name

or:

SELECT t.*
FROM person_table t
WHERE EXISTS
      ( SELECT *
        FROM person_table dup
        WHERE dup.last_name = t.last_name
          AND dup.first_name = t.first_name
          AND dup.ID <> t.ID
      )
person ypercubeᵀᴹ    schedule 14.10.2011
comment
Това е присъединяването, което търсех. Толкова просто, че съм изумен, че не можах да го разбера. Добавен е и индекс, който помогна много. - person Ben Hubbard; 15.10.2011

Най-ефективният начин за премахване на дублиращи се редове е чрез самосъединяване:

DELETE FROM person_table a
 WHERE a.rowid > 
       ANY (SELECT b.rowid
              FROM person_table b
             WHERE a.first_name = b.first_name
               AND a.last_name  = b.last_name);

Това ще премахне всички дубликати, дори ако има повече от един дублиращ се ред.

Има повече за премахването на дубликати и различни методи тук: http://www.dba-oracle.com/t_delete_duplicate_table_rows.htm

Дано помогне...

РЕДАКТИРАНЕ: Според вашите коментари, ако искате да изберете всички, освен един от дубликатите, тогава

SELECT *
  FROM person_table a
 WHERE a.rowid > 
       ANY (SELECT b.rowid
              FROM person_table b
             WHERE a.first_name = b.first_name
               AND a.last_name  = b.last_name);
person Ollie    schedule 14.10.2011
comment
Това всъщност може да работи, ако го променя малко. Проблемът е, че не се опитвам да ги изтрия, а ги копирам в друга таблица. Ако избера вместо изтриване с това, ще получа всички освен първия ред от всяко дублирано име, нали? - person Ben Hubbard; 14.10.2011
comment
Добре, без притеснения, радвам се, че помогна. Между другото, използването на ROWID е най-бързият метод за достъп до таблица, по-бърз дори от използването на първичния ключ на таблицата. - person Ollie; 14.10.2011
comment
Трябваше да променя коментара си, мисля, че ми липсва един ред за всеки набор от дубликати. Съжалявам, че превключих така, но благодаря за обратната връзка. - person Ben Hubbard; 14.10.2011
comment
Да, изявлението такова, каквото е, ще изтрие всички дубликати освен един. Ако го промените на SELECT, той ще избере всички дубликати освен един. - person Ollie; 14.10.2011
comment
защо не 'a.rowid != ANY' ? Преди малко имах ситуация, когато 'a.rowid ›' се провали (не всички дубликати бяха изтрити). Промяната на „a.rowid != ANY“ помогна... - person rtbf; 24.03.2014

Това ще ви даде идентификатор, който искате да преместите/изтриете/и т.н. Имайте предвид, че не работи, ако count(*) > 2, тъй като получавате само 1 ID (можете да изпълните отново заявката си за тези случаи).

SELECT max(ID), first_name, last_name, COUNT(*)
FROM person_table
GROUP BY first_name, last_name
HAVING COUNT(*) > 1

Редактиране: Можете да използвате COLLECT, за да получите всички идентификатори наведнъж (но внимавайте, тъй като искате да преместите/изтриете само всички освен един)

person michael667    schedule 14.10.2011
comment
Благодаря, Майкъл. Бързо проследяване, разбрах, че така или иначе получавам само всяко име, което се дублира веднъж, където трябва да мога да получа всичките. Някои са били дублирани 40+ пъти (странно, знам). Има ли по-лесен начин да направите това от повторното изпълнение на заявката? - person Ben Hubbard; 14.10.2011
comment
Благодаря много, функцията за събиране ми дава точно това, от което имам нужда. - person Ben Hubbard; 14.10.2011

За да добавя друга опция, обикновено използвам тази за премахване на дубликати:

delete from person_table
where rowid in (select rid
                  from (select rowid rid, row_number() over
                         (partition by first_name,last_name order by rowid) rn
                          from person_table
                        )
                 where rn <> 1 )
person Aitor    schedule 16.10.2011