2012-07-15 25 views
5

Qui si desidera eliminare le righe con il valore di una colonna duplicata (Product) che verrà quindi utilizzata come chiave primaria .Elimina duplicati senza chiave primaria

La colonna è di tipo nvarchar e non vogliamo avere 2 righe per un prodotto. Il database è grande con circa migliaia di righe che dobbiamo rimuovere.

Durante la ricerca di tutti i duplicati, vogliamo mantenere il primo elemento e rimuovere il secondo come duplicato.

Non esiste ancora la chiave primaria e vogliamo eseguirla dopo questa attività di rimozione dei duplicati. Quindi il columm Product potrebbe essere la nostra chiave primaria.

Il database è SQL Server CE.

ho provato diversi metodi, e soprattutto ottenendo errore simile a:

Si è verificato un errore di analisi della query. [Linea di token numero = 2, linea di token offset = 1, Token per errore = FROM]

Un metodo che ho provato:

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

Il modo preferito cercando di imparare e regolare il mio codice con qualcosa di simile (non è corretto ancora):

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

Quanto è grande il database. Stiamo parlando di milioni di righe qui? Miliardi? –

+0

circa 200.000 record con 3000 duplicati, non molto: D – Sypress

+0

Quando si hanno due record con gli stessi dati per Prodotto, ma dati diversi in altre colonne, come si fa a sapere qual è quello corretto da conservare? –

risposta

4

Se si dispone di due record DIFFERENTI con la stessa colonna Prodotto, è possibile selezionare i record indesiderati con alcuni criteri, ad es.

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

Quindi è possibile eseguire un ELIMINA UNISCI tra ProductTable e Victims.

Oppure selezionare Solo prodotto, quindi eseguire un CANC per alcune altre condizioni JOIN, ad esempio con CustomerId non valido o EntryDate NULL o altro. Questo funziona se si specifica che conosce che esiste una sola copia valida del Prodotto e che tutti gli altri sono riconoscibili dai dati non validi.

Supponiamo che tu abbia documenti IDENTICI (o che tu abbia entrambi identici e non identici, oppure potresti avere diversi duplicati per qualche prodotto e non sai quale). Esegui esattamente la stessa query. Quindi, si esegue una query SELECT su ProductsTable e SELECT DISTINCT tutti i prodotti corrispondenti ai codici prodotto da deduplicare, raggruppati per prodotto e scegliendo una funzione aggregata adeguata per tutti i campi (se identico, qualsiasi aggregato dovrebbe fare. o MIN).Ciò "salverà" esattamente una riga per ogni prodotto.

A quel punto si esegue il comando DELETE JOIN e si eliminano tutti i prodotti duplicati. Quindi, reimportare semplicemente il sottoinsieme salvato e deduplicato nella tabella principale.

Ovviamente, tra il DELETE JOIN e l'INSERT SELECT, si avrà il DB in uno stato instabile, con tutti i prodotti con almeno un duplicato semplicemente scomparso.

Un altro modo che dovrebbe funzionare in 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: il modo sopra NON FUNZIONA se si vuole distinguere i "buoni" e "record duplicati non validi". Funziona solo se hai ridondanti record DUPLICATE o se non ti interessa la riga che tieni e che butti via!!

EDIT: Si dice che "duplicati" hanno campi non validi. In questo caso è possibile modificare quanto sopra con un trucco di smistamento:

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

Poi, se si dispone di una sola riga per il prodotto, cosa buona e giusta, otterrà selezionato. Se ne hai di più, quello per cui (FieldWhichShouldNeverBeNull IS NULL) è FALSE (cioè quello in cui FieldWhichShouldNeverBeNull non è effettivamente nullo come dovrebbe) verrà selezionato per primo e inserito. Tutti gli altri rimbalzeranno, silenziosamente a causa della clausola IGNORE, contro l'unicità del Prodotto. Non è un modo molto carino per farlo (e verificare che non ho mescolato true con false nella mia clausola!), Ma dovrebbe funzionare.

EDIT
in realtà più di una nuova risposta

Si tratta di una semplice tabella per illustrare il 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'); 

Non ci sono indice, e nessuna chiave primaria. Potremmo ancora dichiarare il prodotto come chiave primaria.

Ma succede qualcosa di brutto. Entrano due nuovi record e entrambi hanno una descrizione NULL.

Tuttavia, il secondo è un prodotto valido poiché non sapevamo nulla di CBPD14 prima d'ora, e quindi NON vogliamo perdere completamente questo record. Noi do vogliamo sbarazzarci dello spurio CBPD10 però.

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

Un maleducato DELETE FROM WHERE ProductTable Descrizione IS NULL è fuori questione, che avrebbe ucciso CBPD14 che non è un duplicato.

Quindi lo facciamo così. In primo luogo ottenere l'elenco dei duplicati:

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

