Select to view content in your preferred language

Attribute Rule Logic to Check for Unique Value in Table

964
8
12-20-2023 02:19 PM
Boomer1187
New Contributor II

Hi everyone,

I have an attribute rule applied to fire whenever there is a new project from Project A that intersects with a project from Project B. This is applied with the 'update' trigger only, and not upon 'insert' and 'update' as it would generate a duplicate record, one for when the polygon was created, and another when attributes were created in the latter circumstance.

Here is a screenshot of the relationship table that I am updating via attribute rules.

Boomer1187_0-1703110198798.png

In the screenshot above, RID 129 was created first when I created the Projects A polygon and populated a few attributes which is what I want.

RID 130 was created in the table after, when I updated a project_name in the same Projects A polygon.

Is there any logic I could incorporate into my attribute rule arcade script that would check for values in the relationship table and just update the existing record upon update, and not generate a whole new record/row in the relationship table every time a polygon is updated? The relationship table only has those few attributes that I have shown in the screenshot.

var fs = FeatureSetByName($datastore, "Project_B");
var fsInt = intersects(fs, $feature);
var payload = [];
var c = 0;
for (var proj in fsInt)
{
     payload[c++] = {"attributes": { "PROJECT_NAME": $feature.project_name, "PROJA_NUM": $feature.proja_num, "PROJB_NUM": $feature.projb_num} }
} 


return {
     "edit": [{"className": "Relationship_Class",
                "adds": payload
              }]
}

 

0 Kudos
8 Replies
Jake_S
by Esri Contributor
Esri Contributor

Boomer1187,

One thing you can do is to split your code into two parts after checking if the row exists already.

So query the Relationship_Class for the unique identifier using the Filter() function. If the row exists then do updates. Else, insert as you have now.

Depending on how you identify objects in your Relationship_Class table will require you to modify your filter.

 

 

var fs = FeatureSetByName($datastore, "Project_B");
var fsInt = intersects(fs, $feature);
var payload = [];
var c = 0;

var RelationshipClass = RelationshipClassByName($datastore, "Relationship_Class", ['*'], false);

var filter = filter(RelationshipClass, "[YOUR QUERY HERE]")

if (count(filter) > 0) // or if(isEmpty(filter){) or if(!isEmpty(filter) {} ) depends on what you want
{
  //do updates
}
else{

  //do adds
}

 

 

Hope this helps.

~Jake

0 Kudos
Boomer1187
New Contributor II

Thanks so much @Jake_S !

I had tried implementing the logic above, but it advises that in the line with RelationshipClassByName, it is an unknown function. I'm not sure if this is a valid function within Arcade.

But that being said, I had tried implementing similar logic to what you suggested below. The proja_number would be the unique identifier:

 

 

var fs = FeatureSetByName($datastore, "Project_B");
var fsInt = intersects(fs, $feature);
var payload = [];
var c = 0;
//filter within the fsInt, to filter out when proja_num is populated in the new polygon
var relatedRecords = Filter(fsInt, "PROJA_NUM = '" + $feature.proja_num + "'");

if (Count(relatedRecords) > 0) {
  // Perform updates if related records exist
  for (var proj in fsInt) {
    payload[c++] = {
      "attributes": {
        "OBJECTID": proj.objectID, 
        "GlobalID": proj.globalID,
        "PROJECT_NAME": $feature.project_name,
        "PROJA_NUM": $feature.proja_num,
        "PROJB_NUM": $feature.projb_num
      }
    };
  }

  return {
    "edit": [{
      "className": "Relationship_Class",
      "updates": payload
    }]
  };
} else {
  // Perform adds if no related records exist
  for (var proj in fsInt) {
    payload[c++] = {
      "attributes": {
        "OBJECTID": proj.objectID, 
        "GlobalID": proj.globalID,
        "PROJECT_NAME": $feature.project_name,
        "PROJA_NUM": $feature.proja_num,
        "PROJB_NUM": $feature.projb_num
      }
    };
  }

  return {
    "edit": [{
      "className": "Relationship_Class",
      "adds": payload
    }]
  };
}

 

 

 It accepts the rule as valid, but when I try creating a Project A polygon that intersects with Project B, I get this error:

