¿Cuál es la mejor manera de eliminar duplicados de una tabla de datos?
-
19-08-2019 - |
Pregunta
Revisé todo el sitio y busqué en Google en la red, pero no pude encontrar una solución simple a este problema.
Tengo una tabla de datos que tiene alrededor de 20 columnas y 10K filas.Necesito eliminar las filas duplicadas en esta tabla de datos en función de 4 columnas clave.¿No tiene .Net una función que haga esto?La función más cercana a lo que estoy buscando era datatable.DefaultView.ToTable (verdadero, conjunto de columnas para mostrar), pero esta función hace una diferencia todo las columnas.
Sería genial si alguien pudiera ayudarme con esto.
EDITAR:Lamento no haber sido claro en esto.Esta tabla de datos se crea leyendo un archivo CSV y no desde una base de datos.Entonces usar una consulta SQL no es una opción.
Solución
Puede usar Linq para conjuntos de datos. Verifique esto . Algo como esto:
// Fill the DataSet.
DataSet ds = new DataSet();
ds.Locale = CultureInfo.InvariantCulture;
FillDataSet(ds);
List<DataRow> rows = new List<DataRow>();
DataTable contact = ds.Tables["Contact"];
// Get 100 rows from the Contact table.
IEnumerable<DataRow> query = (from c in contact.AsEnumerable()
select c).Take(100);
DataTable contactsTableWith100Rows = query.CopyToDataTable();
// Add 100 rows to the list.
foreach (DataRow row in contactsTableWith100Rows.Rows)
rows.Add(row);
// Create duplicate rows by adding the same 100 rows to the list.
foreach (DataRow row in contactsTableWith100Rows.Rows)
rows.Add(row);
DataTable table =
System.Data.DataTableExtensions.CopyToDataTable<DataRow>(rows);
// Find the unique contacts in the table.
IEnumerable<DataRow> uniqueContacts =
table.AsEnumerable().Distinct(DataRowComparer.Default);
Console.WriteLine("Unique contacts:");
foreach (DataRow uniqueContact in uniqueContacts)
{
Console.WriteLine(uniqueContact.Field<Int32>("ContactID"));
}
Otros consejos
¿Cómo puedo eliminar filas duplicadas? . (Ajuste la consulta allí para unirse en sus 4 columnas clave)
EDITAR: con su nueva información, creo que la forma más fácil sería implementar IEqualityComparer < T > y use Distinct en sus filas de datos. De lo contrario, si está trabajando con IEnumerable / IList en lugar de DataTable / DataRow, ciertamente es posible con algunos kung-fu de LINQ-to-objects.
EDITAR: ejemplo IEqualityComparer
public class MyRowComparer : IEqualityComparer<DataRow>
{
public bool Equals(DataRow x, DataRow y)
{
return (x.Field<int>("ID") == y.Field<int>("ID")) &&
string.Compare(x.Field<string>("Name"), y.Field<string>("Name"), true) == 0 &&
... // extend this to include all your 4 keys...
}
public int GetHashCode(DataRow obj)
{
return obj.Field<int>("ID").GetHashCode() ^ obj.Field<string>("Name").GetHashCode() etc.
}
}
Puedes usarlo así:
var uniqueRows = myTable.AsEnumerable().Distinct(MyRowComparer);
Si tiene acceso a Linq, creo que debería poder usar la funcionalidad de grupo integrada en la colección en memoria y seleccionar las filas duplicadas
Busque en Google el grupo Linq por ejemplos
Debe tenerse en cuenta que se debe llamar a Table.AcceptChanges () para completar la eliminación. De lo contrario, la fila eliminada todavía está presente en DataTable con RowState establecido en Eliminado. Y Table.Rows.Count no cambia después de la eliminación.
Creo que esta debe ser la mejor manera de eliminar duplicados de Tabla de datos mediante el uso Linq
y moreLinq
Código:
Linq
RemoveDuplicatesRecords(yourDataTable);
private DataTable RemoveDuplicatesRecords(DataTable dt)
{
var UniqueRows = dt.AsEnumerable().Distinct(DataRowComparer.Default);
DataTable dt2 = UniqueRows.CopyToDataTable();
return dt2;
}
Artículo de blog: Eliminar registros de filas duplicadas de DataTable Asp.net c#
MásLinq
// Distinctby column name ID
var valueDistinctByIdColumn = yourTable.AsEnumerable().DistinctBy(row => new { Id = row["Id"] });
DataTable dtDistinctByIdColumn = valueDistinctByIdColumn.CopyToDataTable();
Nota: moreLinq
Es necesario agregar una biblioteca.
En morelinq puede utilizar la función llamada DistinctBy en la que puede especificar la propiedad en la que desea encontrar objetos Distinct.
Artículo de blog: Uso del método moreLinq DistinctBy para eliminar registros duplicados
La respuesta de Liggett78 es mucho mejor, especialmente. como el mío tuvo un error! Corrección de la siguiente manera ...
DELETE TableWithDuplicates
FROM TableWithDuplicates
LEFT OUTER JOIN (
SELECT PK_ID = Min(PK_ID), --Decide your method for deciding which rows to keep
KeyColumn1,
KeyColumn2,
KeyColumn3,
KeyColumn4
FROM TableWithDuplicates
GROUP BY KeyColumn1,
KeyColumn2,
KeyColumn3,
KeyColumn4
) AS RowsToKeep
ON TableWithDuplicates.PK_ID = RowsToKeep.PK_ID
WHERE RowsToKeep.PK_ID IS NULL
Encontré esto en bytes.com :
Puede usar el proveedor JET 4.0 OLE DB con las clases en el Espacio de nombres System.Data.OleDb para acceder al archivo de texto delimitado por comas (utilizando un DataSet / DataTable).
O podría usar Microsoft Text Driver para ODBC con las clases en el Espacio de nombres System.Data.Odbc para acceder al archivo utilizando controladores ODBC.
Eso le permitiría acceder a sus datos a través de consultas sql, como lo propusieron otros.
" Esta tabla de datos se está creando leyendo un archivo CSV y no desde un DB. "
Entonces, imponga una restricción única en las cuatro columnas de la base de datos, y las inserciones que están duplicadas en su diseño no entrarán. A menos que decida fallar en lugar de continuar cuando esto suceda, pero esto seguramente es configurable en su CSV Importar script.
Use una consulta en lugar de funciones:
DELETE FROM table1 AS tb1 INNER JOIN
(SELECT id, COUNT(id) AS cntr FROM table1 GROUP BY id) AS tb2
ON tb1.id = tb2.id WHERE tb2.cntr > 1
Este es un código muy simple que no requiere linq ni columnas individuales para hacer el filtro. Si todos los valores de las columnas en una fila son nulos, se eliminarán.
public DataSet duplicateRemoval(DataSet dSet)
{
bool flag;
int ccount = dSet.Tables[0].Columns.Count;
string[] colst = new string[ccount];
int p = 0;
DataSet dsTemp = new DataSet();
DataTable Tables = new DataTable();
dsTemp.Tables.Add(Tables);
for (int i = 0; i < ccount; i++)
{
dsTemp.Tables[0].Columns.Add(dSet.Tables[0].Columns[i].ColumnName, System.Type.GetType("System.String"));
}
foreach (System.Data.DataRow row in dSet.Tables[0].Rows)
{
flag = false;
p = 0;
foreach (System.Data.DataColumn col in dSet.Tables[0].Columns)
{
colst[p++] = row[col].ToString();
if (!string.IsNullOrEmpty(row[col].ToString()))
{ //Display only if any of the data is present in column
flag = true;
}
}
if (flag == true)
{
DataRow myRow = dsTemp.Tables[0].NewRow();
//Response.Write("<tr style=\"background:#d2d2d2;\">");
for (int kk = 0; kk < ccount; kk++)
{
myRow[kk] = colst[kk];
// Response.Write("<td class=\"table-line\" bgcolor=\"#D2D2D2\">" + colst[kk] + "</td>");
}
dsTemp.Tables[0].Rows.Add(myRow);
}
} return dsTemp;
}
Esto incluso puede usarse para eliminar datos nulos de la hoja de Excel.
Prueba esto
Consideremos que dtInput es su tabla de datos con registros duplicados.
Tengo un nuevo DataTable dtFinal en el que quiero filtrar las filas duplicadas.
Por lo tanto, mi código será similar al siguiente.
DataTable dtFinal = dtInput.DefaultView.ToTable(true,
new string[ColumnCount] {"Col1Name","Col2Name","Col3Name",...,"ColnName"});
No estaba interesado en usar la solución de Linq anterior, así que escribí esto:
/// <summary>
/// Takes a datatable and a column index, and returns a datatable without duplicates
/// </summary>
/// <param name="dt">The datatable containing duplicate records</param>
/// <param name="ComparisonFieldIndex">The column index containing duplicates</param>
/// <returns>A datatable object without duplicated records</returns>
public DataTable duplicateRemoval(DataTable dt, int ComparisonFieldIndex)
{
try
{
//Build the new datatable that will be returned
DataTable dtReturn = new DataTable();
for (int i = 0; i < dt.Columns.Count; i++)
{
dtReturn.Columns.Add(dt.Columns[i].ColumnName, System.Type.GetType("System.String"));
}
//Loop through each record in the datatable we have been passed
foreach (DataRow dr in dt.Rows)
{
bool Found = false;
//Loop through each record already present in the datatable being returned
foreach (DataRow dr2 in dtReturn.Rows)
{
bool Identical = true;
//Compare the column specified to see if it matches an existing record
if (!(dr2[ComparisonFieldIndex].ToString() == dr[ComparisonFieldIndex].ToString()))
{
Identical = false;
}
//If the record found identically matches one we already have, don't add it again
if (Identical)
{
Found = true;
break;
}
}
//If we didn't find a matching record, we'll add this one
if (!Found)
{
DataRow drAdd = dtReturn.NewRow();
for (int i = 0; i < dtReturn.Columns.Count; i++)
{
drAdd[i] = dr[i];
}
dtReturn.Rows.Add(drAdd);
}
}
return dtReturn;
}
catch (Exception)
{
//Return the original datatable if something failed above
return dt;
}
}
Además, esto funciona en TODAS las columnas en lugar de un índice de columna específico:
/// <summary>
/// Takes a datatable and returns a datatable without duplicates
/// </summary>
/// <param name="dt">The datatable containing duplicate records</param>
/// <returns>A datatable object without duplicated records</returns>
public DataTable duplicateRemoval(DataTable dt)
{
try
{
//Build the new datatable that will be returned
DataTable dtReturn = new DataTable();
for (int i = 0; i < dt.Columns.Count; i++)
{
dtReturn.Columns.Add(dt.Columns[i].ColumnName, System.Type.GetType("System.String"));
}
//Loop through each record in the datatable we have been passed
foreach (DataRow dr in dt.Rows)
{
bool Found = false;
//Loop through each record already present in the datatable being returned
foreach (DataRow dr2 in dtReturn.Rows)
{
bool Identical = true;
//Compare all columns to see if they match the existing record
for (int i = 0; i < dt.Columns.Count; i++)
{
if (!(dr2[i].ToString() == dr[i].ToString()))
{
Identical = false;
}
}
//If the record found identically matches one we already have, don't add it again
if (Identical)
{
Found = true;
break;
}
}
//If we didn't find a matching record, we'll add this one
if (!Found)
{
DataRow drAdd = dtReturn.NewRow();
for (int i = 0; i < dtReturn.Columns.Count; i++)
{
drAdd[i] = dr[i];
}
dtReturn.Rows.Add(drAdd);
}
}
return dtReturn;
}
catch (Exception)
{
//Return the original datatable if something failed above
return dt;
}
}