Managing Inactive Records in ServiceNow: Best Practices & Tips






Mastering Inactive Records: Your Definitive Guide to ServiceNow GlideRecord


Mastering Inactive Records: Your Definitive Guide to ServiceNow GlideRecord

Hey there, fellow ServiceNow enthusiast! Ever found yourself staring at a list of records, wondering where the “old” ones went? Or perhaps you’re tasked with cleaning up historical data without accidentally deleting critical information? You’re not alone. Managing inactive records in ServiceNow is a common challenge, but it’s also an opportunity to master one of the platform’s most powerful tools: GlideRecord.

In the fast-paced world of IT Service Management (ITSM), data hygiene isn’t just a nice-to-have; it’s crucial for accurate reporting, efficient workflows, and optimal platform performance. Inactive records – think closed incidents, retired CIs, or cancelled change requests – still hold valuable historical context, but they shouldn’t clutter your daily active views. Understanding how to query, update, and even safely delete these records is a hallmark of a skilled ServiceNow developer or administrator.

This article isn’t just another dry technical manual. We’re going on a deep dive into GlideRecord, focusing specifically on how it empowers you to interact with inactive data. We’ll explore the foundational concepts, walk through practical examples, discuss best practices, and even touch upon troubleshooting and interview relevance. So, grab your favorite beverage, get comfortable, and let’s unravel the mysteries of working with inactive records in ServiceNow!

Understanding GlideRecord: Your ServiceNow Data Whisperer

Imagine ServiceNow’s database as a vast library. If you wanted to find a specific book (record) or a collection of books (records) based on certain criteria, you wouldn’t directly rummage through the shelves yourself, speaking in arcane database incantations (SQL). Instead, you’d go to the librarian, describe what you need in plain English, and they would fetch it for you. In ServiceNow, GlideRecord is that brilliant librarian.

At its core, GlideRecord is a server-side JavaScript API that acts as your primary interface for interacting with the ServiceNow database. It’s a special Java class under the hood, but don’t let that intimidate you; you’ll be writing pure JavaScript to wield its power. Its main purpose? To perform CRUD operations (Create, Read, Update, Delete) on records without ever having to write a single line of SQL. ServiceNow handles the translation from your GlideRecord JavaScript into efficient SQL queries behind the scenes.

Why is GlideRecord Indispensable?

  • Abstraction Layer: You don’t need to know the underlying database structure or SQL syntax. GlideRecord provides a high-level, object-oriented way to work with data.
  • Security: It enforces ServiceNow’s Access Control Lists (ACLs) by default, ensuring that your scripts only interact with data the current user (or system, if impersonated) has permission to access.
  • Efficiency: Optimized by ServiceNow to work seamlessly with its platform architecture.
  • Central to Server-Side Scripting: From Business Rules and Script Includes to Fix Scripts and UI Actions, GlideRecord is everywhere on the server side.

A Critical Warning: Test Before You Deploy!

Before we go any further, let’s engrain this golden rule: ALWAYS test your GlideRecord scripts on a non-production instance first! An incorrectly constructed query, a typo in a field name, or a logical error can lead to unintended consequences, including mass data corruption or loss. Imagine accidentally deleting all your active incidents because of a misplaced addInactiveQuery() or an inverted `active` flag! It’s a nightmare scenario that proper testing can prevent.

GlideRecord Architecture: Bridging JavaScript and Database

Think of the flow like this:

GlideRecord API Mapping Diagram

For instance, an SQL SELECT * FROM incident WHERE priority = 1; effectively translates to something like `var inc = new GlideRecord(‘incident’); inc.addQuery(‘priority’, 1); inc.query();` in GlideRecord. Pretty neat, right?

The Anatomy of a GlideRecord Query (and Why It Matters for Inactive Records)

Before we specifically target inactive records, let’s refresh our memory on the fundamental steps of building a GlideRecord query. These steps are the building blocks for any data retrieval task, active or inactive.

1. Instantiation: Pointing to the Right Table

First, you need to tell GlideRecord which table you want to work with. You do this by creating a new GlideRecord object:

var gr = new GlideRecord('table_name');

For example, to work with incidents:

var inc = new GlideRecord('incident');

2. Defining Your Filters: The `addQuery()` Powerhouse

This is where you specify the conditions for the records you want to retrieve. The `addQuery()` method is incredibly versatile.

Basic `addQuery()`: Your First Filter

