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:
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:
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)