¿Seleccionar eficientemente registros relacionados usando ArcPy?

14

A continuación se muestra el código que estoy usando para replicar el botón "tablas relacionadas" en ArcMap. En ArcMap, ese botón selecciona entidades en una clase de entidad o tabla en función de la selección de entidades en otra clase de entidad o tabla relacionada.

En ArcMap puedo usar ese botón para "empujar" mi selección a la tabla relacionada en cuestión de segundos. No pude encontrar nada integrado en arcpy que replicara el botón, así que usé algunos bucles anidados para hacer la misma tarea.

El siguiente código recorre una tabla de "tratamientos". Para cada tratamiento, recorre una lista de "árboles". Cuando se encuentra una coincidencia entre los campos ID de tratamiento y los árboles, se produce una selección en la capa del árbol. Una vez que se encuentra una coincidencia para un tratamiento, el código no continúa buscando coincidencias adicionales en la capa del árbol. Vuelve a la tabla de tratamiento, selecciona el siguiente tratamiento y vuelve a buscar en la clase de entidad de árbol.

El código en sí funciona bien, pero es extremadamente lento. La "tabla de tratamiento" en este caso tiene 16,000 registros. La clase de entidad "árbol" tiene 60,000 registros.

¿Hay otra manera más eficiente de recrear lo que está haciendo ESRI cuando empuja la selección de una tabla a otra? ¿Debo estar creando un índice para las tablas? NOTA: estos datos se almacenan en un SDE.

 # Create search cursor to loop through the treatments
treatments = arcpy.SearchCursor(treatment_tv)
treatment_field = "Facility_ID"

for treatment in treatments:

    #Get ID of treatment
    treatment_ID = treatment.getValue(treatment_field)

    # Create search cursor for looping through the trees
    trees = arcpy.SearchCursor(tree_fl)
    tree_field = "FACILITYID"

    for tree in trees:

        # Get FID of tree
        tree_FID = tree.getValue(tree_field)

        if tree_FID == treatment_FID:
            query = "FACILITYID = " + str(tree_FID)
            arcpy.SelectLayerByAttribute_management(tree_fl, "REMOVE_FROM_SELECTION", query)
            break
WatsonP
fuente
2
¿Estás usando ArcGIS 10.1? Si es así, es probable que arcpy.da.SearchCursor sea mucho más rápido (quizás 10X) que arcpy.SearchCursor. Además, es posible que desee considerar el uso de un diccionario Python. Sospecho que una "selección de archivo de claves" como esta podría beneficiarse enormemente del enfoque utilizado aquí
PolyGeo
¿Es su base de datos SDE en Oracle por casualidad?
blah238

Respuestas:

12

En primer lugar, sí, definitivamente querrá asegurarse de que sus campos de clave primaria y externa estén indexados en ambas tablas. Esto permite que el DBMS planifique y ejecute consultas en estos campos de manera mucho más eficiente.

En segundo lugar, está llamando SelectLayerByAttribute_managementen un ciclo cerrado y anidado (una vez por árbol por tratamiento). Esto es altamente ineficiente, por varias razones:

  • No necesita dos bucles, uno anidado dentro del otro, para esto, por lo que puedo decir. Uno será suficiente.
  • Las funciones de geoprocesamiento son "gruesas" y requieren mucho tiempo para llamar en comparación con las funciones integradas típicas de Python. Debe evitar llamarlos en un circuito cerrado.
  • Pedir un registro / ID a la vez da como resultado muchos más viajes de ida y vuelta a la base de datos.

En su lugar, refactorice su código para que llame SelectLayerByAttribute_managementsolo una vez con una cláusula where construida para seleccionar todos los registros relacionados.

Tomando prestada una función de otra respuesta para la lógica de construcción whereclause, me imagino que se vería así:

def selectRelatedRecords(sourceLayer, targetLayer, sourceField, targetField):
    sourceIDs = set([row[0] for row in arcpy.da.SearchCursor(sourceLayer, sourceField)])
    whereClause = buildWhereClauseFromList(targetLayer, targetField, sourceIDs)
    arcpy.AddMessage("Selecting related records using WhereClause: {0}".format(whereClause))
    arcpy.SelectLayerByAttribute_management(targetLayer, "NEW_SELECTION", whereClause)

Podrías llamarlo así: selectRelatedRecords(treatment_tv, tree_fl, "Facility_ID", "FACILITYID")

Notas:

  • Esto utiliza un arcpy.da.SearchCursor, solo disponible en 10.1. Como @PolyGeo mencionó, estos cursores son mucho más rápidos que sus predecesores ( arcpy.SearchCursor). Sin embargo, podría modificarse fácilmente para usar el antiguo SearchCursor:

    sourceIDs = set([row.getValue(sourceField) for row in arcpy.SearchCursor(sourceLayer, "", "", sourceField)])
  • Si su geodatabase SDE está en Oracle, tenga en cuenta que la INdeclaración utilizada en la función de la respuesta vinculada está limitada a 1000 elementos. En esta respuesta se describe una posible solución , pero tendría que modificar la función para dividirla en varias INdeclaraciones de 1000 en lugar de una.

blah238
fuente
5

La solución anterior funciona muy bien para mí y fue muy rápida. Utilizando el código anterior y el código referenciado de la otra publicación, así es como lo construí:

# Local Variables
OriginTable = "This must be a Table View or Feature Layer"
DestinationTable = "This must be a Table View or Feature Layer"
PrimaryKeyField = "Matching Origin Table Field"
ForiegnKeyField = "Matching Destination Table Field"

def buildWhereClauseFromList(OriginTable, PrimaryKeyField, valueList):
  """Takes a list of values and constructs a SQL WHERE
       clause to select those values within a given PrimaryKeyField
       and OriginTable."""

    # Add DBMS-specific field delimiters
    fieldDelimited = arcpy.AddFieldDelimiters(arcpy.Describe(OriginTable).path, PrimaryKeyField)

    # Determine field type
    fieldType = arcpy.ListFields(OriginTable, PrimaryKeyField)[0].type

    # Add single-quotes for string field values
    if str(fieldType) == 'String':
    valueList = ["'%s'" % value for value in valueList]

    # Format WHERE clause in the form of an IN statement
    whereClause = "%s IN(%s)" % (fieldDelimited, ', '.join(map(str, valueList)))
    return whereClause

def selectRelatedRecords(OriginTable, DestinationTable, PrimaryKeyField, ForiegnKeyField):
    """Defines the record selection from the record selection of the OriginTable
      and applys it to the DestinationTable using a SQL WHERE clause built
      in the previous defintion"""

    # Set the SearchCursor to look through the selection of the OriginTable
    sourceIDs = set([row[0] for row in arcpy.da.SearchCursor(OriginTable, PrimaryKeyField)])

    # Establishes the where clause used to select records from DestinationTable
    whereClause = buildWhereClauseFromList(DestinationTable, ForiegnKeyField, sourceIDs)

    # Process: Select Layer By Attribute
    arcpy.SelectLayerByAttribute_management(DestinationTable, "NEW_SELECTION", whereClause)

# Process: Select related records between OriginTable and DestinationTable
selectRelatedRecords(OriginTable, DestinationTable, PrimaryKeyField, ForiegnKeyField)
usuario1714326
fuente