Skip to main content

If you have a many-to-many relationship, chances are you have a junction table. And chances are, setting up and maintaining that junction table is probably…not your typical idea of a fun time.


Welp, now it can be! Thanks to some serious help from @Stephen_Suen, we figured out how a script could help us automate this process. In the below example I have a list of projects, and for each project I’ve assigned multiple companies to it. I also have another table named “Tracking” where I track all the details related to a specific project-company pair.



Previously to create that junction, I had to do a lot of finagling, which took a fair bit of time. However, with a script, I can automatically create the records I need. Below I’m going to assign companies to Project 5, and then use a script to create the junction records.



Ahh, the joy of automation. :massage_woman:t5: Thank you @Stephen_Suen! Script below. Example base here.


let projectsTable = base.getTable('Projects');
let projectQuery = await projectsTable.selectRecordsAsync();
let projectRecords = projectQuery.records;

let companyTable = base.getTable('Companies');
let companyQuery = await companyTable.selectRecordsAsync();

let joinTable = base.getTable('Tracking');
let joinQuery = await joinTable.selectRecordsAsync();

// Go through all the records in the projects table
for (let projectRecord of projectRecords) {
output.markdown('#### Checking project: ' + projectRecord.getCellValueAsString('Name') + '...');
// For each project, get the linked company records
let companyRecordsLinkedToProject = projectRecord.getCellValue('Companies');

// For each project, get the linked join records
let joinRecordsLinkedToProject = projectRecord.getCellValue('Tracking');
if (joinRecordsLinkedToProject === null) {
// If there are no linked join records, use an empty array instead
joinRecordsLinkedToProject = P];
}

// Create a Set of company record ids that are linked to the project.
// A Set is like an array, but more efficient.
let companyRecordIds = new Set();
for (let linkedCompanyRecord of companyRecordsLinkedToProject) {
companyRecordIds.add(linkedCompanyRecord.id);
}

// Loop over all join records linked to the project.
for (let linkedJoinRecord of joinRecordsLinkedToProject) {
// Get the full join record, not just name and id
let joinRecord = joinQuery.getRecord(linkedJoinRecord.id);
let companyRecordLinkedToJoin = joinRecord.getCellValue('Company')'0];
let companyRecordId = companyRecordLinkedToJoin.id;

// Remove this company record id from the Set.
output.text('Join record already exists for company: ' + companyRecordLinkedToJoin.name);

companyRecordIds.delete(companyRecordId);
}

// Now we have a Set of company record ids that don't have join records.
for (let companyRecordIdToJoin of Array.from(companyRecordIds)) {
// Create the join record linking this project and this company.
let companyRecord = companyQuery.getRecord(companyRecordIdToJoin);
let companyName = companyRecord.getCellValueAsString('Company Name');
output.text('Creating join record for company: ' + companyName);
await joinTable.createRecordAsync({
'Project': P{id: projectRecord.id}],
'Company': C{id: companyRecordIdToJoin}],
});
}

output.text('\n');

output.inspect(companyRecordIds);
}

Great question! For me it mostly has to do with usability. In this example, one person - say a project manager, is outlining all the high-level information about the project including key dates, and which companies should be involved. I envision the project manager using the script to create those junction records where another contributor to the team can easily find where they should be inputting data.


So yes while you are maintaining two many-to-many relationships with the script, it’s mainly about when/how you’re inputting the information. Would love your thoughts on this as well.


I know this was forever ago, I apologize. I'm trying to use this script, but the example base no longer gives access (or maybe I was supposed to request access manually?) Either way, my main question is about this add-on to the bottom of the script.

in this line:
let nonMatches = joinRecordsTwo.filter(c => !c.getCellValueAsString('Project Lookup').split(', ').includes(c.getCellValueAsString('Company')))

It looks like "Project Lookup" is a column added to the junction table. How do I do that so that it works properly? What am I looking up that allows the script to check for changes? 


I know this was forever ago, I apologize. I'm trying to use this script, but the example base no longer gives access (or maybe I was supposed to request access manually?) Either way, my main question is about this add-on to the bottom of the script.

in this line:
let nonMatches = joinRecordsTwo.filter(c => !c.getCellValueAsString('Project Lookup').split(', ').includes(c.getCellValueAsString('Company')))

It looks like "Project Lookup" is a column added to the junction table. How do I do that so that it works properly? What am I looking up that allows the script to check for changes? 


Answering my own question if anyone gets this far. This was the only part of the code that was missing some comments to help beginners understand.

The addition to the script is to provide a one-way sync update to your new junction table from one of the original tables. If, in your Company table, you delete a Project linking record in your Projects field, this piece of the script would delete the corresponding record of that Company/Project pair. It doesn't do the reverse, so if you ran this script again on the same tables, it would just recreate it.

In this case, (watch how many times I can say lookup in one sentence) you would create a lookup field in your junction table called "Project Lookup" that looks up the Company link record field from your Project table. This is where I got confused - I would have called this "Company Lookup", since the values would be Company links, not Project links - and the default name in Airtable would be "Companies (from Projects)". So this filter will compare the list of all Companies linked to this record's Project, and the Company that is explicetly linked in this record by the previous script. If the Company is missing, you must have deleted that link in the Companies or Projects table, so it will delete the record here.


Reply