Preview:
/**
 * Get records with duplicate values in multiple fields, and return a list of the records'
 * counts and the dupe fields' values.
 *
 * @param {string} tableName - The table to check for duplicates. Must be a string.
 *
 * @param {string|string[]} arrDupeFields - The fields to check for duplicates.
 *  Must be a string or an array of strings.
 *
 * @param {string|string[]} [nonBlankFields=[]] - This optional parameter, if specified, will
 *  be an array of strings consisting of the names of the fields which should not be allowed
 *  to be blank when checking for duplicates.
 * To put it another way, if any of the fields specified in the `nonBlankFields` array are
 *  blank, then the record should not be considered a duplicate.
 * Note that the fields specified in nonBlankFields do NOT need to also be specified in
 * arrDupeFields.
 *
 * @param {boolean} [getDupeSysIds=false] - If true, the sys_ids of the records with the
 *  duplicate values will be included in the returned object. If false, they will not be
 *  included.
 * This may have a performance impact, as it requires an additional query for each
 *  duplicate combination, so use with caution.
 * Default is false.
 *
 * @returns {{dupeCount: (*|number), fields: {}}[]} - An array of objects, each object
 *  representing a confluence of duplicate values in the specified fields - a specific
 *  combination of field values - and the number of records that have that combination of
 *  those values in the specified fields.
 */
function getMultiFieldDupes(tableName, arrDupeFields, nonBlankFields, getDupeSysIds) {
    var gaMyTableDuplicate, grDupe, iNonBlankField, iDupeField, objDupeRecord;
    var arrDupeRecords = [];
    var dupeCount = 0;
    
    /***** INPUT VALIDATION *****/
    
    getDupeSysIds = (typeof getDupeSysIds === 'boolean') ? getDupeSysIds : false;
    
    if (typeof tableName !== 'string' || !tableName) {
        throw new Error(
            'getMultiFieldDupes(): tableName must be a string consisting of a valid ' +
            'table name in the ServiceNow database.'
        );
    }
    
    if ( //If arrDupeFields is not an array or a string, or if it's an array but it's empty
        typeof arrDupeFields === 'undefined' ||
        !arrDupeFields ||
        (!Array.isArray(arrDupeFields)  && typeof arrDupeFields !== 'string') ||
        !arrDupeFields.length
    ) {
        throw new Error(
            'getMultiFieldDupes(): arrDupeFields must be a string with a single ' +
            'field name, or an array of strings - each string representing a ' +
            'field name in the ' + tableName + ' table.'
        );
    }
    
    //If arrDupeFields is a string, convert it to an array.
    if (typeof arrDupeFields === 'string') {
        arrDupeFields = arrDupeFields.split(',');
    }
    
    //If nonBlankFields is undefined, null, or an empty string, set it to an empty array.
    //If it's a string, convert it to an array.
    if (typeof nonBlankFields === 'undefined' || !nonBlankFields) {
        nonBlankFields = [];
    } else if (typeof nonBlankFields === 'string') {
        //Splitting just in case the input data is a comma-separated string - which it
        // shouldn't be, but I don't trust anyone who calls this code. They seem sus.
        nonBlankFields = nonBlankFields.split(',');
    } else if (!Array.isArray(nonBlankFields)) {
        //If it's not a string or an array or undefined, throw an error because wth am I s'posed to do with that
        throw new Error(
            'getMultiFieldDupes(): nonBlankFields must be a string with a single ' +
            'field name, or an array of strings - each string representing a ' +
            'field name in the ' + tableName + ' table.'
        );
    }
    
    /***** ACTUALLY DOING THE THING *****/
    gaMyTableDuplicate = new GlideAggregate(tableName);
    gaMyTableDuplicate.addAggregate('COUNT');
    
    //Group by each field in the arrDupeFields array
    for (
        iDupeField = 0;
        iDupeField < arrDupeFields.length;
        iDupeField++
    ) {
        gaMyTableDuplicate.groupBy(arrDupeFields[iDupeField]);
    }
    
    //If any nonBlankFields were specified, add a query to exclude records where
    // any of those fields are blank.
    for (
        iNonBlankField = 0;
        iNonBlankField < nonBlankFields.length;
        iNonBlankField++
    ) {
        gaMyTableDuplicate.addNotNullQuery(nonBlankFields[iNonBlankField]);
    }
    
    //Only show records with more than one match (duplicates)
    gaMyTableDuplicate.addHaving('COUNT', '>', 1);
    gaMyTableDuplicate.query();
    
    while (gaMyTableDuplicate.next()) {
        dupeCount = gaMyTableDuplicate.getAggregate('COUNT');
        
        //Populate the arrDupeRecords array with some info about the records that have duplicates
        objDupeRecord = {
            "dupeCount": dupeCount,
            "fields": {}
        };
        
        //For each field in the arrDupeFields array, add that field's value to
        // the objDupeRecord.fields object.
        for (
            iDupeField = 0;
            iDupeField < arrDupeFields.length;
            iDupeField++
        ) {
            objDupeRecord.fields[arrDupeFields[iDupeField]] = gaMyTableDuplicate.getValue(arrDupeFields[iDupeField]);
        }
        
        if (getDupeSysIds) {
            objDupeRecord.dupe_sys_ids = [];
            //Add the sys_ids of all the records that have this combination of dupe fields in objDupeRecord.dupe_sys_ids:
            grDupe = new GlideRecord(tableName);
            
            for (
                iDupeField = 0;
                iDupeField < arrDupeFields.length;
                iDupeField++
            ) {
                grDupe.addQuery(arrDupeFields[iDupeField], objDupeRecord.fields[arrDupeFields[iDupeField]]);
            }
            
            grDupe.query();
            while (grDupe.next()) {
                objDupeRecord.dupe_sys_ids.push(grDupe.getUniqueValue());
            }
        }
        
        arrDupeRecords.push(objDupeRecord);
    }
    
    return arrDupeRecords;
}
downloadDownload PNG downloadDownload JPEG downloadDownload SVG

Tip: You can change the style, width & colours of the snippet with the inspect tool before clicking Download!

Click to optimize width for Twitter