diff --git a/Server-Side Components/Background Scripts/Duplicate Finder/findDuplicatesByCombination.js b/Server-Side Components/Background Scripts/Duplicate Finder/findDuplicatesByCombination.js new file mode 100644 index 0000000000..83d3684999 --- /dev/null +++ b/Server-Side Components/Background Scripts/Duplicate Finder/findDuplicatesByCombination.js @@ -0,0 +1,132 @@ +/** + * Finds and reports records that have duplicate values across a combination of specified fields instead of a single field + * Useful for finding for example duplicate items where unique key is not just 1 field + */ + +// --- UPDATE ONLY THE VALUES BELOW --- +var tableName = 'cmdb_model'; // ADD: The table you want to check for duplicates. +var fieldNames = ['name', 'model_number', 'manufacturer']; // ADD: An array of fields that create the unique combination. + +// --- DO NOT EDIT BELOW THIS LINE --- +findDuplicateCombinations(tableName, fieldNames); + +function findDuplicateCombinations(tableName, fieldNames) { + /**************************************/ + /*** Basic Error Handling ***/ + /**************************************/ + + // Check if the table exists + if (!gs.tableExists(tableName)) { + gs.print('Error: Table "' + tableName + '" does not exist.'); + return; + } + + // Check if fieldNames is a valid, non-empty array + if (!Array.isArray(fieldNames) || fieldNames.length === 0) { + gs.print('Error: The "fieldNames" variable must be an array with at least one field.'); + return; + } + + // Check if all specified fields exist on the table + var gr = new GlideRecord(tableName); + gr.initialize(); + for (var i = 0; i < fieldNames.length; i++) { + var currentField = fieldNames[i]; + if (!gr.isValidField(currentField)) { + gs.print('Error: The field "' + currentField + '" does not exist on the "' + tableName + '" table.'); + return; + } + } + + /***************************************/ + /*** Find Duplicate Combinations ***/ + /***************************************/ + var duplicateJson = {}; // Stores the duplicate records and their counts + var duplicateGroupCount = 0; // Counts the number of groups of duplicates + + var duplicateAggregate = new GlideAggregate(tableName); + duplicateAggregate.addAggregate('COUNT'); + + // Loop through the array to group by each field, creating the combination + for (var j = 0; j < fieldNames.length; j++) { + var field = fieldNames[j]; + duplicateAggregate.groupBy(field); + duplicateAggregate.addNotNullQuery(field); // Ignore records where any part of the combination is empty + } + + duplicateAggregate.addHaving('COUNT', '>', 1); // More than 1 means it is a duplicate combination + duplicateAggregate.query(); + + while (duplicateAggregate.next()) { + duplicateGroupCount++; + var combinationDisplay = []; + var queryParams = []; + + for (var k = 0; k < fieldNames.length; k++) { + var fieldName = fieldNames[k]; + var fieldValue = duplicateAggregate.getDisplayValue(fieldName) || duplicateAggregate.getValue(fieldName); + var actualValue = duplicateAggregate.getValue(fieldName); + combinationDisplay.push(fieldName + ': "' + fieldValue + '"'); + queryParams.push({ + field: fieldName, + value: actualValue + }); + } + + var displayKey = combinationDisplay.join(', '); + var countInGroup = duplicateAggregate.getAggregate('COUNT'); + duplicateJson[displayKey] = { + count: countInGroup, + queryParams: queryParams + }; + } + + /***************************************************/ + /*** Print the Results & Fetch sys_id's ***/ + /***************************************************/ + + var fieldCombinationString = '"' + fieldNames.join('", "') + '"'; + + if (Object.keys(duplicateJson).length === 0) { + gs.print('No duplicates found for the field combination [' + fieldCombinationString + '] on table "' + tableName + '".'); + return; + } + + gs.print(''); + gs.print('======================================================'); + gs.print('DUPLICATE COMBINATIONS REPORT'); + gs.print('------------------------------------------------------'); + gs.print('Table: ' + tableName); + gs.print('Found ' + duplicateGroupCount + ' groups of duplicate records.'); + gs.print('======================================================\n'); + + var groupNumber = 1; + for (var key in duplicateJson) { + var groupData = duplicateJson[key]; + + gs.print('Group ' + groupNumber + ':'); + gs.print(' - Occurrences: ' + groupData.count); + gs.print(' - Combination:'); + + var fields = key.split(', '); + for (var i = 0; i < fields.length; i++) { + gs.print(' - ' + fields[i]); + } + + gs.print(" - Duplicate sys_id's:"); + + // Perform the second query to get the sys_id's for this specific group + var rec = new GlideRecord(tableName); + for (var q = 0; q < groupData.queryParams.length; q++) { + var query = groupData.queryParams[q]; + rec.addQuery(query.field, query.value); + } + rec.query(); + + while (rec.next()) { + gs.print(' - ' + rec.getUniqueValue()); + } + gs.print(''); + groupNumber++; + } +} diff --git a/Server-Side Components/Background Scripts/Duplicate Finder/script.js b/Server-Side Components/Background Scripts/Duplicate Finder/findDuplicatesBySingleField.js similarity index 100% rename from Server-Side Components/Background Scripts/Duplicate Finder/script.js rename to Server-Side Components/Background Scripts/Duplicate Finder/findDuplicatesBySingleField.js diff --git a/Server-Side Components/Background Scripts/Duplicate Finder/readme.md b/Server-Side Components/Background Scripts/Duplicate Finder/readme.md index 3ca613a445..1f9a1931f2 100644 --- a/Server-Side Components/Background Scripts/Duplicate Finder/readme.md +++ b/Server-Side Components/Background Scripts/Duplicate Finder/readme.md @@ -1,10 +1,11 @@ -## ServiceNow Duplicate Record Finder -A simple server-side script for ServiceNow that finds and reports on duplicate values for any field on any table. It uses an efficient GlideAggregate query and formats the results into a clean, readable report. - +# ServiceNow Duplicate Record Finder +A simple server-side script for ServiceNow that finds and reports on duplicate values for any field on any table. It uses an efficient `GlideAggregate` query. +## Find Duplicates by Single Field - `findDuplicatesBySingleField.js` +This finds duplicates based on a single field which matches ### How to Use This script is designed to be run in **Scripts - Background** or as a Fix Script. 1. Navigate: Go to **System Definition > Scripts - Background** (or type sys.scripts.do in the filter navigator). -2. Copy Script: Copy the entire contents of the script.js file. +2. Copy Script: Copy the entire contents of the `findDuplicatesBySingleField.js` file. 3. Paste and Configure: Paste the script into the "Run script" text box. Add the table to search in `tableName` and the field to search for duplicates in `fieldName` ```js // Update ONLY below values to find duplicates @@ -22,6 +23,25 @@ This script is designed to be run in **Scripts - Background** or as a Fix Script var fieldName = 'short_description'; ``` - 4. Run Script: Click the "Run script" button. The results will be displayed on the screen below the editor. +## Find Duplicates by Field Combination - `findDuplicatesByCombination.js` +This is a more powerful script that finds duplicate records based on a unique combination of one or more fields. + +### How to use +This script is also designed to be run in **Scripts - Background** +1. Navigate: Go to **System Definition > Scripts - Background** +2. Copy Script: Copy the entire contents of the `findDuplicatesByCombination.js` file. +3. Paste and Configure: Paste the script into the "Run script" text box. Update the `tableName` field and the `fieldNames` array. The `fieldNames` variable is an array, so even a single field must be enclosed in square brackets `[]` + +```js +// --- UPDATE ONLY THE VALUES BELOW --- +var tableName = ''; // The table you want to check. +var fieldNames = ['field_1', 'field_2']; // An array of fields for the unique combination. +``` + +For example, to find models with the same name, model number and manufacturer +```js +var tableName = 'cmdb_model'; +var fieldNames = ['name', 'model_number', 'manufacturer']; +```