The simplest form of `addQuery()` takes a field name and a value. It implicitly uses the ‘equals’ operator.

var inc = new GlideRecord ('incident');
inc.addQuery ('priority', 1); // Get all incidents where priority is 1
inc.query ();
while(inc.next()){
    gs.print(inc.number);
}

Result: This script will print the numbers of all incidents with Priority 1.

Multiple `addQuery()`: Chaining Conditions

You can chain multiple `addQuery()` statements. By default, these are treated as ‘AND’ conditions.

var inc = new GlideRecord('incident');
inc.addQuery ('active', true);           // Condition 1: Active records
inc.addQuery ('priority', 1);            // Condition 2: Priority is 1
inc.addQuery ('category','software');    // Condition 3: Category is 'software'
inc.query ();
while(inc.next()){
    gs.print (inc.number);
}

Result: Prints incident numbers that are active AND have Priority 1 AND are categorized as ‘software’.

`addQuery()` with Operators: Getting Specific

The `addQuery()` method also allows you to specify an operator for more nuanced filtering. This is especially useful when you need to retrieve records based on ranges or partial matches.

var inc = new GlideRecord('incident');
inc.addActiveQuery(); // Implicitly adds 'active=true'
inc.addQuery('priority','<=',2); // Get records where priority is less than or equal to 2 (Critical or High)
inc.query();
while(inc.next()){
    gs.print(inc.number);
}

Result: This will display incident numbers that are active and have a priority of 1 (Critical) or 2 (High).

Common operators you can use include:

  • = (equals)
  • != (not equals)
  • > (greater than)
  • >= (greater than or equals)
  • < (less than)
  • <= (less than or equals)
  • IN (value is in a list)
  • NOT IN (value is not in a list)
  • STARTSWITH
  • ENDSWITH
  • CONTAINS
  • DOES NOT CONTAIN

3. `addEncodedQuery()`: The Power User's Shortcut

For complex queries with many conditions or 'OR' clauses, `addEncodedQuery()` is your best friend. Instead of writing multiple `addQuery()` lines, you can grab an encoded query directly from any list view in ServiceNow.

How to get an Encoded Query:

  1. Navigate to any list view (e.g., Incident -> All).
  2. Apply your desired filters using the condition builder (e.g., active = true AND priority = 1 AND category = software).
  3. Right-click on the breadcrumbs (the filter chips at the top) and select "Copy query."
  4. Paste this string into your script.
var inc = new GlideRecord ('incident');
inc.addEncodedQuery('active=true^category=software^priority=1');
inc.query();
while(inc.next()){
    gs.print(inc.number);
}

Result: Prints incident numbers matching the complex filter from the encoded query. You can also assign the encoded query to a variable for better readability:

var ecq = 'active=true^category=software^priority=1'; // Encoded query set to a variable
var inc = new GlideRecord ('incident');
inc.addEncodedQuery (ecq);
inc.query();
while (inc.next()){
    gs.print (inc.number);
}

4. Executing the Query: `query()`

After defining all your filters, you need to execute the query to fetch the records from the database:

inc.query();

5. Iterating Through Results: `next()` and `while()`

Once the query runs, the results are essentially a cursor pointing to the first record. To process each record, you use a `while` loop with `next()`:

while(inc.next()){
    // Do something with each record here
    gs.print(inc.number + ' - ' + inc.short_description);
}

The `inc.next()` method moves the cursor to the next record and returns `true` if there's another record, or `false` if it has reached the end of the result set. This makes the `while` loop an elegant way to process all matching records.

The Heart of the Matter: Interacting with Inactive Records

Now that we've covered the GlideRecord fundamentals, let's zero in on the main event: working with inactive records. This is where `addActiveQuery()` and `addInactiveQuery()` become invaluable.

What Exactly Are "Inactive Records"?

In ServiceNow, records often have an active field, typically a true/false (boolean) type. When this field is set to false, the record is considered "inactive." It's not deleted from the database; it simply exists in a state that usually hides it from standard list views, reports, and some workflows to prevent clutter.

Common scenarios for inactive records:

  • Incidents: Closed or Cancelled.
  • Change Requests: Closed or Cancelled.
  • Service Catalog Items: Retired.
  • Configuration Items (CIs): Retired or Decommissioned.
  • Users: Locked out, or employees who have left the organization.