Edit operation failed.

Required keyword is not defined in the dictionary regurn script
Class Name: Project_A
GlobalID:
{B8453-0D1a-4567-3456adsdad} Required keyword is not defined in the dictionary return script. {Missing keyword 'objectID or globalID' in the'updates'array or dictionary]

I had thought by adding in the object id and global id attributes in my above script that would not generate that error.

Would I be on the right track? Thanks so much in advance for any tips.

 

0 Kudos
Jake_S
by Esri Contributor
Esri Contributor

Boomer1187,

FeatureSetByRelationshipClass is valid it is available at ArcGIS Pro 3.2 (Arcade version 1.24). Check out this documentation, Attribute rules and relationship classes

Question, are there multiple objects in the relatedRecords filter?

 

~Jake

0 Kudos
Boomer1187
New Contributor II

Got it, I have ArcGIS Pro 3.1 at my organization so not quite at 3.2 yet which is too bad because would love to implement the FeatureSetByRelationshipClass functions 😞

In response to your question about objects in the relatedRecords filter, I think that would depend on how many of Project B polygons the polygon from Polygon A that is being created would intersect with.

The only attributes in the relationship_class table would be objectid, globalid, project_name, proja_num and projb_num. 

In terms of the attributes in the relatedRecords filter, the proja_num would be the unique identifier to identify (if there is a proja_num in the new polygon being created in Project A FC, then it should trigger an update, and if there is no proja_num in the new polygon being created in Project A FC, then it should trigger the add). Or are you asking what the other attributes would be in the fsInt variable from the two FCs?

Thanks so much again

0 Kudos
Jake_S
by Esri Contributor
Esri Contributor

Re-reading the initial post I realize that you have a rule to insert into the Records table and a rule to update.  I think we can can work some magic to make this all one rule.

Is there a way to share this data? I'd like to play around with it.

~Jake

0 Kudos
Jake_S
by Esri Contributor
Esri Contributor

@Boomer1187  I reviewed this whole issue and realized I have missed a fundamental part. Attribute rule dictionary keywords (result, edit, adds, etc.), depending on the dictionary requires the objectID or globall ID of the object that is being edited in feature or table (Line 5).

return {
    'edit': [{
        'className': 'b_edit_dict',
        'updates': [{
            'globalID': '{7EBAB596-E9DB-40D8-9756-B2EBED2500B7}',
            'attributes': {
                'field_name': 22
            }
        }]
    }]
}


This means you cannot use the a field value as your lookup, you need the global ID of the row. 

So after looking at this code again and using my own data, so you'll have to modify for your own this is what I have done for updates.

We need to use the PROJECT NUM (or whatever field) to filter the Relationship_Class (line 10-12).
Then we need to iterate over that filter to get the GlobalID's on that Relationship_Class table for that project (Line 15-16) and insert that into the return (Line 18). That way the return knows what feature to update.

//update only

var fs = FeatureSetByName($datastore, "Project_B");
var relatedRecFs = FeatureSetByName($datastore, "Relationship_Class");
//var fsInt = intersects(fs, $feature);
var payload = [];
var c = 0;


var getPrpjNum = $feature.PROJECT_NUM;
// Filter the Relationship_Class table to get the related records that match the Project Num of the new feature 
var relatedRecords = Filter(relatedRecFs, "PROJA_NUM = @getPrpjNum"); //

// Loop through the related records to get the GlobalID and add it to the payload
for (var rec in relatedRecords) {
  var recGlobalID = rec.GLOBALID;
  payload[c++] = {
    "GlobalID": recGlobalID,
    "attributes": {
      "PROJECT_NAME": $feature.PROJECT_NAME,
      "PROJA_NUM": $feature.PROJECT_NUM
    }
  };
}
return {
  "edit": [{
    "className": "Relationship_Class",
    "updates": payload
  }]
};


The return is different for each dictionary keyword. If you wanted to put all the code together in one for inserts and updates (which is useful for maintenance) you could do the following. Note: I'm sure this could be optimized. This is built just to give context.

This code can use insert (Line 12) and update (Line 31). It is checked using the $editcontext global variable  to build the dictionary keyword returns differently as the 'add's' does not require, nor can it have,  the GlobalID.

 

