Czy skutecznie wybierasz powiązane rekordy za pomocą ArcPy?

14

Poniżej znajduje się kod, którego używam do replikacji przycisku „powiązanych tabel” w ArcMap. W ArcMap ten przycisk wybiera obiekty w jednej klasie obiektów lub tabeli na podstawie wyboru obiektów w innej pokrewnej klasie obiektów lub tabeli.

W ArcMap mogę użyć tego przycisku, aby „wcisnąć” mój wybór do odpowiedniej tabeli w ciągu kilku sekund. Nie mogłem znaleźć niczego wbudowanego w arcpy, które replikuje przycisk, więc użyłem zagnieżdżonych pętli do wykonania tego samego zadania.

Poniższy kod zapętla tabelę „zabiegów”. Przy każdym zabiegu przegląda listę „drzew”. Po znalezieniu dopasowania między polami leczenia i drzewami następuje selekcja w warstwie drzewa. Po znalezieniu dopasowania dla leczenia kod nie kontynuuje przeszukiwania warstwy drzewa w poszukiwaniu dodatkowych dopasowań. Wraca do tabeli leczenia, wybiera następny zabieg i ponownie przeszukuje klasę drzewa.

Sam kod działa dobrze, ale jest bolesnie wolny. „Tabela leczenia” w tym przypadku ma 16 000 rekordów. Klasa obiektów „drzewo” ma 60 000 rekordów.

Czy istnieje inny bardziej skuteczny sposób na odtworzenie tego, co robi ESRI, gdy przesuwa zaznaczenie z jednej tabeli do drugiej? Czy powinienem tworzyć indeks dla tabel? UWAGA: Te dane są przechowywane w 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
źródło
2
Czy korzystasz z ArcGIS 10.1? Jeśli tak, arcpy.da.SearchCursor prawdopodobnie będzie znacznie szybszy (być może 10X) niż arcpy.SearchCursor. Możesz także rozważyć użycie słownika Python. Podejrzewam, że „wybór pliku klucza” może znacznie skorzystać z zastosowanego tutaj
PolyGeo
Czy Twoja baza danych SDE na Oracle jest przypadkiem?
blah238

Odpowiedzi:

12

Po pierwsze, tak, na pewno będziesz chciał się upewnić, że pola klucza głównego i klucza obcego są indeksowane w obu tabelach. Pozwala to DBMS planować i wykonywać zapytania dotyczące tych pól znacznie wydajniej.

Po drugie, wzywamy SelectLayerByAttribute_managementw ciasną, zagnieżdżoną pętlę (raz na drzewo na zabieg). Jest to wysoce nieefektywne z kilku powodów:

  • O ile mi wiadomo, nie potrzebujesz do tego dwóch pętli, jedna zagnieżdżona w drugiej. Wystarczy.
  • Funkcje geoprzetwarzania są „masywne” i wymagają dużo czasu na wywołanie w porównaniu do typowych wbudowanych funkcji Pythona. Powinieneś unikać dzwonienia do nich w ciasnej pętli.
  • Pytanie o jeden rekord / identyfikator naraz powoduje znacznie więcej podróży w obie strony do bazy danych.

Zamiast tego SelectLayerByAttribute_managementdokonaj refaktoryzacji kodu, aby zadzwonić tylko raz za pomocą skryptu, w którym zaznaczono wszystkie powiązane rekordy.

Pożyczając funkcję z innej odpowiedzi dla logiki konstrukcji whereclause, wyobrażam sobie, że wyglądałoby to tak:

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)

Możesz to tak nazwać: selectRelatedRecords(treatment_tv, tree_fl, "Facility_ID", "FACILITYID")

Uwagi:

  • To używa arcpy.da.SearchCursor, dostępne tylko w 10.1. Jak wspomniano @PolyGeo, kursory te są znacznie szybsze niż ich poprzednicy ( arcpy.SearchCursor). Można go jednak łatwo zmodyfikować, aby używał starego SearchCursor:

    sourceIDs = set([row.getValue(sourceField) for row in arcpy.SearchCursor(sourceLayer, "", "", sourceField)])
  • Jeśli twoja geobaza SDE znajduje się na Oracle, ostrzeż, że INinstrukcja użyta w funkcji z połączonej odpowiedzi jest ograniczona do 1000 elementów. Jedno z możliwych rozwiązań jest opisane w tej odpowiedzi , ale trzeba by zmodyfikować funkcję, aby podzielić ją na wiele INinstrukcji o długości 1000 zamiast jednej.

blah238
źródło
5

Powyższe rozwiązanie działa dla mnie świetnie i było bardzo szybkie. Używając powyższego kodu i kodu z drugiego postu, zbudowałem go w następujący sposób:

# 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)
użytkownik1714326
źródło