The `active` field plays a significant role in how users perceive data. By default, most out-of-the-box lists and reports filter out inactive records. This is a good thing for day-to-day operations, but when you need to analyze historical trends, perform data cleanup, or re-activate a record, you need to explicitly tell ServiceNow to show you the inactive ones.

`addActiveQuery()`: Explicitly Fetching the Live Data

You might be thinking, "Why do I need to explicitly query for active records if they're shown by default?" That's a great question! While many lists implicitly filter for active records, `addActiveQuery()` makes your intent explicit in scripts and can be particularly useful when combining with other complex conditions.

The method `addActiveQuery()` is a shortcut for `addQuery('active', true)`. It's concise and clearly communicates your intention to retrieve only active records.

var inc = new GlideRecord('incident');
inc.addActiveQuery(); // Effectively: inc.addQuery('active', true);
inc.addQuery ('priority', 1); // Only active incidents with priority 1
inc.query ();
while (inc.next ()){
    gs.info (inc.number + ' is an active, Critical incident.');
}

Real-World Scenario: Imagine needing to send a daily report to management listing all active, critical incidents. Using `addActiveQuery()` ensures you're only working with currently open and urgent issues, preventing confusion with closed or resolved tickets.

`addInactiveQuery()`: Unearthing the Archived/Historical Data

This is where the real magic happens for our topic! `addInactiveQuery()` is the direct opposite of `addActiveQuery()`, and it's your go-to method for specifically targeting records where `active` is set to `false`.

It's a shortcut for `addQuery('active', false)`. Without this, or an explicit `addQuery('active', false)`, your GlideRecord queries will often implicitly *exclude* inactive records, leading to frustrating "no results" scenarios when you know the data exists.

var inc = new GlideRecord ('incident');
inc.addInactiveQuery (); // Effectively: inc.addQuery('active', false);
inc.addQuery ('priority', 1); // Find inactive incidents that were Priority 1
inc.query ();
while (inc.next ()) {
    gs.print (inc.number + ' is an inactive incident that was Priority 1. Current state: ' + inc.state.getDisplayValue());
}

Result: This script will print the numbers of incidents that are currently inactive (e.g., Closed, Cancelled) but had a Priority of 1 at some point. It helps you look back in time.

Real-World Scenario: A compliance audit requires a list of all change requests that were cancelled over the last quarter, along with their associated reasons. Using `addInactiveQuery()` is essential to access these "completed" (but not active) records, which otherwise would be hidden from your script's default view.

When to Use Which: A Quick Comparison

  • `addActiveQuery()`: Use when you definitively only want records that are currently "live" or open. It makes your script's intent clear.
  • `addInactiveQuery()`: Use when you specifically need to access historical, closed, or retired records. This is crucial for reports, data analysis, or cleanup tasks involving archived data.
  • `addQuery('active', true)` / `addQuery('active', false)`: These are the underlying methods. `addActiveQuery()` and `addInactiveQuery()` are simply convenience wrappers. Use the direct `addQuery()` if you prefer explicit control or need to combine it with other complex `OR` conditions where the convenience methods might be less intuitive.

Pro Tip: When debugging or troubleshooting why a script isn't finding records you expect, always consider the `active` flag. It's one of the most common reasons for GlideRecord queries returning empty results, especially if you're forgetting to use `addInactiveQuery()` when dealing with historical data.

Working Beyond Queries: Manipulating Inactive Records

Sometimes, simply querying inactive records isn't enough. You might need to update their fields (e.g., to correct historical data) or, with extreme caution, delete them entirely.

Updating Inactive Records

The process for updating inactive records is largely the same as active ones, but you must ensure your query correctly targets the inactive records you intend to modify.

Updating a Single Inactive Record: `update()`

var inc = new GlideRecord ('incident');
// First, find the specific inactive record. Must use addInactiveQuery if it's inactive!
inc.addInactiveQuery();
inc.addQuery('number', 'INC0000057'); 
inc.query();
if (inc.next()) {
    inc.setValue ('state', 2); // Set state to 'In Progress' for example (make it active again or change its final state)
    inc.update ();
    gs.print('Record ' + inc.number + ' updated successfully.');
} else {
    gs.print('Incident INC0000057 (inactive) not found.');
}

Real-World Scenario: An auditor finds a discrepancy in the closure notes for a specific, already-closed incident. You need to update that single inactive record's `close_notes` field without impacting its `sys_updated_on` or `sys_updated_by` fields.

