Sélection efficace des enregistrements associés à l'aide d'ArcPy?

14

Vous trouverez ci-dessous le code que j'utilise pour répliquer le bouton "tables associées" dans ArcMap. Dans ArcMap, ce bouton sélectionne des entités dans une classe d'entités ou une table en fonction de la sélection d'entités dans une autre classe d'entités ou table associée.

Dans ArcMap, je peux utiliser ce bouton pour "pousser" ma sélection vers la table associée en quelques secondes. Je n'ai pas pu trouver quoi que ce soit intégré à arcpy qui reproduise le bouton, j'ai donc utilisé des boucles imbriquées pour faire la même tâche.

Le code ci-dessous fait une boucle à travers un tableau de "traitements". Pour chaque traitement, il parcourt une liste "d'arbres". Lorsqu'une correspondance est trouvée entre les champs ID de traitement et les arbres, une sélection se produit dans la couche d'arbre. Une fois qu'une correspondance est trouvée pour un traitement, le code ne continue pas de rechercher dans la couche d'arborescence des correspondances supplémentaires. Il revient à la table de traitement, sélectionne le traitement suivant et effectue une nouvelle recherche dans la classe d'entités arborescentes.

Le code lui-même fonctionne bien, mais il est terriblement lent. La "table de traitement" contient dans ce cas 16 000 enregistrements. La classe d'entités "arborescence" contient 60 000 enregistrements.

Existe-t-il un autre moyen plus efficace de recréer ce que fait ESRI quand il pousse la sélection d'une table à une autre? Dois-je créer un index pour les tables? REMARQUE: ces données sont stockées dans 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
la source
2
Utilisez-vous ArcGIS 10.1? Si tel est le cas, arcpy.da.SearchCursor est susceptible d'être beaucoup plus rapide (peut-être 10 fois) que arcpy.SearchCursor. En outre, vous voudrez peut-être envisager l'utilisation d'un dictionnaire Python. Je soupçonne qu'une "sélection de fichiers clés" comme celle-ci pourrait grandement bénéficier de l'approche utilisée ici
PolyGeo
Votre base de données SDE sur Oracle est-elle un hasard?
blah238

Réponses:

12

Tout d'abord, oui, vous voudrez certainement vous assurer que vos champs de clé primaire et étrangère sont indexés sur les deux tables. Cela permet au SGBD de planifier et d'exécuter des requêtes sur ces champs beaucoup plus efficacement.

Deuxièmement, vous appelez SelectLayerByAttribute_managementdans une boucle imbriquée serrée (une fois par arbre par traitement). Ceci est très inefficace, pour plusieurs raisons:

  • Pour autant que je sache, vous n'avez pas besoin de deux boucles imbriquées l'une dans l'autre. Un suffira.
  • Les fonctions de géotraitement sont "volumineuses" et prennent beaucoup de temps à appeler par rapport aux fonctions Python intégrées typiques. Vous devez éviter de les appeler dans une boucle étroite.
  • La demande d'un enregistrement / ID à la fois entraîne des allers-retours beaucoup plus nombreux dans la base de données.

Au lieu de cela, refactorisez votre code afin d'appeler SelectLayerByAttribute_managementune seule fois avec une clause de construction conçue pour sélectionner tous les enregistrements associés.

En empruntant une fonction à une autre réponse pour la logique de construction où, je suppose que cela ressemblerait à quelque chose comme ceci:

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)

Vous pourriez l'appeler ainsi: selectRelatedRecords(treatment_tv, tree_fl, "Facility_ID", "FACILITYID")

Remarques:

  • Cela utilise un arcpy.da.SearchCursor, uniquement disponible à la version 10.1. Comme l'a mentionné @PolyGeo, ces curseurs sont beaucoup plus rapides que leurs prédécesseurs ( arcpy.SearchCursor). Il pourrait être facilement modifié pour utiliser l'ancien SearchCursor cependant:

    sourceIDs = set([row.getValue(sourceField) for row in arcpy.SearchCursor(sourceLayer, "", "", sourceField)])
  • Si votre géodatabase SDE se trouve sur Oracle, soyez averti que l' INinstruction utilisée dans la fonction de la réponse liée est limitée à 1 000 éléments. Une solution possible est décrite dans cette réponse , mais vous devrez modifier la fonction pour la diviser en plusieurs INinstructions de longueur 1000 au lieu d'une.

blah238
la source
5

La solution ci-dessus fonctionne très bien pour moi et a été très rapide. En utilisant le code ci-dessus et le code référencé de l'autre post, voici comment je l'ai construit:

# 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)
user1714326
la source