Determinar duplicados en una tabla de datos

Tengo una tabla de datos que he cargado desde un archivo CSV. Necesito determinar qué filas están duplicadas en función de dos columnas (product_id y owner_org_id) en la tabla de datos. Una vez que he determinado eso, puedo usar esa información para construir mi resultado, que es una tabla de datos que contiene solo las filas que no son únicas y una tabla de datos que contiene solo las filas que son únicas.

He mirado otros ejemplos aquí y el código que he encontrado hasta ahora se compila y ejecuta, pero parece pensar que cada fila en los datos es única. En realidad, en los datos de prueba hay 13 filas y solo 6 son únicas. Así que claramente estoy haciendo algo mal.

EDITAR: Pensé que debería tener en cuenta que las filas que tienen duplicados deberían TODOS eliminarse, no solo los duplicados de esa fila. por ejemplo, si hay 4 duplicados, los 4 deben eliminarse, no 3, dejando una fila única de los 4.

EDIT2: Alternativamente, si puedo seleccionar todas las filas duplicadas (en lugar de intentar seleccionar filas únicas), está bien para mí. De cualquier manera puede llevarme a mi resultado final.

El código en el método de procesamiento:

MyRowComparer myrc = new MyRowComparer();
var uniquerows = dtCSV.AsEnumerable().Distinct(myrc);

junto con lo siguiente:

public class MyRowComparer : IEqualityComparer<DataRow>
{
    public bool Equals(DataRow x, DataRow y)
    {
        //return ((string.Compare(x.Field<string>("PRODUCT_ID"),   y.Field<string>("PRODUCT_ID"),   true)) ==
        //        (string.Compare(x.Field<string>("OWNER_ORG_ID"), y.Field<string>("OWNER_ORG_ID"), true)));
        return
            x.ItemArray.Except(new object[] { x[x.Table.Columns["PRODUCT_ID"].ColumnName] }) ==
            y.ItemArray.Except(new object[] { y[y.Table.Columns["PRODUCT_ID"].ColumnName] }) &&
            x.ItemArray.Except(new object[] { x[x.Table.Columns["OWNER_ORG_ID"].ColumnName] }) ==
            y.ItemArray.Except(new object[] { y[y.Table.Columns["OWNER_ORG_ID"].ColumnName] });
    }

    public int GetHashCode(DataRow obj)
    {
        int y = int.Parse(obj.Field<string>("PRODUCT_ID"));
        int z = int.Parse(obj.Field<string>("OWNER_ORG_ID"));
        int c = y ^ z;
        return c;
    }
}

preguntado el 22 de mayo de 12 a las 18:05

No entiendo porque estas usando Except - ¿Por qué no estás simplemente comparando los valores de las 2 columnas que importan? también x.Table.Columns["PRODUCT_ID"].ColumnName debe ser idéntico a "PRODUCT_ID" por definición, por lo que puede omitir la búsqueda de columnas. -

2 Respuestas

Podría usar LINQ-To-DataSet y Enumerable.Except/Intersect:

var tbl1ID = tbl1.AsEnumerable()
        .Select(r => new
        {
            product_id = r.Field<String>("product_id"),
            owner_org_id = r.Field<String>("owner_org_id"),
        });
var tbl2ID = tbl2.AsEnumerable()
        .Select(r => new
        {
            product_id = r.Field<String>("product_id"),
            owner_org_id = r.Field<String>("owner_org_id"),
        });


var unique = tbl1ID.Except(tbl2ID);
var both = tbl1ID.Intersect(tbl2ID);

var tblUnique = (from uniqueRow in unique
                join row in tbl1.AsEnumerable()
                on uniqueRow equals new
                {
                    product_id = row.Field<String>("product_id"),
                    owner_org_id = row.Field<String>("owner_org_id")
                }
                select row).CopyToDataTable();
var tblBoth = (from bothRow in both
              join row in tbl1.AsEnumerable()
              on bothRow equals new
              {
                  product_id = row.Field<String>("product_id"),
                  owner_org_id = row.Field<String>("owner_org_id")
              }
              select row).CopyToDataTable();

Editar: Obviamente, no he entendido bien tu requerimiento. Entonces solo tienes uno DataTable y desea obtener todas las filas únicas y duplicadas, eso es aún más sencillo. Puedes usar Enumerable.GroupBy con un tipo anónimo que contiene ambos campos:

var groups = tbl1.AsEnumerable()
    .GroupBy(r => new
    {
        product_id = r.Field<String>("product_id"),
        owner_org_id = r.Field<String>("owner_org_id")
    });