Updating Multiple Inactive Records: `updateMultiple()`

This method is powerful for bulk changes, especially useful for data migration or cleanup tasks.

var inc = new GlideRecord('incident');
inc.addInactiveQuery(); // Target only inactive incidents
inc.addQuery ('category', 'hardware'); // Filter by category
inc.setValue('category', 'infrastructure'); // Change category from hardware to infrastructure
inc.updateMultiple ();
gs.print('Multiple inactive hardware incidents updated to infrastructure category.');

Real-World Scenario: After a department reorganization, all previously closed incidents that were categorized as 'Hardware' for a specific team now need to be re-categorized as 'Infrastructure' for historical reporting purposes. `updateMultiple()` is perfect for this.

Important Considerations for Updating Inactive Records (especially historical data)

When you update records, ServiceNow typically logs the change (sys_updated_on, sys_updated_by, sys_mod_count) and can trigger Business Rules and Workflows. For historical data, you might want to bypass these behaviors to preserve the original audit trail or prevent unintended re-processing.

  • `autoSysFields(false)`: This method (not working in scoped applications, but relevant for global scope scripts) prevents the system fields like `sys_updated_on`, `sys_updated_by`, `sys_mod_count` from being updated. This is crucial for preserving the exact historical context.
  • `setWorkflow(false)`: This method prevents Business Rules from running when the record is updated. This is vital when you're making behind-the-scenes data corrections and don't want to re-trigger notifications, state transitions, or other automated processes.
var inc = new GlideRecord ('incident');
inc.addInactiveQuery();
inc.addQuery ('state', 7); // Let's say state 7 is 'Closed'
inc.query ();
while (inc.next ()) {
    inc.autoSysFields (false); // Don't update system fields (e.g., last updated by/on)
    inc.setWorkflow (false);   // Don't run any Business Rules or workflows
    inc.setValue ('close_code', 'Resolved Permanently (Manual Correction)'); // Correct a historical close code
    inc.update ();
}
gs.print('Inactive records updated without touching system fields or triggering workflows.');

Result: Records are updated as specified, but their audit trail (who last updated it, when) remains unchanged, and no new notifications or process flows are initiated.

Deleting Inactive Records: Proceed with Extreme Caution!

Deleting records is irreversible. While it might be tempting to clean up old, inactive data, ensure you have a clear policy, proper backups, and necessary approvals before proceeding. Deletion should be a last resort, especially for production data. For most cases, making a record inactive is sufficient.

Deleting a Single Inactive Record: `deleteRecord()`

var inc = new GlideRecord('incident');
inc.addInactiveQuery(); // Ensure we're targeting inactive records
inc.addQuery('number','INC0010013'); // Target a specific inactive incident
inc.query();
if (inc.next()){
    inc.deleteRecord ();
    gs.print('Inactive incident ' + inc.number + ' has been permanently deleted.');
} else {
    gs.print('Inactive incident INC0010013 not found for deletion.');
}

Deleting Multiple Inactive Records: `deleteMultiple()`

var inc = new GlideRecord('incident');
inc.addInactiveQuery(); // Crucial: ONLY target inactive records
inc.addQuery('priority', 4); // Target all inactive incidents with Priority 4 (Low)
inc.query ();
inc.deleteMultiple ();
gs.print('All inactive incidents with Priority 4 have been permanently deleted.');

Deleting Records is Permanent!

Once a record is deleted using `deleteRecord()` or `deleteMultiple()`, it's gone from the database, typically without any possibility of recovery (unless you have a recent backup of your entire instance). Use these methods with the utmost care, and always confirm your filters are absolutely correct. Misuse can lead to catastrophic data loss.

Safety First: Access Control and Best Practices

Working with data, especially inactive or historical data, requires a mindful approach to security and performance.

Verifying Permissions: The `can` Methods

Before attempting any CRUD operation, especially in complex scripts or integrations, it's good practice to check if the current user (or the script's running context) has the necessary permissions. This can prevent errors and ensure your script fails gracefully.

  • `canCreate()`: Checks if a user can create records in the table.
  • `canRead()`: Checks if a user can read records in the table.
  • `canWrite()`: Checks if a user can update records in the table.
  • `canDelete()`: Checks if a user can delete records in the table.
