2012-07-15 32 views
5

Aquí desea eliminar filas con el valor de una columna duplicada (Product) que luego se utilizará como clave principal .Eliminar duplicados sin clave principal

La columna es del tipo nvarchar y no queremos tener 2 filas para un producto. La base de datos es grande con alrededor de miles filas tenemos que eliminar.

Durante la consulta de todos los duplicados, queremos mantener el primer elemento y eliminar el segundo como el duplicado.

Aún no hay una clave principal, y queremos hacerlo después de esta actividad de eliminar duplicados. Entonces la columna Product podría ser nuestra clave principal.

La base de datos es SQL Server CE.

He probado varios métodos, y sobre todo conseguir de error similar al siguiente:

Se ha producido un error al analizar la consulta. [Línea de emergencia número = 2, línea de emergencia compensado = 1, Token por error = FROM]

Un método que he intentado:

DELETE FROM TblProducts 
FROM TblProducts w 
    INNER JOIN (
      SELECT Product 
      FROM TblProducts 
      GROUP BY Product 
      HAVING COUNT(*) > 1 
      )Dup ON w.Product = Dup.Product 

La forma preferida tratando de aprender y ajustar mi código con algo similar (no es correcto aún):

SELECT Product, COUNT(*) TotalCount 
FROM TblProducts 
GROUP BY Product 
HAVING COUNT(*) > 1 
ORDER BY COUNT(*) DESC 

-- 
;WITH cte -- These 3 lines are the lines I have more doubt on them 
    AS (SELECT ROW_NUMBER() OVER (PARTITION BY Product 
             ORDER BY (SELECT 0)) RN 
     FROM Word) 
DELETE FROM cte 
WHERE RN > 1 
+0

¿Qué tan grande es la base de datos. ¿Estamos hablando de millones de filas aquí? Miles de millones? –

+0

aproximadamente 200,000 registros con 3000 duplicados, no mucho: D – Sypress

+0

Cuando tiene dos registros con los mismos datos para Producto, pero datos diferentes en otras columnas, ¿cómo sabe cuál es el correcto para conservar? –

Respuesta

4

Si tiene dos registros DIFERENTES con la misma columna de Producto, puede SELECCIONAR los registros no deseados con algún criterio, p.

CREATE TABLE victims AS 
    SELECT MAX(entryDate) AS date, Product, COUNT(*) AS dups FROM ProductsTable WHERE ... 
    GROUP BY Product HAVING dups > 1; 

Luego puede hacer una ELIMINACIÓN DE JUNTA entre ProductTable y Víctimas.

O también puede seleccionar Producto solamente, y luego hacer un DELETE para alguna otra condición JOIN, por ejemplo, tener un CustomerId no válido, o EntryDate NULL, o cualquier otra cosa. Esto funciona si sabe que hay una y solo una copia válida del Producto, y todas las demás son reconocibles por los datos no válidos.

Supongamos que en su lugar tiene registros IDENTICOS (o tiene idénticos y no idénticos, o puede tener varios engaños para algún producto y no sabe cuál). Ejecuta exactamente la misma consulta. Luego, ejecuta una consulta SELECT en ProductsTable y SELECT DISTINCT todos los productos que coinciden con los códigos de producto para ser deducidos, agrupando por Producto y eligiendo una función agregada adecuada para todos los campos (si es idéntico, cualquier agregado debería hacerlo. De lo contrario, generalmente intento MAX o MIN).Esto "guardará" exactamente una fila para cada producto.

En ese punto, ejecuta DELETE JOIN y elimina todos los productos duplicados. Luego, simplemente vuelva a importar el subconjunto guardado y deducido en la tabla principal.

Por supuesto, entre DELETE JOIN y INSERT SELECT, tendrá la base de datos en un estado inestable, con todos los productos con al menos un duplicado simplemente desapareció.

Otra forma que debe trabajar en MySQL:

-- Create an empty table 
CREATE TABLE deduped AS SELECT * FROM ProductsTable WHERE false; 

CREATE UNIQUE INDEX deduped_ndx ON deduped(Product); 

-- DROP duplicate rows, Joe the Butcher's way 
INSERT IGNORE INTO deduped SELECT * FROM ProductsTable; 

ALTER TABLE ProductsTable RENAME TO ProductsBackup; 

ALTER TABLE deduped RENAME TO ProductsTable; 
-- TODO: Copy all indexes from ProductsTable on deduped. 

