Created
November 16, 2021 19:03
-
-
Save justinsbarrett/e6ceb1dbdc1c0aefb41ea5785256b20a to your computer and use it in GitHub Desktop.
Mark matching records between two tables in Airtable.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const settings = input.config({ | |
title: "Mark Matching Records", | |
description: `Searches an incoming data table for records matching those in a \"master\" table. | |
Any matching records are marked via a checkbox field.`, | |
items: [ | |
input.config.table("masterTable", { | |
label: "Master table", | |
description: "The table containing the master list." | |
}), | |
input.config.field("masterField", { | |
parentTable: "masterTable", | |
label: "Master field", | |
description: "The field in the master table that contains the data you wish to search for in the incoming data table.", | |
}), | |
input.config.view("selectedView", { | |
parentTable: "masterTable", | |
label: "Selected view", | |
description: `The view in the master table that will display matching records. This view should have a filter to only show | |
records with a check in the selected field (set below).` | |
}), | |
input.config.field("selectedField", { | |
parentTable: "masterTable", | |
label: "Selected field", | |
description: "A checkbox field used to mark matching records." | |
}), | |
input.config.select("clearCheck", { | |
label: "Remove old matches?", | |
description: "Remove selection from previously-matched records in the master table?", | |
options: [{value: "Yes"}, {value: "No"}] | |
}), | |
input.config.table("incomingTable", { | |
label: "Incoming table", | |
description: "The table containing the data to compare against the master table." | |
}), | |
input.config.field("incomingMatchField", { | |
parentTable: "incomingTable", | |
label: "Incoming match field", | |
description: "The field to compare against the master field in the master table." | |
}), | |
input.config.field("incomingNoteField", { | |
parentTable: "incomingTable", | |
label: "Incoming note field", | |
description: "A text field where notes can be added for non-matching emails." | |
}) | |
] | |
}) | |
/** | |
* Update records in a table | |
* @param {Table} table | |
* @param {object[]} updates | |
* @param {string} updates[].id | |
* @param {object} updates[].fields | |
* @param {boolean} showProgress | |
*/ | |
async function updateRecords (table, updates, showProgress=false) { | |
const total = updates.length | |
let updateCount = 0 | |
while (updates.length > 0) { | |
if (showProgress) { | |
updateCount += Math.min(50, updates.length) | |
output.clear() | |
output.text(`Updating records (${updateCount} of ${total})`) | |
} | |
await table.updateRecordsAsync(updates.slice(0, 50)); | |
updates = updates.slice(50); | |
} | |
} | |
async function clearOldRecords() { | |
const matchQuery = await settings.selectedView.selectRecordsAsync({ | |
fields: [settings.selectedField.name] | |
}); | |
const updates = [] | |
for (let record of matchQuery.records) | |
updates.push({ | |
id: record.id, | |
fields: { | |
[settings.selectedField.name]: false | |
} | |
}); | |
await updateRecords(settings.masterTable, updates) | |
} | |
async function main() { | |
// Clear formerly-checked records | |
if (settings.clearCheck === "Yes") { | |
output.text("Clearing old matches...") | |
await clearOldRecords() | |
} | |
// Build the catalog from the master list | |
let catalog = {}; | |
const masterQuery = await settings.masterTable.selectRecordsAsync({ | |
fields: [ | |
settings.masterField.name, | |
settings.selectedField.name | |
] | |
}); | |
for (let record of masterQuery.records) { | |
// Skip records that might have an empty field | |
if (!record.getCellValueAsString(settings.masterField)) { | |
continue | |
} | |
catalog[record.getCellValueAsString(settings.masterField).trim()] = record.id; | |
} | |
output.text(`Master list size: ${Object.keys(catalog).length} records.`); | |
// Parse the incoming list and build two arrays: one for marking matching records | |
// in the master table, the other for marking non-matching records in the incoming table | |
let masterUpdates = []; | |
let incomingUpdates = []; | |
const incomingQuery = await settings.incomingTable.selectRecordsAsync({ | |
fields: [ | |
settings.incomingMatchField.name, | |
settings.incomingNoteField.name | |
] | |
}); | |
for (let record of incomingQuery.records) { | |
let email = record.getCellValueAsString(settings.incomingMatchField).trim(); | |
if (catalog[email]) { | |
masterUpdates.push({ | |
id: catalog[email], | |
fields: { | |
[settings.selectedField.name]: true | |
} | |
}); | |
} | |
incomingUpdates.push({ | |
id: record.id, | |
fields: { | |
[settings.incomingNoteField.name]: catalog[email] ? "Match found" : "No match found" | |
} | |
}); | |
} | |
output.text(`${masterUpdates.length} matches found in ${incomingQuery.records.length} incoming records`); | |
output.text("--------------------------------------------"); | |
// Update the master table records | |
output.text(`Updating ${masterUpdates.length} master records...`); | |
await updateRecords(settings.masterTable, masterUpdates) | |
// Update the incoming table records | |
output.text(`Updating ${incomingUpdates.length} incoming records...`); | |
await updateRecords(settings.incomingTable, incomingUpdates) | |
} | |
await main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This is an improved version of the script that I wrote for this forum post: https://community.airtable.com/t/re-creating-a-script-to-search-for-a-list-of-values/44218 . I was still in my early "sloppy" coding days when I first wrote the original version.