Hello,
I'm trying to create an attribute rule that will create a sequential count of features in one field (NumberID) based on their value in another field (Category). An example is below. Whenever a feature is inserted, updated, or deleted, I'd like the counts in the NumberID field to automatically recalculate based on the occurrences of the values in the Category field. I want the count for each category to start at 1, so a geodatabase sequence doesn't seem to be the way to go. The previous NumberID values don't matter. In other words, it doesn't matter if a feature numbered "1" later becomes "2" as long as every feature in that category is numbered sequentially without skipping any numbers.
Category NumberID
Category1 1
Category1 2
Category1 3
Category2 1
Category3 1
Category3 2
I had some success modifying the solution in the post below to create the counts, but it doesn't recalculate the counts when I delete features. I'd like the NumberID values to recalculate whenever there's an edit so each feature is always numbered 1, 2, 3, etc. by each category.
Solved: Re: Sequentially/incrementally numbering a data po... - Esri Community
Does anyone have any suggestions for how to do this? Thank you!
I'm not sure this approach is feasible, as it would likely create an infinite update loop, resulting in the error:
"The evaluation of attribute rules is cyclic or exceeds maximum cascading level."
For example, if your attribute rule is set to trigger on INSERT, UPDATE, and DELETE, any update will recursively trigger the rule again.
Consider this scenario:
You have two features, Feature_1 and Feature_2, both categorized as "Category_A", with NumberID values of 1 and 2. When you insert a new feature, Feature_3, also with "Category_A", your script filters all features in that category (now totaling 3) and recalculates their NumberID values in sequence.
This update then triggers the rule again for each feature whose NumberID was changed—causing a loop.
Even if you try to avoid updates by checking whether a feature’s NumberID already matches the expected sequence, eventually a change will occur that re-triggers the rule, continuing the loop.
So unless you implement very strict controls to prevent any form of cyclic triggering, this logic is prone to unintended recursion.
This is an example of code you could start with but it results in the cyclic triggering.
var numberID_field = "NumberID";
var updates = [];
var cnt = 0;
var cat = $feature.Category;
if ($editcontext.editType == "DELETE") {
cat = $originalFeature.Category;
}
var myFeature = FeatureSetByName($datastore, "{Your feature}", ['*'], false);
var features_with_same_category = Filter(myFeature, "Category = @Cat")
for (var f in features_with_same_category) {
cnt += 1;
Push(updates, {
'globalID': f.GlobalID,
'attributes': Dictionary(numberID_field, cnt)
});
}
return {
'edit': [{'className': 'Your feature', 'updates': updates}
]
}
Thanks for looking into this and for the info, Jake. I'm new to arcade. I think I follow how a loop would result with the update trigger. I tried the code with just insert and delete as triggers, and while it calculated new NumberID values when I created new features, the numbers didn't update when I deleted a feature. In other words, I had 3 features in CategoryA, but when I deleted the CategoryA feature with NumberID value 2, I was left with NumberIDs 1 and 3 for the remaining CategoryA features. Any idea why? Thanks again.
@MikeA You can try this one.
// variables //
var numberID_field = "NumberID";
var updates = [];
var cnt = 0;
var cat = $feature.Category;
var originalGlobalID = $originalFeature.GlobalID;
// If the edit type is DELETE, we need to use the original feature's category
// to ensure we are counting features in the same category as the one being deleted.
if ($editcontext.editType == "DELETE") {
cat = $originalFeature.Category;
}
// Get all features in the same category
var features = FeatureSetByName($datastore, "{Your Feature}", ['GlobalID', 'Category'], false);
var whereClause = "Category = @cat";
// If the edit type is DELETE, we need to exclude the original feature from the count
// to avoid counting it as part of the updates.
if ($editcontext.editType == "DELETE") {
whereClause += " AND GlobalID <> @originalGlobalID";
}
// Filter features based on the category
var features_with_same_category = Filter(features, whereClause);
for (var f in features_with_same_category) {
cnt += 1;
// Create an update object for each feature in the same category
Push(updates, {
'globalID': f.GlobalID,
'attributes': Dictionary(numberID_field, cnt)
});
}
return {
'edit': [{ 'className': '{Your Feature}', 'updates': updates }
]
}
Works great! Thanks again!
Disregard - meant to reply above.