var fs = FeatureSetByName($datastore, "Project_B");
var relatedRecFs = FeatureSetByName($datastore, "Relationship_Class");
var fsInt = intersects(fs, $feature);
var payloadInsert = [];
var payloadUpdate = [];
var c = 0;
var getPrpjNum = $feature.PROJECT_NUM;

// Filter the Relationship_Class table to get the related records that match the Project Num of the new feature 
var relatedRecords = Filter(relatedRecFs, "PROJA_NUM = @getPrpjNum"); //

if ($editcontext.editType == "INSERT"){
  for (var proj in fsInt) {
    payloadInsert[c++] = {
      "attributes": {
        "PROJECT_NAME": $feature.PROJECT_NAME,
        "PROJA_NUM": $feature.PROJECT_NUM
      }
    };
  }
  return {
    "edit": [{
      "className": "Relationship_Class",
      "adds": payloadInsert
    }]
  };
}



if ($editcontext.editType == "UPDATE"){
  for (var rec in relatedRecords) {
    var recGlobalID = rec.GLOBALID;
    payloadUpdate[c++] = {
      "GlobalID": recGlobalID, // this is the GlobalID of the related record
      "attributes": {
        "PROJECT_NAME": $feature.PROJECT_NAME,
        "PROJA_NUM": $feature.PROJECT_NUM
      }
    };
  }
  return {
    "edit": [{
      "className": "Relationship_Class",
      "updates": payloadUpdate
    }]
  };
}



Hope this helps

~Jake


0 Kudos
Boomer1187
New Contributor II

Hi @Jake_S !

Sorry for the delay as I'm just returning from some time off over the holidays. Hope you had a great holiday and happy new year! Hope your 2024 is off to a good start!

I have edited the above to match my individual case and it works! Thanks so very much for your detailed analysis of the scenario as a whole and advising of the globalID! 🙂

I also added in an additional attribute rule applied to Projects A, that deletes the associated record from the relationship class table upon deletion of the Projects A polygon which is working out great.

//deletes only

var fs = FeatureSetByName($datastore, "Projects_B");
var relatedRecFs = FeatureSetByName($datastore, "Relationship_Class");
//var fsInt = intersects(fs, $feature);
var payload = [];
var c = 0;
var getProjNum = $feature.project_number;

// Filter the Relationship_Class table to get the related records that match the Project Num of the new feature 
var relatedRecords = Filter(relatedRecFs, "PROJA_NUM = @getProjNum"); //

// Loop through the related records to get the GlobalID and add it to the payload
for (var rec in relatedRecords) {
  var recGlobalID = rec.GLOBALID;
  payload[c++] = {
    "GlobalID": recGlobalID,
    "attributes": {
      "PROJECT_NAME": $feature.PROJECT_NAME,
      "PROJA_NUM": $feature.PROJA_PROJECT_NUM,
      "PROJB_NUM": $feature.PROJB_PROJECT_NUM
    }
  };
}
return {
  "edit": [{
    "className": "Relationship_Class",
    "deletes": payload
  }]
};

I'm onto my last part on trying to apply the final attribute rule(s)  to Projects_B to add to the relationship class table any time a new Projects_B polygon is created/updated/deleted. 

I am trying to alter the script to:

When a new record/polygon is created in Projects B, pull in the values from the intersecting polygons of Projects_A and insert/update them into the relationship class table, where PROJECT_NAME would be the project_name from Projects A and PROJA_NUM would be the proja_project_num from Projects A.

var fs = FeatureSetByName($datastore, "Projects_A");
var relatedRecFs = FeatureSetByName($datastore, "Relationship_Class");
var fsInt = intersects(fs, $feature);
var payloadInsert = [];
var payloadUpdate = [];
var c = 0;
var getPrpjNum = $feature.projb_project_num;

// Filter the Relationship_Class table to get the related records that match the Project Num of the new feature 
var relatedRecords = Filter(relatedRecFs, "PROJB_NUM = @getPrpjNum"); //

