Here's a sheet worker you can try. A worker as complex as this might not work on first attempt - i may need a copy of the sheet to test. But if it works first time, yay! Note there's a variable at the start, const spellstats , that contains the various attribute names. This is there in case you change your attribute names - you just have to change them in this one place. I did this because your attribute names look a bit clunky and you might want to change them for increased clarity. Like one repeating section might be called repeating_skills, and the other repeating_spells, and the attributes within them similarly made a bit more distinct and clear. But anyway, whether you change them or not, here's the worker to test. /* each spell must have a skill assigned. if the skill doesnt match a skill in the skillsection, bonus and ranks will both be set to 0. If there is no name assigned at all, then no ranks or bonus will be calculated. if the first section contains two or more identical skill names (an error), the function below will assume the LAST one is correct. */ const spellstats = { skillsection : 'repeating_baselistskill' , spellsection : 'repeating_baselist' , skills_name : 'baselistskillname' , skills_ranks : 'baselistskillranks' , skills_bonus : 'baselistskilltotal' , spells_name : 'baselistname' , spells_ranks : 'baselistranks' , spells_bonus : 'baselisttotal' }; on ( `change: ${ spellstats . skillsection } : ${ spellstats . skills_name } change: ${ spellstats . skillsection } : ${ spellstats . skills_bonus } change: ${ spellstats . spellsection } : ${ spellstats . spells_name } ` ,() => { getSectionIDs ( spellstats . skillsection , idSkills => { getSectionIDs ( spellstats . spellsection , idSpells => { const getRepeatingName = ( section , id , field ) => ` ${ section } _ ${ id } _ ${ field } ` ; const skillsnames = idSkills . reduce (( s , row ) => [... s , getRepeatingName ( spellstats . skillsection , row , spellstats . skills_name ), getRepeatingName ( spellstats . skillsection , row , spellstats . skills_ranks ), getRepeatingName ( spellstats . skillsection , row , spellstats . skills_bonus )], []); const spellsnames = idSpells . reduce (( s , row ) => [... s , getRepeatingName ( spellstats . spellsection , row , spellstats . spells_name ),], []); // you dont need to READ the ranks and bonuses in the spells section, just write them, so dont need to grab them here. getAttrs ([... skillsnames , ... spellsnames ], v => { const output = {}; // build an object with a key = skill, and values = ranks and total. const skillsobject = idSkills . reduce (( a , row ) => ({... a , [ v [ getRepeatingName ( spellstats . skillsection , row , spellstats . skills_name )]]: { ranks : v [ getRepeatingName ( spellstats . skillsection , row , spellstats . skills_ranks )], bonus : v [ getRepeatingName ( spellstats . skillsection , row , spellstats . skills_bonus )] } }), {}); //iterate through items in spells, and for each one get the ranks aand bonus from above object. idSpells . forEach ( spellrow => { const skillname = v [ getRepeatingName ( spellstats . spellsection , spellrow , spellstats . spells_name )] || undefined ; if ( skillsobject . hasOwnProperty ( skillname )) { output [ getRepeatingName ( spellstats . spellsection , spellrow , spellstats . spells_ranks )] = skillsobject [ skillname ]. ranks ; output [ getRepeatingName ( spellstats . spellsection , spellrow , spellstats . spells_bonus )] = skillsobject [ skillname ]. bonus ; } else { output [ getRepeatingName ( spellstats . spellsection , spellrow , spellstats . spells_ranks )] = 0 ; output [ getRepeatingName ( spellstats . spellsection , spellrow , spellstats . spells_bonus )] = 0 ; } }); if ( output ) setAttrs ( output ); }); }); }); });