NOTA: la forma anterior NO FUNCIONA si desea distinguir "buenos" y "registros duplicados no válidos". Solo funciona si tiene registros redundantes DUPLICADOS, o si no le importa qué fila guarda y la tira!

EDITAR: Es decir que "duplicados" tienen campos no válidos. En ese caso se puede modificar el anterior con un truco de clasificación:

SELECT * FROM ProductsTable ORDER BY Product, FieldWhichShouldNotBeNULL IS NULL; 

Entonces, si usted tiene sólo una fila para el producto, todo muy bien, se pondrá seleccionado. Si tiene más, aquel para el cual (FieldWhichShouldNouldBeNull IS NULL) es FALSE (es decir, aquél en el que FieldWhichShouldNheBeverNull en realidad no es nulo como debería) se seleccionará primero y se insertará. Todos los demás rebotarán, silenciosamente debido a la cláusula IGNORE, en contra de la singularidad del Producto. No es una forma realmente bonita de hacerlo (¡y comprueba que no mezcle verdadero con falso en mi cláusula!), Pero debería funcionar.

EDITAR
en realidad más de una nueva respuesta

Esta es una tabla simple para ilustrar el problema

CREATE TABLE ProductTable (Product varchar(10), Description varchar(10)); 
INSERT INTO ProductTable VALUES ('CBPD10', 'C-Beam Prj'); 
INSERT INTO ProductTable VALUES ('CBPD11', 'C Proj Mk2'); 
INSERT INTO ProductTable VALUES ('CBPD12', 'C Proj Mk3'); 

No existe un índice todavía, y sin clave primaria. Todavía podríamos declarar el producto como clave principal.

Pero sucede algo malo. Entran dos nuevos registros, y ambos tienen una descripción NULL.

Sin embargo, el segundo es un producto válido ya que no sabíamos nada de CBPD14 hasta ahora, y por lo tanto, NO queremos perder este registro por completo. Nosotros do queremos deshacernos de la falsa CBPD10 sin embargo.

INSERT INTO ProductTable VALUES ('CBPD10', NULL); 
INSERT INTO ProductTable VALUES ('CBPD14', NULL); 

Un grosero Eliminar en ProductTable DONDE Descripción IS NULL está fuera de la cuestión, sería matar CBPD14 que no es un duplicado.

Así lo hacemos así. En primer lugar obtener la lista de duplicados:

SELECT Product, COUNT(*) AS Dups FROM ProductTable GROUP BY Product HAVING Dups > 1; 

asumimos que: "Hay por lo menos un buen registro para cada conjunto de registros en mal estado".

Comprobamos esta suposición al postular lo contrario y consultarlo. Si todo es copacetic, esperamos que esta consulta no devuelva nada.

SELECT Dups.Product FROM ProductTable 
RIGHT JOIN (SELECT Product, COUNT(*) AS Dups FROM ProductTable GROUP BY Product HAVING Dups > 1) AS Dups 
ON (ProductTable.Product = Dups.Product 
     AND ProductTable.Description IS NOT NULL) 
WHERE ProductTable.Description IS NULL; 

Para verificar más, inserto dos registros que representan este modo de falla; ahora espero que la consulta anterior devuelva el nuevo código.

INSERT INTO ProductTable VALUES ("AC5", NULL), ("AC5", NULL); 

Ahora la consulta "cheque" de hecho se regresa,

AC5 

Por lo tanto, la generación de Dups se ve bien.

Procedo ahora a eliminar todos los registros duplicados que no son válidos. Si hay registros duplicados y válidos, se mantendrán duplicados a menos que se encuentre alguna condición, distinguiendo entre ellos un registro "bueno" y declarando a todos los demás como "no válidos" (tal vez repitiendo el procedimiento con un campo diferente que la Descripción).