if ($editcontext.editType == "INSERT"){
  for (var proj in fsInt) {
    payloadInsert[c++] = {
      "attributes": {
        "PROJECT_NAME": fs.PROJECT_NAME,
        "PROJA_NUM": fs.PROJA_PROJECT_NUM,
        "PROJB_NUM": $feature.PROJB_PROJECT_NUM
      }
    };
  }
  return {
    "edit": [{
      "className": "Relationship_Class",
      "adds": payloadInsert
    }]
  };
}



if ($editcontext.editType == "UPDATE"){
  for (var rec in relatedRecords) {
    var recGlobalID = rec.GLOBALID;
    payloadUpdate[c++] = {
      "GlobalID": recGlobalID, // this is the GlobalID of the related record
      "attributes": {
        "PROJECT_NAME": fs.PROJECT_NAME,
        "PROJA_NUM": fs.PROJA_PROJECT_NUM,
        "PROJB_NUM": $feature.PROJB_PROJECT_NUM
      }
    };
  }
  return {
    "edit": [{
      "className": "Relationship_Class",
      "updates": payloadUpdate
    }]
  };
}

 

While I am getting a valid check mark when validating the script and able to save it as a valid attribute rule, when creating a new Projects B polygon, I get this error:


Failed to create Projects_B.
Failed to evaluate Arcade expression. [
Rule name: Updates Relationship Table,
Triggering event: Insert,
Class name: Projects_B,
GlobalID: {B53F669D-82E3-4542-AECE-F6E24A120074},
Arcade error: Dictionary type expected,
Script line: 15]

 Is there anything odd you may see at first glance that I may be missing? Thanks so much in advance again for your expertise!

0 Kudos
Boomer1187
New Contributor II

Edit, I've edited the above to only add in the PROJB_PROJECT_NUM into the Relationship Class table any time a new polygon is created in Projects_B which is great.

However, I would still like to know if there is any logic that can be worked into the script to 'call' the intersecting underlying PROJECT_NAME from Projects_A and most importantly, PROJA_PROJECT_NUM from Projects_A and insert it into the Relationship Class table. I would need to ensure that these values are added to the relationship class table to maintain its integrity.

This is due to the fact that the Relationship_Class table has the PROJECT_NAME from Projects_A, the PROJA_PROJECT_NUM from Projects_A and the PROJB_PROJECT_NUM from Projects_B. So any time a new polygon is created in either Projects_A or Projects_B, I would need the relationship class table to be udpated with all of those values only in  circumstances where the polygons intersect.

ATTRIBUTE RULE APPLIED TO PROJECTS_B.

 

var fs = FeatureSetByName($datastore, "Projects_A");
var relatedRecFs = FeatureSetByName($datastore, "Relationship_Class");
var fsInt = intersects(fs, $feature);
var payloadInsert = [];
var payloadUpdate = [];
var c = 0;
var getPrpjNum = $feature.projb_project_num;

// Filter the Relationship_Class table to get the related records that match the Project Num of the new feature 
var relatedRecords = Filter(relatedRecFs, "PROJB_PROJECT_NUM = @getPrpjNum"); //

if ($editcontext.editType == "INSERT"){
  for (var proj in fsInt) {
    payloadInsert[c++] = {
      "attributes": {
        "PROJB_PROJECT_NUM": $feature.PROJB_PROJECT_NUM
      }
    };
  }
  return {
    "edit": [{
      "className": "Relationship_Class",
      "adds": payloadInsert
    }]
  };
}



if ($editcontext.editType == "UPDATE"){
  for (var rec in relatedRecords) {
    var recGlobalID = rec.GLOBALID;
    payloadUpdate[c++] = {
      "GlobalID": recGlobalID, // this is the GlobalID of the related record
      "attributes": {
        "PROJB_PROJECT_NUM": $feature.PROJB_PROJECT_NUM
      }
    };
  }
  return {
    "edit": [{
      "className": "Relationship_Class",
      "updates": payloadUpdate
    }]
  };
}

 

 

This is the relationship class table, where I want to pull in or 'call' the PROJA_PROJECT_NUM and PROJECT_NAME from Projects_A in the above script, without getting the 'Dictionary Type expected' error when creating a polygon.

Boomer1187_0-1704322550419.png

 



0 Kudos