How to Join Two Tables in ArcGIS Pro
Joining two tables in ArcGIS connects records that share a common attribute, such as a geographic identifier, parcel number, customer ID, or station code. After the join, fields from the second table become available through the target layer or table. You can use those fields to symbolize a map, label features, filter records, calculate new values, or run further analysis.
This guide explains how to join tables in ArcGIS Pro using the interface and ArcPy. It also explains when to use a temporary Add Join or a permanent Join Field, how to handle duplicate keys, how to validate the match, and how to troubleshoot missing records.
Short answer: Add the join to a layer or table view when you need a temporary connection. Use
Join Fieldor export the joined layer when you need a permanent copy of the joined fields.
What is a table join in ArcGIS?
An attribute join matches records in one table to records in another table using two fields with corresponding values. The field names do not have to be identical, but the values must match and the field types must be compatible.
For example, a polygon layer may contain:
| GEOID | County_Name |
|---|---|
| 1001 | Alpha County |
| 1003 | Beta County |
A separate population table may contain:
| County_Code | Population |
|---|---|
| 1001 | 245000 |
| 1003 | 118000 |
Joining County_Code to GEOID makes the population field available in the county layer. The join works even though the fields have different names because their values identify the same records.
A table join does not use geometry. It is based on attribute values. If you want to match features by location, use a spatial join instead.
Attribute join versus spatial join
An attribute join uses a common field such as ID, GEOID, PARCEL_NO, or STATION_ID. It is appropriate when the two datasets already contain a shared identifier.
A spatial join matches features by their spatial relationship, such as within, intersecting, nearest, or contained by. It is appropriate when there is no common identifier and the relationship must be inferred from geometry.
For example:
- Join a CSV of county populations to county polygons using
GEOID: attribute join. - Count schools within each district polygon: spatial join.
- Join a table of survey results to points using
SITE_ID: attribute join.
Do not use a spatial join simply because the target dataset is geographic. If a reliable key exists, an attribute join is usually easier to audit and reproduce.
Prepare the two tables before joining
Most join failures are caused by the data rather than by ArcGIS. Before opening the Join dialog, check the following:
- Identify the target table. This is the table or layer that should receive fields from the other table.
- Identify the join table. This contains the additional fields.
- Choose the target join field and the corresponding join field.
- Confirm that the values use the same format.
- Check whether the join field is unique in the join table.
- Remove accidental spaces, inconsistent capitalization, and formatting differences.
- Check that IDs stored as text are not being compared with IDs stored as numbers.
- Preserve leading zeros when they are part of the identifier.
A code such as 00125 should usually be stored as text. If one dataset stores it as the number 125, a direct join may fail or may lose the leading zeros. Convert both fields to a documented common format before joining.
Field names do not need to match, but the field types and values must be compatible. ArcGIS supports joins based on text, date, and numeric fields. Text joins are case sensitive according to the current Join Field documentation. 1
Join two tables in the ArcGIS Pro interface
Step 1: Add the datasets
Add the target feature layer or table and the join table to the ArcGIS Pro map or project. A CSV, Excel worksheet, geodatabase table, shapefile attribute table, or feature class can be used when supported by the workspace and data type.
Step 2: Open the Join tool
In the Contents pane, right-click the target layer and choose Joins and Relates > Add Join. The exact menu wording can vary slightly by ArcGIS Pro release.
Step 3: Choose the target field
Select the field in the target layer that contains the identifier. For a county layer this might be GEOID; for a parcel layer it might be PARCEL_ID.
Step 4: Choose the join table and field
Select the table containing the fields to add, then select its matching field. The field names can differ as long as the values correspond.
Step 5: Choose the join type
Use Keep all records when every target feature should remain in the layer, including features with no matching row. Unmatched fields are empty or null.
Use Keep only matching records when the layer should contain only target records with a match in the join table. This behaves like a filtered result and can be useful for analysis, but it may hide unmatched features if you expected a complete layer.
Step 6: Validate and run
Use the validation option when available. Review the number of matching records, the join relationship, and warnings about field names or Object ID fields. Run the join and open the target attribute table.
Joined fields usually appear with a table or layer prefix. This prevents collisions when both datasets contain a field with the same name.
Temporary joins with Add Join
Add Join adds a join to a layer or table view. The join is stored with the layer or view rather than permanently changing the source dataset. It is useful for exploration, mapping, selection, and short workflows.
import arcpy
counties = r"C:\GIS_Data\Project\boundaries.gdb\counties"
population_table = r"C:\GIS_Data\Project\tables.gdb\county_population"
arcpy.management.MakeFeatureLayer(counties, "counties_layer" )
arcpy.management.AddJoin(
in_layer_or_view="counties_layer",
in_field="GEOID",
join_table=population_table,
join_field="COUNTY_CODE",
join_type="KEEP_ALL"
)
print("Temporary join added to counties_layer")
KEEP_ALL retains every target record. KEEP_COMMON retains only target records that have a matching join record. The temporary join can later be removed with RemoveJoin.
arcpy.management.RemoveJoin("counties_layer")
When processing a joined layer with tools such as Copy Features or Export Features, verify which fields are carried into the output and how duplicate field names are qualified. A temporary join is not the same as updating the source geodatabase table.
Make the join permanent with Join Field
Use Join Field when fields from the join table should be written into the input table or feature class. This updates the input dataset, so make a backup or create a copy when the original data must remain unchanged.
import arcpy
input_features = r"C:\GIS_Data\Project\boundaries.gdb\counties"
join_table = r"C:\GIS_Data\Project\tables.gdb\county_population"
arcpy.management.JoinField(
in_data=input_features,
in_field="GEOID",
join_table=join_table,
join_field="COUNTY_CODE",
fields=["POPULATION", "MEDIAN_INCOME"]
)
The fields list limits the transfer to the columns needed by the project. If it is omitted, ArcGIS can transfer all eligible fields from the join table. Selecting only the required fields reduces clutter and avoids accidental name collisions.
The input field and join field can have different names. They can be text, date, or numeric fields, provided their values match. A join-table field with the same name as an input field may receive a suffix such as _1 to make it unique. 1
Export a temporary join to a permanent dataset
Another workflow is to add a temporary join, then export the joined layer. This preserves the original target data while producing a new feature class.
import arcpy
counties = r"C:\GIS_Data\Project\boundaries.gdb\counties"
population_table = r"C:\GIS_Data\Project\tables.gdb\county_population"
output_features = r"C:\GIS_Data\Project\results.gdb\counties_with_population"
arcpy.management.MakeFeatureLayer(counties, "counties_layer")
arcpy.management.AddJoin(
"counties_layer",
"GEOID",
population_table,
"COUNTY_CODE",
"KEEP_ALL"
)
arcpy.management.CopyFeatures("counties_layer", output_features)
This approach is useful when the output should contain geometry and joined fields but the source feature class should not be modified. Check the output field names after export because qualified names and duplicate fields may be renamed by the workspace.
One-to-one and one-to-many joins
A one-to-one join has at most one matching record in the join table for each target key. A county population table should normally have one row per county. This is the simplest and most predictable relationship.
A one-to-many relationship has multiple join-table records for one target key. For example, a county may have many annual population records, or a parcel may have several inspection records.
A regular attribute join may not display every related record as separate target rows. If the join table contains duplicate key values, the result depends on the tool, workspace, and transfer method. For a summary such as total inspections, first summarize the join table by key and then join the summary table. For detailed related records, consider a relate, a relationship class, or a one-to-many workflow supported by the relevant tool and workspace.
A safe summary pattern is:
- Aggregate the many-record table by the join key.
- Calculate totals, counts, minimums, maximums, or latest dates.
- Join the one-row-per-key summary to the target layer.
This prevents a one-to-many table from creating ambiguous or incomplete attributes.
Summarize a table before joining it
Suppose inspections contains several rows per PARCEL_ID. Create a summary table first.
import arcpy
inspections = r"C:\GIS_Data\Project\tables.gdb\inspections"
summary = r"C:\GIS_Data\Project\tables.gdb\inspection_summary"
arcpy.analysis.Statistics(
in_table=inspections,
out_table=summary,
statistics_fields=[
["INSPECTION_ID", "COUNT"],
["SCORE", "MEAN"],
["INSPECTION_DATE", "MAX"]
],
case_field=["PARCEL_ID"]
)
The exact output field names depend on the statistic and source field. Inspect the summary table, then join its PARCEL_ID field to the parcel layer. This produces a clear one-to-one relationship between each parcel and its summarized inspection data.
Validate a join before relying on it
ArcGIS Pro includes a Validate Join tool that can check field names, Object ID requirements, matching records, and whether the relationship is one-to-one or one-to-many. Use it when a join is important or when the input tables come from different systems.
import arcpy
result = arcpy.management.ValidateJoin(
input_layer="counties_layer",
input_field="GEOID",
join_table=population_table,
join_field="COUNTY_CODE"
)
print(result)
You can also perform simple checks with a cursor. Count distinct keys in the join table and compare them with the target table. Look for null keys and duplicate values.
import arcpy
from collections import Counter
join_table = r"C:\GIS_Data\Project\tables.gdb\county_population"
keys = []
with arcpy.da.SearchCursor(join_table, ["COUNTY_CODE"]) as rows:
for (key,) in rows:
if key is not None:
keys.append(key)
counts = Counter(keys)
duplicates = {key: count for key, count in counts.items() if count > 1}
print(f"Unique non-null keys: {len(counts)}")
print(f"Duplicate keys: {len(duplicates)}")
A duplicate count of zero is often expected for a one-to-one lookup table. If duplicates are intended, decide how they should be summarized before joining.
Clean and standardize join fields
If one table stores numeric IDs and the other stores text IDs, create standardized fields. Do not convert a field without considering leading zeros and the maximum value size.
import arcpy
input_table = r"C:\GIS_Data\Project\boundaries.gdb\counties"
field_name = "GEOID_TEXT"
field_names = {field.name.upper() for field in arcpy.ListFields(input_table)}
if field_name.upper() not in field_names:
arcpy.management.AddField(input_table, field_name, "TEXT", field_length=20)
arcpy.management.CalculateField(
input_table,
field_name,
"str(!GEOID!).zfill(5) if !GEOID! is not None else None",
"PYTHON3"
)
Apply the same logic to the other table, using the required width and format. For text values, trim spaces and standardize case only when that does not merge distinct identifiers.
Index join fields for performance
Indexes can improve join performance, especially for large geodatabase tables. Add an attribute index to both the target and join fields when the same fields are used repeatedly.
The Join Field tool has an index option. You can also add indexes directly:
import arcpy
arcpy.management.AddIndex(
in_table=input_features,
fields=["GEOID"],
index_name="idx_counties_geoid"
)
arcpy.management.AddIndex(
in_table=join_table,
fields=["COUNTY_CODE"],
index_name="idx_population_code"
)
Indexing does not fix mismatched values or incorrect field types. It improves lookup performance; it does not change the join logic.
Common problems and solutions
No records match
Check that the fields contain the same values, not just similar-looking values. Look for leading zeros, spaces, different case, numeric-versus-text storage, nulls, and different identifier versions.
Some records match but others do not
Use a left or keep-all join and inspect the unmatched keys. Export the unmatched target records and compare them with the source table. This often reveals a formatting or data-refresh issue.
Duplicate records appear
The join field is not unique in the join table, or the workflow is producing a one-to-many result. Count duplicate keys and summarize the table before joining if one output row per target feature is required.
Fields have prefixes or suffixes
ArcGIS qualifies fields to distinguish fields from the target and join datasets. Duplicate field names may receive suffixes. Rename fields in a copy or use field mapping when the output schema must be controlled.
The join disappears after closing the project
A join created with Add Join is associated with a layer or table view. Save the layer or export the joined data if the relationship must persist. Use Join Field for a permanent field transfer.
The join field is unavailable
Some system fields cannot be used or transferred in the same way as ordinary user fields. Confirm that the selected field exists in the actual dataset, not only as a field alias, and inspect the data type in the Fields view.
The output is slow
Reduce the transferred fields, use a file geodatabase, add indexes, filter to the required records, and summarize one-to-many tables before joining. Avoid repeatedly joining very large CSV files directly from a network location.
ArcGIS Pro versus ArcMap
The concepts are similar in ArcGIS Pro and ArcMap: identify a target, identify a join table, select matching fields, and decide whether the result should be temporary or permanent. The interface labels and Python environments differ.
ArcGIS Pro uses Python 3, while older ArcMap installations commonly use Python 2.7. ArcPy scripts written for one environment may require syntax and package changes in the other. Use the documentation for the exact application version and test the script with a representative copy of the data.
When should you use a relate instead?
Use a relate when one target record can have many related records and you need to preserve the relationship rather than flattening the data into fields. For example, a parcel may have many inspections, or a customer may have many service events. A relate lets you navigate between related records without pretending that several records can be represented by one set of columns.
Use a join when the relationship is suitable for adding attributes to the target table. Use a summary table when the many-side records can be reduced to meaningful totals, averages, counts, or latest values.
Quality-control checklist
Before publishing or analyzing a joined layer, verify:
- the target table and join table are clearly identified;
- the chosen fields contain the intended identifier;
- field types and formatting are compatible;
- null and duplicate keys have been reviewed;
- the join type is appropriate;
- the number of matches is documented;
- transferred fields have meaningful names and units;
- the output was exported or written permanently when required; and
- the original data was preserved before destructive updates.
Open the output table and inspect several matched and unmatched records. A join that runs without an error can still be logically wrong if the key represents different versions of the data in the two sources.
Frequently asked questions
How do I join two tables in ArcGIS Pro?
Add both tables to the project, right-click the target layer, choose Joins and Relates > Add Join, select the target field and join field, choose the join type, validate the match, and run the tool.
What is the difference between Add Join and Join Field?
Add Join creates a join on a layer or table view and is commonly temporary. Join Field writes selected fields from the join table into the input dataset, creating a permanent field transfer.
Do the join fields need the same name?
No. They need compatible values and data types. For example, GEOID can match COUNTY_CODE when both store the same identifiers.
Why are some records missing after the join?
The keys may not match because of spaces, capitalization, leading zeros, null values, or numeric-versus-text storage. Use a keep-all join and inspect the unmatched records.
Can I join a CSV to a shapefile in ArcGIS Pro?
Yes, provided both datasets can be read by ArcGIS and the join fields contain compatible values. For repeated or large workflows, consider importing the CSV into a file geodatabase first.
How do I make an ArcGIS join permanent?
Use Join Field, or add a join to a layer and export the joined layer to a new feature class or table. A temporary Add Join does not permanently modify the source data.
What happens when the join table has duplicate IDs?
The result may be ambiguous or behave as a one-to-many relationship depending on the tool and workspace. Summarize the join table by ID first when the target should have one output row per record.
Should I use a spatial join instead?
Use a spatial join when the match is based on location rather than a common attribute. If a reliable identifier exists, use an attribute join.
Summary
Joining tables in ArcGIS is straightforward when the target, join table, key fields, relationship type, and output behavior are defined before running the tool. Use Add Join for a temporary layer connection, Join Field for a permanent transfer, and a relate or summary workflow when the join table contains multiple records per target key.
The most important quality checks are matching field types, consistent values, duplicate-key detection, validation, and inspection of unmatched records. These checks prevent a technically successful join from producing misleading analysis.