7 votos

Obtener registros de valor único de un campo

Construyendo una aplicación de escritorio usando C#.net y ArcObjects. Estoy tratando de recuperar los registros de un shapefile utilizando IQueryFilter . Tengo problemas para escribir la cláusula where en mi escenario y la lógica aplicable.

Este es el escenario:

Tengo un Categoría y "Completado" (ambos son de tipo cadena). Lo que necesito hacer es, para una categoría determinada ver si todos los valores del completado son únicos. Si los valores no son únicos necesito mostrar esos registros fuera.

Datos de la muestra

alt text

En el ejemplo anterior, las categorías B y C no tienen valores únicos. Por lo tanto, todos estos registros para B y C deben ser mostrados.

Supongo que el uso de una interfaz IQueryFilter debería funcionar. Pero no sé cómo puedo conseguirlo utilizando una cláusula where correcta. (Sólo necesito conseguir que la combinación de los campos Category y Completed no sea única)

Por favor, ayuda...

12voto

Nathan Bedford Puntos 3157

Está llegando a los límites de la consulta de datos mediante ArcObjects. ArcObjects no le permitirá escribir una consulta atribulada usando IQueryFilter, está limitado a consultas muy simples.

No voy a intentar componer la consulta por ti ya que otros ya han dado algunas respuestas. Sin embargo, permítame esbozar las posibilidades de consulta más complejas en ArcObjects, aunque no estén directamente relacionadas con su situación particular. Usted o alguien más podría encontrar esta información útil.

  • Las consultas más complejas que implican uniones de tablas se pueden realizar utilizando los definidores de consultas, es decir, el IQueryDef interfaz. La referencia a esta interfaz puede obtenerse llamando a IFeatureWorkspace.CreateQueryDef .

  • Si necesita ORDER BY, GROUP BY en los filtros de consulta, puede utilizar IQueryFilterDefinition.PostfixClause . Sin embargo, no podrá, por ejemplo, recuperar el recuento de filas dentro de un grupo, porque no puede poner expresiones como COUNT(*) en el filtro de consulta SubFields . No funciona porque no hay ningún campo llamado COUNT(*) en la tabla que está consultando. Tampoco se puede poner un HAVING en el filtro de consulta WhereClause . En aplicaciones del mundo real, esto hace que IQueryFilterDefinition sea inútil para cualquier propósito de agrupación.

  • En ArcGIS 10, puede utilizar IQueryFilterDefinition2.PrefixClause para especificar cláusulas como DISTINCT .

  • Puede superar algunas de las dificultades con filtros de consulta y GROUP BY con los defs de consulta, pero sólo en ArcGIS 10. Hay una nueva interfaz IQueryDef2 que permite especificar tanto PrefixClause como PostfixClause en las definiciones de consulta. Las definiciones de consulta no le obligan a consultar sólo los campos que existen realmente en una tabla, por lo que puede componer consultas como, por ejemplo, SELECT A, COUNT(*) FROM TABLE GROUP BY A .

Ahora, probablemente entienda que ninguna de esta información es particularmente útil en su solución. Básicamente, hay pocas cosas que PUEDES hacer:

  1. Examinar la tabla en una sola consulta, almacenando los datos en estructuras de datos intermedias (listas, diccionarios) para identificar grupos y registros dentro de estos grupos. Esto podría consumir bastante memoria si se tienen muchos datos.
  2. Divida su problema en varias consultas y ejecútelas una por una. Esto también podría afectar al rendimiento en gran medida, pero podría no ser un problema si la cantidad de datos es baja.
  3. Cree una vista de base de datos (utilizando las consultas de las otras respuestas, por ejemplo) y luego consúltela con ArcObjects. Podrá abrir las vistas como cualquier otra tabla y consultarlas utilizando tanto IQueryFilter como IQueryDef. Esto tiene la ventaja de aprovechar las capacidades existentes de la base de datos, pero es una molestia si tiene un acceso limitado a la administración de la base de datos. Sólo funciona en geodatabases SDE y sus datos no deben estar versionados. De lo contrario, no tendrás suerte.

3voto

Anthony Cramp Puntos 126

Qué petr k. ha dicho es correcto, pero me gustaría ampliarlo más.

