Forked from nickrsan/ArcGISPermanentOneToOneJoin.py
Last active
November 16, 2022 17:43
-
-
Save alexfriant/c553d9a8e81e0c99a42d to your computer and use it in GitHub Desktop.
Provides a way to permanently attach a field to another table, as in a one to one join, but without performing a join then exporting a new dataset. Operates in place by creating a new field on the existing dataset.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import arcpy | |
def permanent_join(target_table, target_attribute, source_table, source_attribute, attribute_to_attach, rename_attribute=None): | |
""" | |
Attaches a field to a dataset in place in ArcGIS - instead of the alternative of doing an actual join and then saving out a new dataset | |
Only works as a one to one join. | |
:param target_table: the table to attach the joined attribute to | |
:param target_attribute: the attribute in the table to base the join on | |
:param source_table: the table containing the attribute to join | |
:param source_attribute: the attribute in table2 to base the join on | |
:param attribute_to_attach: the attribute to attach to table 1 | |
:param rename_attribute: string to indicate what to rename the field as when it's joined. | |
:return: None | |
""" | |
# first, we need to find the information about the field that we're attaching | |
join_table_fields = arcpy.ListFields(source_table) | |
for field in join_table_fields: | |
if field.name == attribute_to_attach: # we found our attribute | |
base_field = field | |
break | |
else: | |
raise ValueError("Couldn't find field to base join on in source table") | |
type_mapping = {"Integer": "LONG", "OID": "LONG", "SmallInteger": "SHORT", "String": "TEXT"} # ArcGIS annoyingly doesn't report out the same data types as you need to provide, so this allows us to map one to the other | |
if base_field.type in type_mapping.keys(): # if it's a type that needs conversion | |
new_type = type_mapping[base_field.type] # look it up and save it | |
else: | |
new_type = base_field.type.upper() # otherwise, just grab the exact type as specified | |
if rename_attribute: | |
new_name = rename_attribute | |
else: | |
new_name = base_field.name | |
# copy the field over other than those first two attributes | |
arcpy.AddField_management(target_table, new_name, new_type, field.precision, field.scale, field.length, field_alias=None, field_is_nullable="NULLABLE", field_is_required=field.required, field_domain=field.domain) | |
arcpy.AddMessage("New field '{0}' added to {1}".format(new_name,target_table)) | |
join_data = read_field_to_dict(source_table, attribute_to_attach, source_attribute) # look up these values so we can easily just use one cursor at a time - first use the search cursor, then the update cursor on the new table | |
updater = arcpy.UpdateCursor(target_table) | |
for row in updater: | |
if row.getValue(target_attribute) in join_data.keys(): # since we might not always have a match, we need to check, this should speed things up too | |
row.setValue(new_name, join_data[row.getValue(target_attribute)]) # set the value for the new field to the other table's value for that same field, indexed by key | |
updater.updateRow(row) | |
del updater | |
arcpy.AddMessage("\nYour table '{0}' now has the '{1}' field from the '{2}' table appended as the field called '{3}'.\n\n".format(arcpy.Describe(target_table).name, attribute_to_attach, arcpy.Describe(source_table).name, new_name)) | |
arcpy.AddMessage("REMEMBER TO REFRESH YOUR TABLE TO SEE THE RESULTS!!\n\n") | |
def read_field_to_dict(input_table, data_field, key_field): | |
""" | |
Given an arcgis table and a field containing keys and values, reads that field into a dict based on the key field | |
:param table: an ArcGIS table (or feature class, etc) | |
:param data_field: the field that contains the data of interest - these values will be the dictionary values | |
:param key_field: the field that contains the keys/pkey values - these values will be the keys in the dictionary | |
:return: dict of the data loaded from the table | |
""" | |
data_dict = {} | |
rows = arcpy.SearchCursor(input_table) | |
for row in rows: | |
data_dict[row.getValue(key_field)] = row.getValue(data_field) | |
del rows | |
return data_dict |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
######################################################################################### | |
# | |
# Requirements: You'll need ArcGIS Desktop 10.1 or higher with Python 2.7+ | |
# | |
# Make sure you have the "ArcGISPermanentOneToOneJoin.py" file in the same directory | |
# as this script. | |
# | |
# This script is designed to run as an ArcToolbox script, so add this script to an | |
# ArcToolbox of your choice, and make sure to add the required parameters for that | |
# script reference. | |
# | |
######################################################################################### | |
import arcpy | |
from ArcGISPermanentOneToOneJoin import permanent_join, read_field_to_dict | |
##ArcToolbox Script Input Parameters (suggested script parameter names are in the comments) | |
#The target table you want to join a field to: | |
#For Data_Type, choose "Feature Class" | |
target_table = arcpy.GetParameterAsText(0) | |
#The field name in your target table to base the join on: | |
#For Parameter_Properties.Obtained_From, choose the target table parameter | |
target_attribute = arcpy.GetParameterAsText(1) | |
#The source table containing the field you want to join to the target table: | |
#For Data_Type, choose "Feature Class" | |
source_table = arcpy.GetParameterAsText(2) | |
#The field name in your source table to base the join on: | |
#For Parameter_Properties.Obtained_From, choose the source table parameter | |
source_attribute = arcpy.GetParameterAsText(3) | |
#The field you want to copy over to the target table: | |
#For Parameter_Properties.Obtained_From, choose the source table parameter | |
attribute_to_attach = arcpy.GetParameterAsText(4) | |
#If you need to rename the field, give it a new name here: | |
#For Parameter_Properties.Type, choose "Optional" | |
rename_attribute = arcpy.GetParameterAsText(5) | |
arcpy.AddMessage("Parameters gathered.") | |
##Attempt to execute the join function | |
try: | |
permanent_join(target_table, target_attribute, source_table, source_attribute, attribute_to_attach, rename_attribute) | |
except NameError as e: | |
arcpy.AddMessage("There was a Name Error: {0}".format(e)) | |
raise | |
except: | |
arcpy.AddMessage("There was an error: {0}".format(sys.exc_info()[0])) | |
raise |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
ArcGIS Pro now offers this functionality out of the box with their Data Engineering tools > Integrate > Join