Pero ay, hay un problema. Actualmente, no puede eliminar de una tabla y seleccionar de la misma tabla en una subconsulta (http://dev.mysql.com/doc/refman/5.0/en/delete.html). Por lo tanto se necesita un poco de solución:

CREATE TEMPORARY TABLE Dups AS 
    SELECT Product, COUNT(*) AS Duplicates 
     FROM ProductTable GROUP BY Product HAVING Duplicates > 1; 

DELETE ProductTable FROM ProductTable JOIN Dups USING (Product) 
    WHERE Description IS NULL; 

Ahora bien, esto borrará todos los registros no válidos, siempre que aparecen en la tabla Dups.

Por lo tanto nuestro registro CBPD14 se deja intacto, ya que no aparece allí. El "buen" registro para CBPD10 quedará intacto porque no es cierto que su Descripción es NULL. Todos los demás - poof.

permítanme decir una vez más que si un registro tiene no registros válidos y aúnes un duplicado, a continuación, todas las copias de ese registro serán matados - no habrá sobrevivientes.

Para evitar esto, primero puede SELECCIONAR (utilizando la consulta anterior, la comprobación "que no debería devolver nada") las filas que representan este modo de falla en otra TABLA TEMPORAL, luego INSERTARlas nuevamente en la tabla principal después de la eliminación (usar transacciones podría estar en orden).

+0

Lo intentaré y dar una opinión sobre esto pronto, gracias – Sypress

+0

Amigo, estoy tratando basado en su enfoque, de ser posible proporcione la muestra 3-5 líneas de código basadas en lo que pensado y resumido. será apreciado. – Sypress

+1

Puede hacer. Incluiré un pequeño ejemplo para estar realmente seguro de haber entendido tu problema. Eliminar grandes cantidades de datos siempre me pone nervioso de alguna manera :-) – LSerni

-2

Prueba esto:

DELETE FROM TblProducts  
WHERE Product IN 
     (
    SELECT Product 
    FROM TblProducts 
    GROUP BY Product 
    HAVING COUNT(*) > 1) 

Esto adolece del defecto que borra TODOS los registros con un producto duplicado. Lo que probablemente quiera hacer es borrar todos menos uno de cada grupo de registros con un producto determinado. Podría valer la pena copiar todos los duplicados en una tabla separada primero, y luego eliminar de algún modo los duplicados de esa tabla, luego aplicar lo anterior y luego copiar los productos restantes en la tabla original.

+0

Lo intentaré y dar una opinión sobre esto pronto, gracias – Sypress

+0

¡Esta ejecución de esto es realmente lenta!, es cerca de media hora ... – Sypress

+2

Walter, ¿por qué incluso publicar el código si sabes que va a zap cada producto en la mesa que tiene un duplicado (incluido el que el operador necesita conservar)? Esperemos que Sypress lea el párrafo debajo del código antes de ejecutar O O tenga una copia de seguridad completa reciente ... – brian

1

crear una nueva tabla de secuencias de comandos por el antiguo y el cambio de nombre. También script todos los objetos (índices, etc.) de la tabla anterior a la nueva. Inserta los guardianes en la nueva mesa. Si su base de datos está en un modelo de recuperación simple o de registro masivo, esta operación se registrará mínimamente. Suelta la tabla anterior y luego cambia el nombre de la nueva al nombre anterior.

La ventaja de esto sobre un borrado será que el inserto se puede registrar mínimamente. Las eliminaciones hacen doble trabajo porque no solo se eliminan los datos, sino que la eliminación debe escribirse en el registro de transacciones. Para las tablas grandes, las inserciones mínimamente registradas serán mucho más rápidas que las eliminaciones.

1

Si no es tan grande y tiene un tiempo de inactividad, y usted tiene SQL Server Management Studio, se puede poner un campo de identidad en la mesa utilizando la interfaz gráfica de usuario. Ahora tiene la situación como su CTE, excepto que las filas mismas son realmente distintas. Así que ahora puede hacer lo siguiente

SELECT MIN(table_a.MyTempIDField) 
FROM 
table_a lhs 
join table_1 rhs 
on lhs.field1 = rhs.field1 
and lhs.field2 = rhs.field2 [etc] 
WHERE 
table_a.MyTempIDField <> table_b.MyTempIDField 
GROUP BY 
lhs.field1, rhs.field2 etc 

Esto le da todos los 'buenos' duplicados. Ahora puede ajustar esta consulta con una consulta DELETE FROM.

DELETE FROM lhs 
FROM table_a lhs 
join table_b rhs 
on lhs.field1 = rhs.field1 
and lhs.field2 = rhs.field2 [etc] 
WHERE 
lhs.MyTempIDField <> rhs.MyTempIDField 
and lhs.MyTempIDField not in (

SELECT MIN(lhs.MyTempIDField) 
FROM 
table_a lhs 
join table_a rhs 
on lhs.field1 = rhs.field1 
and lhs.field2 = rhs.field2 [etc] 
WHERE 
lhs.MyTempIDField <> rhs.MyTempIDField 
GROUP BY 
    lhs.field1, lhs.field2 etc 
) 
+0

Hola y gracias intentaré esto. ¿Has considerado que es la versión compacta? – Sypress

+0

No debería importar en términos de lenguaje, agregando una fila de identidad bastante fácil a través del script si es necesario. –

Cuestiones relacionadas