Yo lo haría teniendo varias consultas.

Puede obtener los valores Distinct de Propiedad IDataStatistics.UniqueValues . Así que primero obtendría los valores únicos del campo Categoría.

A continuación, un bucle en que y obtener el Uniquevaluecount para el campo completado, (mientras que obtener el cursor con sólo las características con la categoría particular)

De este modo, podrá saber qué categoría tiene más de un valor Único.

1voto

swilliams Puntos 19415

Tiene que haber una manera mejor que la que voy a publicar - alguien más inteligente en los encantos de SQL sin duda puede arreglar esto:

SELECT * FROM table WHERE category IN (
  SELECT category FROM (
    SELECT category, COUNT(*) AS c2 FROM (
      SELECT category, completed, COUNT(*) AS c1 FROM table
      GROUP BY category, completed
    ) AS t1 
    GROUP BY category
  HAVING COUNT(*) > 1
  ) AS t2 
)

Esto tiene el resultado previsto de mostrar todos los registros de B y C, ya que tienen múltiples valores para "completado", pero es a costa de ser casi incomprensible.

B   No
C   Yes
B   No
B   Yes
C   No
C   Yes

Si quisieras hacerlo menos horrible, podrías eliminar la consulta externa, lo que te devolvería los registros que estaban fallando, y su recuento.

He probado lo anterior en SQL Server, por si sirve de algo, pero no en Oracle. Y es casi seguro que no funcionará en Access. ¿Alguien quiere simplificar esto?

1voto

saint_groceon Puntos 2696

Ya que pediste C#, a continuación hay una forma de hacer esto con System.Collections.Generic. No lo he probado.

public void Test()
{
    try
    {
        var fLayer = ArcMap.Document.Maps.get_Item(0).get_Layer(0) as IFeatureLayer;
        var categories = Categorize((ITable)fLayer.FeatureClass, "Category", "Completed");
        foreach (KeyValuePair<string, List<string>> kvp in categories)
        {
            if (kvp.Value.Count > 1)
                Debug.Print("Category {0} has {1} unique values", kvp.Key, kvp.Value.Count);
        }
    }
    catch (Exception ex)
    {
        Debug.Print(ex.Message);
    }
}

public Dictionary<string, List<string>> Categorize(ITable table, string categoryFld, string otherFld)
{
    var outList = new Dictionary<string, List<string>>();
    IQueryFilter qf = new QueryFilter();
    qf.SubFields = String.Format("{0},{1}", categoryFld, otherFld);

    int idxCat = FindField(table,categoryFld);
    int idxOther = FindField(table,otherFld);
    ICursor cur = null;
    // **edited to release cursor in a finally block**
    try
    {
        cur = table.Search(qf, true);
        IRow row;
        while ((row = cur.NextRow()) != null)
        {
            string category = GetStrVal(row, idxCat);
            string other = GetStrVal(row, idxOther);
            if (!outList.ContainsKey(category))
                outList.Add(category, new List<string>());
            if (!outList[category].Contains(other))
                outList[category].Add(other);
        }
    }
    catch
    {
        throw;
    }
    finally
    {
        System.Runtime.InteropServices.Marshal.FinalReleaseComObject(cur);
    }
    return outList;
}

public static string GetStrVal(IRow row, int idx)
{
    return row.get_Value(idx) is DBNull ? "<Null>" : row.get_Value(idx).ToString();
}

public static int FindField(ITable table, string fldName)
{
    int idx = table.FindField(fldName);
    if(idx == -1)
        throw new Exception(String.Format("field {0} not found on table {1}",
            fldName,((IDataset)table).Name));
    return idx;
}

1voto

JarrettV Puntos 9099

Probablemente sea imposible escribir un proveedor de LINQ, pero aquí hay un ORM personalizado para la geodatabase. Funciona con cualquier cosa que implemente IFeatureClass o ITable.

http://jshirota.github.com/Earthworm/

i-Ciencias.com

I-Ciencias es una comunidad de estudiantes y amantes de la ciencia en la que puedes resolver tus problemas y dudas.
Puedes consultar las preguntas de otros usuarios, hacer tus propias preguntas o resolver las de los demás.

Powered by:

X