I am trying to use an InsertCursor/SeachCursor to transfer data from one dataset to another wile incorporating a repeating tuple. The first dataset (in the below code is "Forest_Surface_Fuels") contains all the data and has attribute fields for each record that contains data for 1hr, 10hr, and 100hr fuels. What I am trying to do as I transfer this data to a empty dataset ("1_10_100") is to not only take the values in those fields, but create a master list for all records in a project and in a new field state whether the data is a 1hr, 10hr, or 100hr.
Here is the code that I have:
# Fields that data will be inserted into in the final excel table
fields = ['Plot_', 'SurveyType', 'Transect', 'OneHour', 'TenHr', 'HunHr']
#The data to be inserted (this is the tuple that will be repeated)
fuelsTuple = [
('Macroplot', "Pre", "1", 'onehr_pre_1', 'tenhr_pre_1', 'hundhr_pre_1'),
('Macroplot', "Pre", "2", 'onehr_pre_2', 'tenhr_pre_2', 'hundhr_pre_2'),
('Macroplot', "Post", "1", 'onehr_post_1', 'tenhr_post_1', 'hundhr_post_1'),
('Macroplot', "Post", "2", 'onehr_post_2', 'tenhr_post_2', 'hundhr_post_2')
]
#make count of times to repeat the tuple
repeats = arcpy.management.GetCount("Forest_Surface_Fuels")
count = int(repeats[0])
#Insert Cursor to update fields
plotCursor = arcpy.da.InsertCursor("Forest_Surface_Fuels", fuelsTuple)
with arcpy.da.SearchCursor("1_10_100", fields) as cursor:
for i in range(count):
for row in cursor:
plotCursor.insertRow(row)
del plotCursor
del cursor
When I run this, I get this error:
Screenshot 2025-12-26 155449.png
Thanks!
Solved! Go to Solution.
You’re very close, but the cursors are flipped and you’re passing the tuple list as “fields” (that’s why it blows up).
SearchCursor should read from Forest_Surface_Fuels (source)
InsertCursor should write to 1_10_100 (target)
InsertCursor("Forest_Surface_Fuels", fuelsTuple) is wrong because the 2nd argument must be a list of field names, not row data.
If your goal is: for each input feature, create 4 rows (Macroplot Pre/Post x Transect 1/2) and pull values from those named fields, do this pattern:
out_fields = ['Plot_', 'SurveyType', 'Transect', 'OneHour', 'TenHr', 'HunHr']
templates = [
("Pre","1", "onehr_pre_1","tenhr_pre_1","hundhr_pre_1"),
("Pre","2", "onehr_pre_2","tenhr_pre_2","hundhr_pre_2"),
("Post","1","onehr_post_1","tenhr_post_1","hundhr_post_1"),
("Post","2","onehr_post_2","tenhr_post_2","hundhr_post_2"),
]
# read Plot_ plus all fuel fields you reference
src_fields = ['Plot_'] + sorted({f for t in templates for f in t[2:]})
with arcpy.da.SearchCursor("Forest_Surface_Fuels", src_fields) as sc, \
arcpy.da.InsertCursor("1_10_100", out_fields) as ic:
idx = {name:i for i,name in enumerate(src_fields)}
for r in sc:
plot_id = r[idx['Plot_']]
for survey, tran, f1, f10, f100 in templates:
ic.insertRow([plot_id, survey, tran, r[idx[f1]], r[idx[f10]], r[idx[f100]]])
That gives you the “master table” (one row per plot per survey/transect) with 1/10/100 hr values populated correctly.
I'd approach this by making the field tuple a bit more complex. It seems that the first records are raw data and the last few are field values. You can just break it into two mappings like this:
# First Dictionary is direct data writes, second is field values to pull from the Origin dataset
conversion_map: list[tuple[dict[str, Any], dict[str, Any]]] = [
(
{'Plot_': 'Macroplot', 'SurveyType': 'Pre', 'Transect': '1'},
{'OneHour': 'onehr_pre_1', 'TenHr': 'tenhr_pre_1', 'HunHr': 'hundhr_pre_1'}
),
(
{'Plot_': 'Macroplot', 'SurveyType': 'Pre', 'Transect': '2'},
{'OneHour': 'onehr_pre_2', 'TenHr': 'tenhr_pre_2', 'HunHr': 'hundhr_pre_2'}
),
(
{'Plot_': 'Macroplot', 'SurveyType': 'Post', 'Transect':'1'},
{'OneHour': 'onehr_post_1', 'TenHr': 'tenhr_post_1', 'HunHr': 'hundhr_post_1'}
),
(
{'Plot_': 'Macroplot', 'SurveyType': 'Post', 'Transect':'2'},
{'OneHour': 'onehr_post_2', 'TenHr': 'tenhr_post_2', 'HunHr': 'hundhr_post_2'}
),
]That will make it clearer later what the raw data mapping is and what the extracted field values are.
When dealing with multiple cursors, I tend to like mapping the rows instead of using an index. This can be done with this incredibly simple helper function:
def as_dict(cur: SearchCursor) -> Iterator[dict[str, Any]]:
for row in cur:
yield dict(zip(cur.fields, row))You can wrap a cursor with this before iterating and you'll get back a dictionary that can be updated
Now we need to implement the mapping:
def convert(origin: str, target: str, fields: list[str], conversion_map: ConversionMap, null_value: Any=None):
with (
SearchCursor(origin, fields) as s_cur,
InsertCursor(target, fields) as i_cur
):
for row in as_dict(s_cur):
for fuel_data, fuel_fields in conversion_map:
new_row = fuel_data.copy()
new_row.update({k: row.get(v, null_value) for k, v in fuel_fields.items()})
i_cur.insertRow([new_row.get(f, null_value) for f in i_cur.fields])Here we create a context block with two cursors, one to search through the origin table ("Forest_Surface_Fuels") and one to insert into the target table ("1_10_100").
The null_value here shouldn't ever trigger, since it'll only exist if the fieldname doesn't exist in the origin table. If you don't have any 0 values, you can re-map nulls by using an or instead:
new_row.update({k: row.get(v) or null_value for k, v in fuel_fields.items()})This abuses pythons truthiness, so any value that's considered falsey will get the null_value ('', 0, False, None, etc.)
Finally, we need to put all this together into a script that doesn't pollute the global namespace with cursor references. Basically just put your logic into an if __name__ == '__main__' block:
from collections.abc import Iterator
from typing import Any
from arcpy.da import SearchCursor, InsertCursor
ConversionMap = list[tuple[dict[str, Any], dict[str, Any]]]
def as_dict(cur: SearchCursor) -> Iterator[dict[str, Any]]:
for row in cur:
yield dict(zip(cur.fields, row))
def convert(origin: str, target: str, fields: list[str], conversion_map: ConversionMap, null_value: Any=None):
with (
SearchCursor(origin, fields) as s_cur,
InsertCursor(target, fields) as i_cur
):
for row in as_dict(s_cur):
for fuel_data, fuel_fields in conversion_map:
new_row = fuel_data.copy()
new_row.update({k: row.get(v, null_value) for k, v in fuel_fields.items()})
i_cur.insertRow([new_row.get(f, null_value) for f in i_cur.fields])
if __name__ == '__main__':
# Fields that data will be inserted into in the final excel table
FIELDS = ['Plot_', 'SurveyType', 'Transect', 'OneHour', 'TenHr', 'HunHr']
ORIGIN_DATA = "Forest_Surface_Fuels"
TARGET_DATA = "1_10_100"
# Update this with the Null Value you want to use, e.g. ['' or -1 or None]
NULL_VALUE = None
# First Dictionary is direct data writes, second is field values to pull from the Origin dataset
CONVERSION_MAP: ConversionMap = [
(
# Direct Data Insertion
{'Plot_': 'Macroplot', 'SurveyType': 'Pre', 'Transect': '1'},
# Field Value Map from Origin
{'OneHour': 'onehr_pre_1', 'TenHr': 'tenhr_pre_1', 'HunHr': 'hundhr_pre_1'}
),
(
{'Plot_': 'Macroplot', 'SurveyType': 'Pre', 'Transect': '2'},
{'OneHour': 'onehr_pre_2', 'TenHr': 'tenhr_pre_2', 'HunHr': 'hundhr_pre_2'}
),
(
{'Plot_': 'Macroplot', 'SurveyType': 'Post', 'Transect':'1'},
{'OneHour': 'onehr_post_1', 'TenHr': 'tenhr_post_1', 'HunHr': 'hundhr_post_1'}
),
(
{'Plot_': 'Macroplot', 'SurveyType': 'Post', 'Transect':'2'},
{'OneHour': 'onehr_post_2', 'TenHr': 'tenhr_post_2', 'HunHr': 'hundhr_post_2'}
),
]
convert(ORIGIN_DATA, TARGET_DATA, FIELDS, CONVERSION_MAP, NULL_VALUE)
You’re very close, but the cursors are flipped and you’re passing the tuple list as “fields” (that’s why it blows up).
SearchCursor should read from Forest_Surface_Fuels (source)
InsertCursor should write to 1_10_100 (target)
InsertCursor("Forest_Surface_Fuels", fuelsTuple) is wrong because the 2nd argument must be a list of field names, not row data.
If your goal is: for each input feature, create 4 rows (Macroplot Pre/Post x Transect 1/2) and pull values from those named fields, do this pattern:
out_fields = ['Plot_', 'SurveyType', 'Transect', 'OneHour', 'TenHr', 'HunHr']
templates = [
("Pre","1", "onehr_pre_1","tenhr_pre_1","hundhr_pre_1"),
("Pre","2", "onehr_pre_2","tenhr_pre_2","hundhr_pre_2"),
("Post","1","onehr_post_1","tenhr_post_1","hundhr_post_1"),
("Post","2","onehr_post_2","tenhr_post_2","hundhr_post_2"),
]
# read Plot_ plus all fuel fields you reference
src_fields = ['Plot_'] + sorted({f for t in templates for f in t[2:]})
with arcpy.da.SearchCursor("Forest_Surface_Fuels", src_fields) as sc, \
arcpy.da.InsertCursor("1_10_100", out_fields) as ic:
idx = {name:i for i,name in enumerate(src_fields)}
for r in sc:
plot_id = r[idx['Plot_']]
for survey, tran, f1, f10, f100 in templates:
ic.insertRow([plot_id, survey, tran, r[idx[f1]], r[idx[f10]], r[idx[f100]]])
That gives you the “master table” (one row per plot per survey/transect) with 1/10/100 hr values populated correctly.
I keep getting an error, but want to confirm something first, when you define src_fields, where you have 'Plot_' that should be the corresponding field in "Forest_Surface_Fuels" to 'Plot_' in the out_fields, correct? I replaced 'Plot_' in those instances with 'Macroplot' and still get this error:
Screenshot 2025-12-28 093200.png
Yes — src_fields must be the field names in Forest_Surface_Fuels, not a value like "Macroplot". So don’t replace Plot_ with "Macroplot" in src_fields. "Macroplot" should be the value you insert, not a column name.
About the error you’re seeing:
RuntimeError: Invalid SQL syntax [near "PLOT": syntax error]
That usually happens when the target table has a field name that the underlying database treats as problematic (often a reserved word like PLOT, or a name that needs quoting). arcpy.da.InsertCursor builds an INSERT statement under the hood and can choke on that.
What to do:
Confirm the exact field name in the output table (not the alias). If it’s PLOT / Plot / Plot_, verify spelling/case.
Rename the output field to something safe like PlotID (best fix).
Re-run the cursor after renaming.
So: keep src_fields as real source column names, and fix the output field name that’s triggering SQL near PLOT.
I ended up needing to convert Forest_Surface_Fuels to a temporary layer for this to work, but it works as I hoped, so thank you!
I'd approach this by making the field tuple a bit more complex. It seems that the first records are raw data and the last few are field values. You can just break it into two mappings like this:
# First Dictionary is direct data writes, second is field values to pull from the Origin dataset
conversion_map: list[tuple[dict[str, Any], dict[str, Any]]] = [
(
{'Plot_': 'Macroplot', 'SurveyType': 'Pre', 'Transect': '1'},
{'OneHour': 'onehr_pre_1', 'TenHr': 'tenhr_pre_1', 'HunHr': 'hundhr_pre_1'}
),
(
{'Plot_': 'Macroplot', 'SurveyType': 'Pre', 'Transect': '2'},
{'OneHour': 'onehr_pre_2', 'TenHr': 'tenhr_pre_2', 'HunHr': 'hundhr_pre_2'}
),
(
{'Plot_': 'Macroplot', 'SurveyType': 'Post', 'Transect':'1'},
{'OneHour': 'onehr_post_1', 'TenHr': 'tenhr_post_1', 'HunHr': 'hundhr_post_1'}
),
(
{'Plot_': 'Macroplot', 'SurveyType': 'Post', 'Transect':'2'},
{'OneHour': 'onehr_post_2', 'TenHr': 'tenhr_post_2', 'HunHr': 'hundhr_post_2'}
),
]That will make it clearer later what the raw data mapping is and what the extracted field values are.
When dealing with multiple cursors, I tend to like mapping the rows instead of using an index. This can be done with this incredibly simple helper function:
def as_dict(cur: SearchCursor) -> Iterator[dict[str, Any]]:
for row in cur:
yield dict(zip(cur.fields, row))You can wrap a cursor with this before iterating and you'll get back a dictionary that can be updated
Now we need to implement the mapping:
def convert(origin: str, target: str, fields: list[str], conversion_map: ConversionMap, null_value: Any=None):
with (
SearchCursor(origin, fields) as s_cur,
InsertCursor(target, fields) as i_cur
):
for row in as_dict(s_cur):
for fuel_data, fuel_fields in conversion_map:
new_row = fuel_data.copy()
new_row.update({k: row.get(v, null_value) for k, v in fuel_fields.items()})
i_cur.insertRow([new_row.get(f, null_value) for f in i_cur.fields])Here we create a context block with two cursors, one to search through the origin table ("Forest_Surface_Fuels") and one to insert into the target table ("1_10_100").
The null_value here shouldn't ever trigger, since it'll only exist if the fieldname doesn't exist in the origin table. If you don't have any 0 values, you can re-map nulls by using an or instead:
new_row.update({k: row.get(v) or null_value for k, v in fuel_fields.items()})This abuses pythons truthiness, so any value that's considered falsey will get the null_value ('', 0, False, None, etc.)
Finally, we need to put all this together into a script that doesn't pollute the global namespace with cursor references. Basically just put your logic into an if __name__ == '__main__' block:
from collections.abc import Iterator
from typing import Any
from arcpy.da import SearchCursor, InsertCursor
ConversionMap = list[tuple[dict[str, Any], dict[str, Any]]]
def as_dict(cur: SearchCursor) -> Iterator[dict[str, Any]]:
for row in cur:
yield dict(zip(cur.fields, row))
def convert(origin: str, target: str, fields: list[str], conversion_map: ConversionMap, null_value: Any=None):
with (
SearchCursor(origin, fields) as s_cur,
InsertCursor(target, fields) as i_cur
):
for row in as_dict(s_cur):
for fuel_data, fuel_fields in conversion_map:
new_row = fuel_data.copy()
new_row.update({k: row.get(v, null_value) for k, v in fuel_fields.items()})
i_cur.insertRow([new_row.get(f, null_value) for f in i_cur.fields])
if __name__ == '__main__':
# Fields that data will be inserted into in the final excel table
FIELDS = ['Plot_', 'SurveyType', 'Transect', 'OneHour', 'TenHr', 'HunHr']
ORIGIN_DATA = "Forest_Surface_Fuels"
TARGET_DATA = "1_10_100"
# Update this with the Null Value you want to use, e.g. ['' or -1 or None]
NULL_VALUE = None
# First Dictionary is direct data writes, second is field values to pull from the Origin dataset
CONVERSION_MAP: ConversionMap = [
(
# Direct Data Insertion
{'Plot_': 'Macroplot', 'SurveyType': 'Pre', 'Transect': '1'},
# Field Value Map from Origin
{'OneHour': 'onehr_pre_1', 'TenHr': 'tenhr_pre_1', 'HunHr': 'hundhr_pre_1'}
),
(
{'Plot_': 'Macroplot', 'SurveyType': 'Pre', 'Transect': '2'},
{'OneHour': 'onehr_pre_2', 'TenHr': 'tenhr_pre_2', 'HunHr': 'hundhr_pre_2'}
),
(
{'Plot_': 'Macroplot', 'SurveyType': 'Post', 'Transect':'1'},
{'OneHour': 'onehr_post_1', 'TenHr': 'tenhr_post_1', 'HunHr': 'hundhr_post_1'}
),
(
{'Plot_': 'Macroplot', 'SurveyType': 'Post', 'Transect':'2'},
{'OneHour': 'onehr_post_2', 'TenHr': 'tenhr_post_2', 'HunHr': 'hundhr_post_2'}
),
]
convert(ORIGIN_DATA, TARGET_DATA, FIELDS, CONVERSION_MAP, NULL_VALUE)