var tblUniques = groups
    .Where(grp => grp.Count() == 1)
    .Select(grp => grp.Single())
    .CopyToDataTable();
var tblDuplicates = groups
    .Where(grp => grp.Count() > 1)
    .SelectMany(grp => grp)
    .CopyToDataTable();

contestado el 22 de mayo de 12 a las 20:05

Al principio solo tengo una tabla de datos, con todo lo que contiene. Este ejemplo parece asumir que ya tengo todo dividido en dos conjuntos de datos, ¿o me estoy perdiendo algo? - user1366062

@ user1366062: Entonces no entendí un poco tu requerimiento. Edité mi respuesta. - Tim Schmelter

Perfecto, esto es justo lo que estaba buscando. ¡Gracias! - Mack

Su criterio está fuera de lugar. Estás comparando conjuntos de objetos que no te interesan (Except excluye) en.

En su lugar, sea lo más claro posible (tipo de datos) y manténgalo simple:

public bool Equals(DataRow x, DataRow y)
{   
    // Usually you are dealing with INT keys
    return (x["PRODUCT_ID"] as int?) == (y["PRODUCT_ID"] as int?)
      && (x["OWNER_ORG_ID"] as int?) == (y["OWNER_ORG_ID"] as int?);

    // If you really are dealing with strings, this is the equivalent:
    // return (x["PRODUCT_ID"] as string) == (y["PRODUCT_ID"] as string)
    //  && (x["OWNER_ORG_ID"] as string) == (y["OWNER_ORG_ID"] as string)
}  

Comprueba si hay null si esa es una posibilidad. Tal vez desee excluir filas que son iguales porque sus ID son nulas.

Observar la int?. Esto no es un error tipográfico. El signo de interrogación es obligatorio si se trata de valores de base de datos de columnas que se pueden NULL. La razón es que NULL valores serán representados por el tipo DBNull C ª#. Utilizando el as el operador solo te da null en este caso (en lugar de un InvalidCastException. Si está seguro, se trata de INT NOT NULL, fundido con (int).

Lo mismo es cierto para las cadenas. (string) afirma que está esperando valores DB no nulos.

EDIT1:

Tenía mal el tipo. ItemArray no es una tabla hash. Utilice la fila directamente.

EDIT2:

Adicional string ejemplo, algun comentario

Para una forma más sencilla, consulte Cómo seleccionar filas distintas en una tabla de datos y almacenarlas en una matriz

EDIT3:

Alguna explicación sobre los moldes.

El otro enlace que sugerí hace lo mismo que su código. Olvidé tu intención original ;-) Acabo de ver tu código y respondí al error más obvio, vi - lo siento

Así es como resolvería el problema.

using System.Linq;
using System.Data.Linq;

var q = dtCSV
    .AsEnumerable()
    .GroupBy(r => new { ProductId = (int)r["PRODUCT_ID"], OwnerOrgId = (int)r["OWNER_ORG_ID"] })
    .Where(g => g.Count() > 1).SelectMany(g => g);

var duplicateRows = q.ToList();

No sé si esto es 100% correcto, no tengo un IDE a mano. Y deberá ajustar los moldes al tipo apropiado. Ver mi adición arriba.

contestado el 23 de mayo de 17 a las 11:05

Esto parece eliminar todos los duplicados excepto uno por grupo de duplicados. por ejemplo, si tengo 5 filas, todas con id_producto y id_org_propietario coincidentes, obtengo 1 fila con id_producto y id_org_propietario coincidentes. Me gustaría eliminar también esa fila final. - user1366062

Además, no pude usar 'as int' a pesar de que son int porque aparece un error en el nombre de la columna que dice 'el operador as debe usarse con un tipo de referencia o un tipo que acepta valores NULL'. Revisé su enlace para encontrar la forma más sencilla, que parecía funcionar parcialmente, sin embargo, tuve problemas para que devolviera una fila completa y no solo las dos columnas en cuestión. Necesito toda la fila, pero solo comparo las dos columnas en busca de duplicados. Podría haberlo hecho mal, pero el ejemplo parecía bastante claro. - user1366062

@ user1366062: respondí a sus dos comentarios en mi respuesta. Hay una parte sobre los moldes en el medio y una adición al final: skarmats

Acabo de ver la edición en la otra respuesta. Tome su código para una solución más completa. Toma las filas resultantes y las retroalimenta a un DataTable que podría ser lo que quieres. - skarmats

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.