Как удалить дубли из таблицы одним оператором

В этом форуме обсуждаем общие вопросы и проблемы языка, решаем задачи на разных диалектах, делимся успехами и неудачами его применения.

Модератор: rozhnev

Unanonimized
Сообщения: 29
Зарегистрирован: Пн май 29, 2023 11:54 am

Re: Как удалить дубли из таблицы одним оператором

Сообщение Unanonimized »

TRust писал(а): Вт апр 22, 2025 11:13 amтаблица tab с заранее неизвестным количеством столбцов
Сложно на этой самой посгре...
Oracle

Код: Выделить всё

-- Лентяйка для генерации запроса по таблице, где многакалонкафф
-- WARN: поля LOB, long [raw], объекты и т.п. в любом случае потребуют особого 
-- внимания, потому фильтровать не стал - данные целее будут.
select q'[delete dept_dubl where (rowid, 1) in 
(select rowid , lag(1) over(partition by ]'
    || listagg(column_name,',') 
    || q'[ order by 1) k
  from dept_dubl)]'
       stmnt
  from all_tab_cols 
 where table_name = upper('dept_dubl')
 group by table_name
/
Если оборачивать вывод лентяйки в какой-нибудь execute immediate

Код: Выделить всё

begin
  for i in (<текст лентяйки>) loop
    execute immediate i.stmnt;
  end loop;
end;
/
тоже лень, то полученный код можно просто скопипастить:

Код: Выделить всё

SQL>
delete dept_dubl where (rowid, 1) in
(select rowid, lag(1) over(partition by DEPTNO,DNAME,LOC order by 1) k
  from dept_dubl)
/
6 rows deleted

SQL>
select *  from dept_dubl t
/
DEPTNO DNAME                                                    LOC
------ -------------------------------------------------------- ----------------------------------------------------
    10 ACCOUNTING                                               NEW YORK
    20 RESEARCH                                                 DALLAS
    30 SALES                                                    CHICAGO
    40 OPERATIONS                                               BOSTON
SQL>
rollback
/
Rollback complete
-- Если в таблице дублей больше чем уникальных записей, то, возможно, этот вариант поможет лучше:

Код: Выделить всё

SQL>
delete dept_dubl
 where rowid NOT in
   (select min(rowid)
      from dept_dubl t
     group by DEPTNO,DNAME,LOC)
/
6 rows deleted

SQL>
select *  from dept_dubl t
/
DEPTNO DNAME                                                    LOC
------ -------------------------------------------------------- ----------------------------------------------------
    10 ACCOUNTING                                               NEW YORK
    20 RESEARCH                                                 DALLAS
    30 SALES                                                    CHICAGO
    40 OPERATIONS                                               BOSTON
НО
Если табличка совсем большая, секционированная, лежит на кластере, содержит особые типы данных и т.п.,
то игрушки следует оставить детям и решать проблему всерьёз и комплексно.
Как минимум:
- определить и изолировать/пофиксить источник дублей
- по возможности прикрыть данные от появления новых дублей, НАПРИМЕР (см. оговорки выше, которые потребуют учета)

Код: Выделить всё

-- создаём НЕуникальный индекс
SQL> create index dept_dubl$dedup on dept_dubl(DEPTNO,DNAME,LOC) online
  2  /
Index created

-- и вешаем на него UK (можно и PK, если ситуация позволяет)
SQL> alter table dept_dubl add constraint dept_dubl#dedup primary key (DEPTNO,DNAME,LOC) using index dept_dubl$dedup [color=#FF0000]NOVALIDATE[/color]
  2  /
Table altered

-- Как оно?
SQL> insert into dept_dubl select * from dept_dubl;
insert into dept_dubl select * from dept_dubl

