Pergunta

Qual é a melhor maneira de remover linhas duplicadas de um arquivo bastante grande? SQL Server mesa (ou seja,mais de 300.000 linhas)?

As linhas, é claro, não serão duplicatas perfeitas devido à existência do RowID campo de identidade.

Minha mesa

RowID int not null identity(1,1) primary key,
Col1 varchar(20) not null,
Col2 varchar(2048) not null,
Col3 tinyint not null
Foi útil?

Solução

Supondo que não haja nulos, você GROUP BY as colunas únicas, e SELECT o MIN (or MAX) RowId como a linha a ser mantida.Depois, basta deletar tudo que não tinha ID de linha:

DELETE FROM MyTable
LEFT OUTER JOIN (
   SELECT MIN(RowId) as RowId, Col1, Col2, Col3 
   FROM MyTable 
   GROUP BY Col1, Col2, Col3
) as KeepRows ON
   MyTable.RowId = KeepRows.RowId
WHERE
   KeepRows.RowId IS NULL

Caso você tenha um GUID em vez de um número inteiro, você pode substituir

MIN(RowId)

com

CONVERT(uniqueidentifier, MIN(CONVERT(char(36), MyGuidColumn)))

Outras dicas

Outra maneira possível de fazer isso é

; 

--Ensure that any immediately preceding statement is terminated with a semicolon above
WITH cte
     AS (SELECT ROW_NUMBER() OVER (PARTITION BY Col1, Col2, Col3 
                                       ORDER BY ( SELECT 0)) RN
         FROM   #MyTable)
DELETE FROM cte
WHERE  RN > 1;

estou usando ORDER BY (SELECT 0) acima, pois é arbitrário qual linha preservar em caso de empate.

Para preservar o mais recente em RowID ordem, por exemplo, você poderia usar ORDER BY RowID DESC

Planos de Execução

O plano de execução para isso geralmente é mais simples e eficiente do que o da resposta aceita, pois não requer auto-junção.

Execution Plans

No entanto, nem sempre é esse o caso.Um lugar onde o GROUP BY solução pode ser preferida são situações em que um agregado de hash seria escolhido em preferência a um agregado de fluxo.

O ROW_NUMBER solução sempre dará praticamente o mesmo plano, enquanto o GROUP BY a estratégia é mais flexível.

Execution Plans

Fatores que podem favorecer a abordagem agregada de hash seriam

  • Nenhum índice útil nas colunas de particionamento
  • relativamente menos grupos com relativamente mais duplicatas em cada grupo

Em versões extremas deste segundo caso (se houver poucos grupos com muitas duplicatas em cada um), também se poderia considerar simplesmente inserir as linhas para manter em uma nova tabela e então TRUNCATE-ing o original e copiá-los de volta para minimizar o registro em comparação com a exclusão de uma proporção muito alta de linhas.

Há um bom artigo sobre removendo duplicatas no site de suporte da Microsoft.É bastante conservador - eles fazem tudo em etapas separadas - mas deve funcionar bem em tabelas grandes.

Eu usei auto-junções para fazer isso no passado, embora provavelmente pudesse ser enfeitado com uma cláusula HAVING:

DELETE dupes
FROM MyTable dupes, MyTable fullTable
WHERE dupes.dupField = fullTable.dupField 
AND dupes.secondDupField = fullTable.secondDupField 
AND dupes.uniqueField > fullTable.uniqueField

A consulta a seguir é útil para excluir linhas duplicadas.A tabela deste exemplo tem ID como uma coluna de identidade e as colunas que possuem dados duplicados são Column1, Column2 e Column3.

DELETE FROM TableName
WHERE  ID NOT IN (SELECT MAX(ID)
                  FROM   TableName
                  GROUP  BY Column1,
                            Column2,
                            Column3
                  /*Even if ID is not null-able SQL Server treats MAX(ID) as potentially
                    nullable. Because of semantics of NOT IN (NULL) including the clause
                    below can simplify the plan*/
                  HAVING MAX(ID) IS NOT NULL) 

O script a seguir mostra o uso de GROUP BY, HAVING, ORDER BY em uma consulta e retorna os resultados com coluna duplicada e sua contagem.

SELECT YourColumnName,
       COUNT(*) TotalCount
FROM   YourTableName
GROUP  BY YourColumnName
HAVING COUNT(*) > 1
ORDER  BY COUNT(*) DESC 
delete t1
from table t1, table t2
where t1.columnA = t2.columnA
and t1.rowid>t2.rowid

Postgres:

delete
from table t1
using table t2
where t1.columnA = t2.columnA
and t1.rowid > t2.rowid
DELETE LU 
FROM   (SELECT *, 
               Row_number() 
                 OVER ( 
                   partition BY col1, col1, col3 
                   ORDER BY rowid DESC) [Row] 
        FROM   mytable) LU 
WHERE  [row] > 1 

Isso excluirá as linhas duplicadas, exceto a primeira linha

DELETE
FROM
    Mytable
WHERE
    RowID NOT IN (
        SELECT
            MIN(RowID)
        FROM
            Mytable
        GROUP BY
            Col1,
            Col2,
            Col3
    )

Referir (http://www.codeproject.com/Articles/157977/Remove-Duplicate-Rows-from-a-Table-in-SQL-Server)

Eu preferiria CTE para excluir linhas duplicadas da tabela do SQL Server

recomendo fortemente seguir este artigo ::http://codaffection.com/sql-server-article/delete-duplicate-rows-in-sql-server/

mantendo o original

WITH CTE AS
(
SELECT *,ROW_NUMBER() OVER (PARTITION BY col1,col2,col3 ORDER BY col1,col2,col3) AS RN
FROM MyTable
)

DELETE FROM CTE WHERE RN<>1

sem manter o original

WITH CTE AS
(SELECT *,R=RANK() OVER (ORDER BY col1,col2,col3)
FROM MyTable)
 
DELETE CTE
WHERE R IN (SELECT R FROM CTE GROUP BY R HAVING COUNT(*)>1)

Rápido e sujo para excluir linhas duplicadas exatas (para tabelas pequenas):

select  distinct * into t2 from t1;
delete from t1;
insert into t1 select *  from t2;
drop table t2;

Eu prefiro a solução subquery\havendo count(*) > 1 à junção interna porque achei mais fácil de ler e foi muito fácil transformá-la em uma instrução SELECT para verificar o que seria excluído antes de executá-la.

--DELETE FROM table1 
--WHERE id IN ( 
     SELECT MIN(id) FROM table1 
     GROUP BY col1, col2, col3 
     -- could add a WHERE clause here to further filter
     HAVING count(*) > 1
--)

Para buscar linhas duplicadas:

SELECT
name, email, COUNT(*)
FROM 
users
GROUP BY
name, email
HAVING COUNT(*) > 1

Para excluir as linhas duplicadas:

DELETE users 
WHERE rowid NOT IN 
(SELECT MIN(rowid)
FROM users
GROUP BY name, email);      
SELECT  DISTINCT *
      INTO tempdb.dbo.tmpTable
FROM myTable

TRUNCATE TABLE myTable
INSERT INTO myTable SELECT * FROM tempdb.dbo.tmpTable
DROP TABLE tempdb.dbo.tmpTable

Pensei em compartilhar minha solução, pois ela funciona em circunstâncias especiais.No meu caso, a tabela com valores duplicados não possuía chave estrangeira (porque os valores foram duplicados de outro banco de dados).

begin transaction
-- create temp table with identical structure as source table
Select * Into #temp From tableName Where 1 = 2

-- insert distinct values into temp
insert into #temp 
select distinct * 
from  tableName

-- delete from source
delete from tableName 

-- insert into source from temp
insert into tableName 
select * 
from #temp

rollback transaction
-- if this works, change rollback to commit and execute again to keep you changes!!

PS:quando trabalho em coisas assim eu sempre uso uma transação, isso não só garante que tudo seja executado como um todo, mas também me permite testar sem arriscar nada.Mas é claro que você deve fazer um backup de qualquer maneira, só para ter certeza...

Usando CTE.A ideia é juntar uma ou mais colunas que formem um registro duplicado e depois remover o que quiser:

;with cte as (
    select 
        min(PrimaryKey) as PrimaryKey
        UniqueColumn1,
        UniqueColumn2
    from dbo.DuplicatesTable 
    group by
        UniqueColumn1, UniqueColumn1
    having count(*) > 1
)
delete d
from dbo.DuplicatesTable d 
inner join cte on 
    d.PrimaryKey > cte.PrimaryKey and
    d.UniqueColumn1 = cte.UniqueColumn1 and 
    d.UniqueColumn2 = cte.UniqueColumn2;

Ainda outra solução fácil pode ser encontrada no link colado aqui.Este é fácil de entender e parece ser eficaz para a maioria dos problemas semelhantes.É para SQL Server, mas o conceito usado é mais do que aceitável.

Aqui estão as partes relevantes da página vinculada:

Considere estes dados:

EMPLOYEE_ID ATTENDANCE_DATE
A001    2011-01-01
A001    2011-01-01
A002    2011-01-01
A002    2011-01-01
A002    2011-01-01
A003    2011-01-01

Então, como podemos excluir esses dados duplicados?

Primeiro, insira uma coluna de identidade nessa tabela usando o seguinte código:

ALTER TABLE dbo.ATTENDANCE ADD AUTOID INT IDENTITY(1,1)  

Use o seguinte código para resolvê-lo:

DELETE FROM dbo.ATTENDANCE WHERE AUTOID NOT IN (SELECT MIN(AUTOID) _
    FROM dbo.ATTENDANCE GROUP BY EMPLOYEE_ID,ATTENDANCE_DATE) 

Esta consulta mostrou um desempenho muito bom para mim:

DELETE tbl
FROM
    MyTable tbl
WHERE
    EXISTS (
        SELECT
            *
        FROM
            MyTable tbl2
        WHERE
            tbl2.SameValue = tbl.SameValue
        AND tbl.IdUniqueValue < tbl2.IdUniqueValue
    )

ele excluiu 1 milhão de linhas em pouco mais de 30 segundos de uma tabela de 2 milhões (50% de duplicatas)

Aqui está outro bom artigo sobre removendo duplicatas.

Ele discute por que é difícil:"SQL é baseado na álgebra relacional e duplicatas não podem ocorrer na álgebra relacional, porque duplicatas não são permitidas em um conjunto."

A solução da tabela temporária e dois exemplos de MySQL.

No futuro, você irá evitá-lo no nível do banco de dados ou na perspectiva do aplicativo.Eu sugeriria o nível do banco de dados porque seu banco de dados deve ser responsável por manter a integridade referencial, os desenvolvedores só causarão problemas;)

Ah com certeza.Use uma tabela temporária.Se você deseja uma declaração única e de baixo desempenho que "funcione", você pode usar:

DELETE FROM MyTable WHERE NOT RowID IN
    (SELECT 
        (SELECT TOP 1 RowID FROM MyTable mt2 
        WHERE mt2.Col1 = mt.Col1 
        AND mt2.Col2 = mt.Col2 
        AND mt2.Col3 = mt.Col3) 
    FROM MyTable mt)

Basicamente, para cada linha da tabela, a subseleção encontra o RowID superior de todas as linhas que são exatamente iguais à linha em consideração.Então você acaba com uma lista de RowIDs que representam as linhas "originais" não duplicadas.

Eu tinha uma tabela onde precisava preservar linhas não duplicadas.Não tenho certeza da velocidade ou eficiência.

DELETE FROM myTable WHERE RowID IN (
  SELECT MIN(RowID) AS IDNo FROM myTable
  GROUP BY Col1, Col2, Col3
  HAVING COUNT(*) = 2 )

A outra maneira é Crie um novo tabela com os mesmos campos e com Índice Único.Então mover todos os dados da tabela antiga para a nova tabela.Ignorar automaticamente o SQL SERVER (há também uma opção sobre o que fazer se houver um valor duplicado:ignorar, interromper ou sth) valores duplicados.Portanto, temos a mesma tabela sem linhas duplicadas. Se você não quiser o Índice Único, após a transferência dos dados você pode descartá-lo.

Especialmente para mesas maiores você pode usar DTS (pacote SSIS para importar/exportar dados) para transferir todos os dados rapidamente para sua nova tabela indexada exclusivamente.Para 7 milhões de linhas, leva apenas alguns minutos.

Usa isto

WITH tblTemp as
(
SELECT ROW_NUMBER() Over(PARTITION BY Name,Department ORDER BY Name)
   As RowNumber,* FROM <table_name>
)
DELETE FROM tblTemp where RowNumber >1

Usando a consulta abaixo, podemos excluir registros duplicados com base em uma única coluna ou em várias colunas.a consulta abaixo está sendo excluída com base em duas colunas.o nome da tabela é: testing e nomes de colunas empno,empname

DELETE FROM testing WHERE empno not IN (SELECT empno FROM (SELECT empno, ROW_NUMBER() OVER (PARTITION BY empno ORDER BY empno) 
AS [ItemNumber] FROM testing) a WHERE ItemNumber > 1)
or empname not in
(select empname from (select empname,row_number() over(PARTITION BY empno ORDER BY empno) 
AS [ItemNumber] FROM testing) a WHERE ItemNumber > 1)
  1. Crie uma nova tabela em branco com a mesma estrutura

  2. Execute uma consulta como esta

    INSERT INTO tc_category1
    SELECT *
    FROM tc_category
    GROUP BY category_id, application_id
    HAVING count(*) > 1
    
  3. Então execute esta consulta

    INSERT INTO tc_category1
    SELECT *
    FROM tc_category
    GROUP BY category_id, application_id
    HAVING count(*) = 1
    

Esta é a maneira mais fácil de excluir registros duplicados

 DELETE FROM tblemp WHERE id IN 
 (
  SELECT MIN(id) FROM tblemp
   GROUP BY  title HAVING COUNT(id)>1
 )

http://askme.indianyouth.info/details/how-to-dumplicate-record-from-table-in-using-sql-105

Eu mencionaria essa abordagem, pois ela pode ser útil e funciona em todos os servidores SQL:Muitas vezes, há apenas uma ou duas duplicatas, e os IDs e a contagem de duplicatas são conhecidos.Nesse caso:

SET ROWCOUNT 1 -- or set to number of rows to be deleted
delete from myTable where RowId = DuplicatedID
SET ROWCOUNT 0

Do nível do aplicativo (infelizmente).Concordo que a maneira correta de evitar duplicação é no nível do banco de dados por meio do uso de um índice exclusivo, mas no SQL Server 2005, um índice pode ter apenas 900 bytes, e meu campo varchar(2048) acaba com isso.

Não sei quão bem seria o desempenho, mas acho que você poderia escrever um gatilho para impor isso, mesmo que não pudesse fazer isso diretamente com um índice.Algo como:

-- given a table stories(story_id int not null primary key, story varchar(max) not null)
CREATE TRIGGER prevent_plagiarism 
ON stories 
after INSERT, UPDATE 
AS 
    DECLARE @cnt AS INT 

    SELECT @cnt = Count(*) 
    FROM   stories 
           INNER JOIN inserted 
                   ON ( stories.story = inserted.story 
                        AND stories.story_id != inserted.story_id ) 

    IF @cnt > 0 
      BEGIN 
          RAISERROR('plagiarism detected',16,1) 

          ROLLBACK TRANSACTION 
      END 

Além disso, varchar(2048) parece suspeito para mim (algumas coisas na vida têm 2.048 bytes, mas é bastante incomum);realmente não deveria ser varchar (max)?

DELETE
FROM
    table_name T1
WHERE
    rowid > (
        SELECT
            min(rowid)
        FROM
            table_name T2
        WHERE
            T1.column_name = T2.column_name
    );
CREATE TABLE car(Id int identity(1,1), PersonId int, CarId int)

INSERT INTO car(PersonId,CarId)
VALUES(1,2),(1,3),(1,2),(2,4)

--SELECT * FROM car

;WITH CTE as(
SELECT ROW_NUMBER() over (PARTITION BY personid,carid order by personid,carid) as rn,Id,PersonID,CarId from car)

DELETE FROM car where Id in(SELECT Id FROM CTE WHERE rn>1)

Se você deseja visualizar as linhas que está prestes a remover e manter o controle sobre quais linhas duplicadas manter.Ver http://developer.azurewebsites.net/2014/09/better-sql-group-by-find-duplicate-data/

with MYCTE as (
  SELECT ROW_NUMBER() OVER (
    PARTITION BY DuplicateKey1
                ,DuplicateKey2 -- optional
    ORDER BY CreatedAt -- the first row among duplicates will be kept, other rows will be removed
  ) RN
  FROM MyTable
)
DELETE FROM MYCTE
WHERE RN > 1
DELETE 
FROM MyTable
WHERE NOT EXISTS (
              SELECT min(RowID)
              FROM Mytable
              WHERE (SELECT RowID 
                     FROM Mytable
                     GROUP BY Col1, Col2, Col3
                     ))
               );
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top