var inc = new GlideRecord ('incident');
gs.print ('Can create incident? ' + inc.canCreate ());
gs.print ('Can read incident? ' + inc.canRead ());
gs.print ('Can write incident? ' + inc.canWrite ());
gs.print ('Can delete incident? ' + inc.canDelete ());

Result: These will print `true` or `false` based on the current user's ACLs for the incident table.

General Best Practices for GlideRecord

  • Always Initialize: Always start with `new GlideRecord('table_name');`.
  • Test, Test, Test: Seriously, non-production instances are your best friends.
  • Specific Queries: Be as specific as possible with your `addQuery()` or `addEncodedQuery()` to minimize the dataset returned, improving performance.
  • Avoid Large Result Sets: If you're querying thousands or millions of records, be extremely cautious. Consider pagination or breaking down your script into smaller, more manageable chunks.
  • `gs.log()` for Debugging: Use `gs.log()`, `gs.print()`, or `gs.info()` (for background scripts) to output variables and track script execution during development. `gs.info()` is generally preferred in current ServiceNow versions.
  • Comments: Document your code! Explain complex logic, especially for sensitive operations like deletion.
  • Graceful Error Handling: Use `if (gr.next())` checks before attempting to work with records, especially for single record operations.

Troubleshooting Common Issues

Even seasoned developers run into bumps. Here are some common issues when working with GlideRecord and inactive records:

  • "My script isn't finding any records!"
    • Check `active` status: Are the records you're looking for actually inactive? If so, did you use `addInactiveQuery()`? Remember, GlideRecord often implicitly filters for `active=true` if you don't specify otherwise.
    • Typos in field names or values: Double-check field names and values for case sensitivity and accuracy.
    • Incorrect operators: Are you using `=`, `>`, `CONTAINS` correctly for your intended filter?
    • ACLs: Does the user running the script (or the system) have read access to the records/table? Check `canRead()`.
  • "My updates / deletions are not working or affecting the wrong records!"
    • Missing `addQuery()`: Did you forget to filter your records before calling `updateMultiple()` or `deleteMultiple()`? This is extremely dangerous.
    • Wrong `sys_id` or `number`: For single record operations, ensure you're using the correct identifier.
    • Business Rules interfering: If updates aren't sticking or unexpected actions occur, remember `setWorkflow(false)` might be needed.
    • Incorrect `active` status in the query: Ensure you're targeting the correct `active` state (e.g., not trying to delete active records when you meant inactive).
  • "My script is running too slow or timing out!"
    • Large result sets: You're trying to process too many records at once. Refine your query.
    • Unindexed fields: Queries on unindexed fields can be slow. Consider asking an admin to index frequently queried fields.
    • Inefficient `while` loop: Are you performing heavy operations inside the loop for every record? Look for ways to optimize.

Interview Relevance: Demonstrating Your ServiceNow Prowess

Understanding GlideRecord, especially the nuances of `addActiveQuery()` and `addInactiveQuery()`, is a fantastic way to impress in a ServiceNow developer or administrator interview. It shows a deeper understanding of the platform's data model and best practices.

Be prepared for questions like:

  • "Explain the purpose of GlideRecord and when you would use it."
  • "What's the difference between `addQuery()` and `addEncodedQuery()`?"
  • "How would you retrieve a list of all closed incidents from last month?" (Hint: `addInactiveQuery()` + `addQuery('sys_updated_on', '>=', '...')`)
  • "When would you use `setWorkflow(false)` or `autoSysFields(false)`?"
  • "Describe a time you had to troubleshoot a GlideRecord script. What was the problem and how did you solve it?"
  • "What are the best practices for deleting records in ServiceNow?" (Emphasize caution and alternatives like inactivation.)

Conclusion: Empowering Your ServiceNow Data Management

Working with inactive records in ServiceNow might seem like a niche topic, but it's fundamental to maintaining a healthy, efficient, and accurate platform. By mastering GlideRecord's querying capabilities, especially `addActiveQuery()` and `addInactiveQuery()`, you gain precise control over your data, whether it's currently live or part of your organizational history.

Remember the golden rules: always test thoroughly, understand the implications of your actions (especially deletions), and use the right tool for the job. GlideRecord is a powerful ally in your ServiceNow journey, empowering you to manage your data with confidence and precision.

So go forth, experiment (in non-prod!), and continue building amazing things in ServiceNow. The world of inactive records is no longer a dark corner, but a well-lit archive waiting for your skilled touch!


Scroll to Top