ORA-00001: unique constraint (GIS_MRG.DEPT_DUBL#DEDUP) violated
Индекс выполнит две задачи:
- с помощью constraint предотвратит появление новых дублей
- поможет ловкому ораклоиду избежать жирных сортировок при поиске и устранении дублей, съэкономив кучу времени

Ну а дальше проводить дедубликацию наиболее уместным в конкретной ситуации способом (коих довольно много).

ЗЫ: помянутый в топике "универсальный" способ, да еще опубликованный в какой-то книжке, суть игрушка даже не детсадовского - ясельного возраста, потому что в детском саду уже умеют пользоваться IDE, которые сами подставляют все атрибуты таблички, а взрослые дяди-счетоводы даже умеют генерировать код по словарю.
Аватара пользователя
SQL*Plus
Сообщения: 329
Зарегистрирован: Ср апр 20, 2022 1:09 pm

Re: Как удалить дубли из таблицы одним оператором

Сообщение SQL*Plus »

Unanonimized писал(а): Чт май 15, 2025 2:25 pm ЗЫ: помянутый в топике "универсальный" способ, да еще опубликованный в какой-то книжке, суть игрушка даже не детсадовского - ясельного возраста, потому что в детском саду уже умеют пользоваться IDE, которые сами подставляют все атрибуты таблички, а взрослые дяди-счетоводы даже умеют генерировать код по словарю.
1) Не в какой-то книжке, а в нашей книжке - первой русскоязычной книге по Oracle,
написанной в 1997 году и полученной из типографии в январе 1998 года :-)

2) Приведенная универсальная команда красива сама по себе.
Она не предлагается в качестве всеобщего рецепта.

3) В те давние времена таких IDE'й не было.
Поэтому, если было нужно, для генерации команд писали SELECT или программу на PL/SQL.

4) Придумайте сами еще одно инженерное возражение для счетовода! :-)
Аватара пользователя
SQL*Plus
Сообщения: 329
Зарегистрирован: Ср апр 20, 2022 1:09 pm

Re: Как удалить дубли из таблицы одним оператором

Сообщение SQL*Plus »

Unanonimized писал(а): Чт май 15, 2025 2:25 pm

Код: Выделить всё

SQL>
delete dept_dubl where (rowid, 1) in
(select rowid, lag(1) over(partition by DEPTNO,DNAME,LOC order by 1) k
  from dept_dubl)
/
6 rows deleted
Мне понравилась эта идея!

Результат выполнения внутреннего запроса:

Код: Выделить всё

SQL> select rowid, lag(1) over(partition by DEPTNO,DNAME,LOC order by 1) k
  2    from dept_dubl;

ROWID                       K
------------------ ----------
AAASK7AAAAAAJcJAAA           
AAASK7AAAAAAJcJAAB          1
AAASK7AAAAAAJcJAAC          1
AAASK7AAAAAAJcJAAD          1
AAASK7AAAAAAJcJAAE           
AAASK7AAAAAAJcJAAG          1
AAASK7AAAAAAJcJAAF          1
AAASK7AAAAAAJcJAAI           
AAASK7AAAAAAJcJAAH          1
AAASK7AAAAAAJcJAAJ           
10 rows selected. 
Нам нужно оставить (не удалять) строчки, не имеющие предшественников при данной сортировке.
Это будут те строки, у которых получится K IS NULL.

Эти строки будут удалены:

Код: Выделить всё

SQL> SELECT rowid, DD.* FROM dept_dubl DD
  2  where (rowid, 1) in
  3  (select rowid, lag(1) over(partition by DEPTNO,DNAME,LOC order by 1) k
  4    from dept_dubl);

ROWID                  DEPTNO DNAME          LOC          
------------------ ---------- -------------- -------------
AAASK7AAAAAAJcJAAB         10 ACCOUNTING     NEW YORK     
AAASK7AAAAAAJcJAAC         10 ACCOUNTING     NEW YORK     
AAASK7AAAAAAJcJAAD         10 ACCOUNTING     NEW YORK     
AAASK7AAAAAAJcJAAF         20 RESEARCH       DALLAS       
AAASK7AAAAAAJcJAAG         20 RESEARCH       DALLAS       
AAASK7AAAAAAJcJAAH         30 SALES          CHICAGO      
6 rows selected. 
Предполагаю, что если сразу отобрать только те строки, у который K =1 (или K IS NOT NULL),
тогда выборка (удаление) сработают быстрее:

Код: Выделить всё

SELECT rowid, DD.* FROM dept_dubl DD
where rowid in (
   SELECT rwi 
   FROM (select rowid AS rwi
              , lag(1) over(partition by DEPTNO,DNAME,LOC order by 1) k
         from dept_dubl)
   WHERE k = 1);
. . .
6 rows selected. 
Ответить