Selecionando com eficiência registros relacionados usando o ArcPy?

14

Abaixo está o código que estou usando para replicar o botão "tabelas relacionadas" no ArcMap. No ArcMap, esse botão seleciona recursos em uma classe ou tabela de recursos com base na seleção de recursos em outra classe ou tabela de recursos relacionados.

No ArcMap, posso usar esse botão para "empurrar" minha seleção para a tabela relacionada em questão de segundos. Não consegui encontrar nada embutido no arcpy que replica o botão, então usei alguns loops aninhados para fazer a mesma tarefa.

O código abaixo percorre uma tabela de "tratamentos". Para cada tratamento, ele percorre uma lista de "árvores". Quando uma correspondência é encontrada entre os campos de tratamento e árvores de ID, ocorre uma seleção na camada de árvore. Depois que uma correspondência é encontrada para um tratamento, o código não continua a procurar na camada da árvore por correspondências adicionais. Volta para a tabela de tratamento, seleciona o próximo tratamento e pesquisa novamente na classe de recurso de árvore.

O código em si funciona bem, mas é extremamente lento. A "tabela de tratamento" neste caso possui 16.000 registros. A classe de recurso "árvore" possui 60.000 registros.

Existe outra maneira mais eficiente de recriar o que a ESRI está fazendo quando empurra a seleção de uma tabela para outra? Devo estar criando um índice para as tabelas? NOTA: Esses dados são armazenados em uma 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
fonte
2
Você está usando o ArcGIS 10.1? Nesse caso, o arcpy.da.SearchCursor provavelmente será muito mais rápido (talvez 10X) que o arcpy.SearchCursor. Além disso, você pode considerar o uso de um dicionário Python. Eu suspeito que uma "seleção de arquivo de chave" como essa possa se beneficiar muito com a abordagem usada aqui
PolyGeo
Seu banco de dados SDE está no Oracle por acaso?
blah238

Respostas:

12

Primeiro, sim, você definitivamente desejará garantir que seus campos de chave primária e externa sejam indexados nas duas tabelas. Isso permite que o DBMS planeje e execute consultas nesses campos com muito mais eficiência.

Em segundo lugar, você está chamando SelectLayerByAttribute_managementum loop aninhado (uma vez por árvore por tratamento). Isso é altamente ineficiente, por várias razões:

  • Você não precisa de dois loops, um aninhado no outro, para isso, até onde eu sei. Um será suficiente.
  • As funções de geoprocessamento são "robustas" e demoram muito tempo para serem chamadas, em comparação com as funções internas típicas do Python. Você deve evitar chamá-los em um loop apertado.
  • Pedir um registro / ID por vez resulta em muito mais viagens de ida e volta ao banco de dados.

Em vez disso, refatorar seu código para que você chame SelectLayerByAttribute_managementapenas uma vez com uma cláusula where construída para selecionar todos os registros relacionados.

Tomando emprestada uma função de outra resposta para a lógica de construção em que estamos, eu imaginaria que seria algo parecido com isto:

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)

Você poderia chamar assim: selectRelatedRecords(treatment_tv, tree_fl, "Facility_ID", "FACILITYID")

Notas:

  • Isso usa um arcpy.da.SearchCursor, disponível apenas na 10.1. Como o @PolyGeo mencionou, esses cursores são muito mais rápidos que seus antecessores ( arcpy.SearchCursor). Poderia ser facilmente modificado para usar o antigo SearchCursor:

    sourceIDs = set([row.getValue(sourceField) for row in arcpy.SearchCursor(sourceLayer, "", "", sourceField)])
  • Se o seu geodatabase da SDE estiver no Oracle, saiba que a INdeclaração usada na função da resposta vinculada é limitada a 1000 elementos. Uma solução possível é descrita nesta resposta , mas você teria que modificar a função para dividi-la em várias INinstruções de 1000 comprimentos , em vez de uma.

blah238
fonte
5

A solução acima funciona muito bem para mim e foi muito rápida. Usando o código acima e o código referenciado do outro post, é assim que eu o 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)
user1714326
fonte