Partiamo dal presupposto che: "Non v'è almeno un buon record per ogni serie di record di cattivo".

Controlliamo questa ipotesi postulando il contrario e interrogandoci. Se tutto è copacetico, ci aspettiamo che questa query non restituisca nulla.

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; 

Per ulteriori verifiche, inserisco due record che rappresentano questa modalità di errore; ora mi aspetto la query sopra per restituire il nuovo codice.

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

Ora la query "check" restituisce infatti,

AC5 

Così, la generazione di Dups sembra buono.

Procedere ora per eliminare tutti i record duplicati che non sono validi. Se ci sono duplicati, record validi, rimarranno duplicati a meno che non si trovi qualche condizione, distinguendo tra loro un record "buono" e dichiarando tutti gli altri "non validi" (magari ripetendo la procedura con un campo diverso da Descrizione).

Ma sì, c'è un problema. Attualmente, non è possibile eliminare da una tabella e selezionare dalla stessa tabella in una sottoquery (http://dev.mysql.com/doc/refman/5.0/en/delete.html). È quindi necessaria una piccola soluzione:

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; 

Ora questo cancellerà tutti i record non validi, purché compaiano nella tabella Dups.

Pertanto, il nostro record CBPD14 non verrà toccato, perché non appare lì. Il record "buono" per CBPD10 rimarrà invariato perché non è vero che la sua Descrizione sia NULL. Tutti gli altri - puf.

Let me stato ancora una volta che se un record ha non record validi e ancoraè un duplicato, poi tutte le copie di quel disco verranno uccisi - non ci saranno superstiti.

Per evitare questo, è possibile innanzitutto selezionare (utilizzando la query sopra, il controllo "che non deve restituire nulla") le righe che rappresentano questa modalità di errore in un'altra TABELLA TEMPORANEA, quindi INSERIRLE nuovamente nella tabella principale dopo l'eliminazione (utilizzando le transazioni potrebbe essere in ordine).

+0

Proverò a dare un feedback su di esso presto, grazie – Sypress

+0

Amico, sto provando in base al tuo approccio, se possibile, per favore fornisci il campione 3-5 righe di codice in base a ciò che pensato e riassunto. sarà apprezzato – Sypress

+1

Can do. Includerò un piccolo esempio per essere davvero sicuro di aver capito il tuo problema. Cancellare grandi quantità di dati mi rende sempre nervoso :-) – LSerni

-2

Prova questo:

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

Questo ha il difetto che elimina TUTTI i record con un Prodotto duplicato. Quello che probabilmente vorresti fare è cancellare tutti tranne uno di ciascun gruppo di record con un determinato prodotto. Potrebbe essere utile copiare prima tutti i duplicati in una tabella separata, quindi rimuovere in qualche modo i duplicati da quella tabella, quindi applicare la precedente e quindi copiare nuovamente i prodotti rimanenti nella tabella originale.

+0

Proveremo presto, grazie – Sypress

+0

Questa esecuzione di questo è veramente lento!, è quasi mezz'ora ... – Sypress

+2

Walter, perché dovresti pubblicare il codice anche se sai che sta per zappare ogni prodotto nella tabella che ha un duplicato (incluso quello che l'op deve mantenere)? Spero che Sypress abbia letto il paragrafo sotto il codice prima di eseguire execute OPPURE ha un backup completo recente ... – brian

1

Creare un nuovo tavolo scrivendo il vecchio e rinominandolo. Scripta anche tutti gli oggetti (indici ecc.) Dalla vecchia tabella alla nuova. Inserisci i custodi nella nuova tabella. Se il database si trova in un modello di recupero con registrazione di massa o semplice, questa operazione verrà registrata in minima parte. Rilasciare la vecchia tabella e rinominare la nuova con il vecchio nome.

Il vantaggio di questa operazione è che l'inserimento può essere registrato in modo minimale. Le eliminazioni fanno il doppio lavoro perché non solo i dati vengono cancellati, ma l'eliminazione deve essere scritta nel log delle transazioni. Per le tabelle grandi, gli inserimenti con minima registrazione saranno molto più veloci delle eliminazioni.

1

Se non è così grande e si dispone di alcuni tempi di inattività e si dispone di Sql Server Management studio, è possibile inserire un campo identità sulla tabella utilizzando la GUI. Ora hai la situazione come il tuo CTE, eccetto che le righe stesse sono veramente distinte. Così ora puoi fare il seguente

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 

Questo ti dà tutti i "buoni" duplicati. Ora puoi racchiudere questa query con una query 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

Ciao e grazie proveremo questo, hai considerato che è la versione compatta? – Sypress

+0

Non dovrebbe importare in termini di lingua, aggiungendo una riga di identità abbastanza facile attraverso lo script se necessario. –

Problemi correlati