Leveraging SQL Operators in GlideRecord for Powerful Queries






Mastering SQL Operators in GlideRecord: Your ServiceNow Data Powerhouse


Mastering SQL Operators in GlideRecord: Your ServiceNow Data Powerhouse

Hey there, fellow ServiceNow enthusiast! Ever felt like you’re trying to navigate a vast ocean of data within the platform, but without a traditional SQL compass? You’re not alone. While SQL is the lingua franca for database interactions in many systems, ServiceNow offers its own powerful, JavaScript-based alternative: GlideRecord. And trust me, once you get the hang of it, you’ll wonder how you ever lived without it.

In this deep dive, we’re going to demystify GlideRecord, especially focusing on how we, as developers, can leverage “SQL-like” operators to precisely filter, sort, and manipulate data. We’ll cover everything from the basics to advanced techniques, ensuring you walk away with the confidence to build robust, data-driven customizations.

The ServiceNow Developer’s Toolkit: A Glide API Overview

Before we jump straight into GlideRecord, let’s set the stage. As a ServiceNow developer, you’re constantly looking for ways to extend the platform’s capabilities or bend it to meet unique business requirements. That’s where Glide APIs come in – your ultimate scripting arsenal.

Glide APIs are a collection of JavaScript classes and methods designed specifically for interacting with the ServiceNow platform. They provide a structured, flexible way to customize application behavior through scripting, allowing you to perform complex operations without directly touching the underlying database with raw SQL.

Client-Side vs. Server-Side: A Quick Distinction

Glide APIs are broadly categorized into two types:

  • Client-Side APIs: These run in the user’s browser, typically for UI interactions, form validation, or dynamic content loading. Examples include GlideForm, GlideUser, GlideAjax.
  • Server-Side APIs: These execute on the ServiceNow server, handling database operations, business logic, integrations, and more. This is where our hero, GlideRecord, shines the brightest. Other notable server-side APIs include GlideSystem, GlideDate, GlideDateTime, and GlideAggregation.

For any heavy-lifting involving data storage and retrieval, especially the kind that would typically involve SQL, we turn to server-side scripting, and more specifically, GlideRecord.

GlideRecord: Your Gateway to Data in ServiceNow

Think of GlideRecord as your trusted data companion within ServiceNow. It’s a special JavaScript class, running entirely on the server-side, that acts as an abstraction layer between your scripts and the platform’s database. Its primary mission? To perform CRUD operations – Create, Read, Update, Delete – without you ever having to write a single SQL query.

Why GlideRecord?

You might be asking, “Why can’t I just write SQL directly?” Good question! ServiceNow is designed for enterprise-grade security and stability. Direct SQL interaction is generally prohibited to prevent:

  • Accidental data corruption.
  • Security vulnerabilities (SQL injection, anyone?).
  • Performance bottlenecks from unoptimized queries.
  • Inconsistencies with the platform’s own data integrity rules and business logic.

GlideRecord elegantly bypasses these issues. It generates optimized SQL queries behind the scenes, ensuring that your data operations adhere to ServiceNow’s best practices and access controls. It handles both rows and columns in the database, giving you full control over record management.

A Crucial Note on Testing!

Always, always, ALWAYS test your GlideRecord queries on a non-production instance first! An incorrectly constructed query, whether it’s an invalid field name, a typo, or a logical error, can lead to unintended consequences. Running a faulty insert(), update(), or deleteMultiple() method can result in significant data loss or corruption, which is a nightmare no one wants to wake up to. Practice safe scripting!

The Fundamentals of GlideRecord:

  • Most Common API: You’ll use it constantly.
  • Server-Side Execution: Runs on the ServiceNow instance.
  • SQL Query Generator: Transforms JavaScript into database commands.
  • CRUD Powerhouse: Create, Read, Update, Delete records with ease.

Beyond SQL: How GlideRecord Works its Magic

At its core, GlideRecord provides an object-oriented way to interact with your data. Instead of raw SQL statements, you use JavaScript methods to build your queries and perform operations. This makes your code more readable, maintainable, and less prone to common database errors.

GlideRecord Architecture & API Mapping

Imagine you want to find all active incidents. In SQL, you’d write: SELECT * FROM incident WHERE active = true;. With GlideRecord, it looks like this:

var incidentGr = new GlideRecord('incident');
incidentGr.addQuery('active', true);
incidentGr.query();

GlideRecord takes your addQuery(), orderBy(), and other methods, and internally translates them into a highly optimized SQL statement, executes it against the database, and then returns the results back to your script as a GlideRecord object.

Mastering GlideRecord Queries: The Heart of Data Interaction

Querying is the bread and butter of GlideRecord. It’s how you fetch specific records that match your criteria. Let’s break down the essential steps and methods.

The Basic Query Loop: query(), next(), and while()

Every GlideRecord query typically follows a pattern: initialize the object, add your filters, execute the query, and then loop through the results.

var inc = new GlideRecord('incident'); // 1. Initialize GlideRecord object for 'incident' table
inc.query();                         // 2. Execute the query (fetches all records if no addQuery is present)

while (inc.next()) {                 // 3. Loop through each returned record
    gs.print(inc.number + ' - ' + inc.short_description); // 4. Access field values for the current record
}
// Result: Prints all incident numbers and short descriptions

Debugging Tip: Use gs.print() for script background/fix scripts and gs.info() for business rules/workflow scripts. gs.info() writes to the system log, which is excellent for tracing execution in production environments.

The Power of addQuery(): Simulating SQL WHERE Clauses

addQuery() is your go-to method for filtering data. It allows you to specify conditions that records must meet to be included in your result set. It’s the equivalent of the SQL WHERE clause.

Basic addQuery(): Field and Value

The simplest form of addQuery() takes two arguments: the field name and the value to match.

var inc = new GlideRecord('incident');
inc.addQuery('priority', '1'); // Find incidents where priority is '1' (Critical)
inc.query();
while(inc.next()){
    gs.print(inc.number);
}
// Result: Prints all incident numbers with Priority 1

Multiple addQuery(): The Logical AND

When you use multiple addQuery() statements, they are implicitly combined with a logical AND operator. This means a record must satisfy ALL conditions to be returned.

var inc = new GlideRecord('incident');
inc.addQuery('active', true);           // Condition 1: Incident is active
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 active, Priority 1, Software category incidents

SQL Operators in addQuery(): Your Data Filtering Arsenal

This is where GlideRecord truly shines in its SQL-like capabilities. addQuery() can take a third argument: an operator, allowing you to build complex comparison criteria.

Here are the common operators and how to use them:

Numerical and Exact Match Operators: =, !=, >, >=, <, <=

These are standard comparison operators, perfect for numbers, dates, or exact string matches.

  • = (equals)
  • != (does not equal)
  • > (greater than)
  • >= (greater than or equal to)
  • < (less than)
  • <= (less than or equal to)
var inc = new GlideRecord('incident');
inc.addActiveQuery(); // Equivalent to inc.addQuery('active', true);
inc.addQuery('priority', '<=', 2); // Priority is 1 or 2 (Critical or High)
inc.query();
while(inc.next()){
    gs.print(inc.number + ' - ' + inc.priority.getDisplayValue());
}
// Result: Prints active incidents with Priority Critical or High.

String Comparison Operators (Case-Insensitive for most, use UPPER CASE for operator):

These operators are invaluable for working with text fields, allowing for partial matches and list comparisons.

  • IN: Value is in a list of possibilities.
  • NOT IN: Value is NOT in a list of possibilities.
  • STARTSWITH: Field value starts with a specific string.
  • ENDSWITH: Field value ends with a specific string.
  • CONTAINS: Field value contains a specific string (like SQL %string%).
  • DOES NOT CONTAIN: Field value does NOT contain a specific string.
  • INSTANCEOF: Used for querying across table hierarchies (e.g., 'task' INSTANCEOF 'incident' will find all incidents).
var inc = new GlideRecord('incident');
inc.addActiveQuery();
inc.addQuery('priority', '<=', 2); // Active, Priority Critical or High
inc.addQuery('short_description', 'CONTAINS', 'SAP'); // Short description contains "SAP"
inc.query();
while(inc.next()){
    gs.print(inc.number + ' ' + inc.short_description);
}
// Result: Prints active incidents (P1/P2) whose short descriptions contain "SAP".

// Example with IN operator
var categories = ['software', 'hardware'];
var incCat = new GlideRecord('incident');
incCat.addQuery('category', 'IN', categories); // Category is either 'software' OR 'hardware'
incCat.query();
while(incCat.next()) {
    gs.print(incCat.getValue('number') + ' - ' + incCat.getValue('category'));
}
// Result: Prints incidents with categories 'software' or 'hardware'.

// Example with STARTSWITH
var incNet = new GlideRecord('incident');
incNet.addQuery('category', 'STARTSWITH', 'net'); // Category starts with 'net' (e.g., Network)
incNet.query();
while(incNet.next()) {
    gs.print(incNet.number + ' - ' + incNet.category);
}
// Result: Prints incidents whose category starts with 'net'.

Troubleshooting: Common addQuery() Pitfalls

  • Case Sensitivity for Strings: While operator names (like 'CONTAINS') are typically uppercase, the actual values you're searching for might be case-sensitive depending on the database configuration. Always test!
  • Incorrect Field Names: A typo in a field name will simply result in no records being returned or an error if strict validation is enabled.
  • Mixing AND/OR Logic: Multiple addQuery() calls are always AND. For OR logic, you'll need addEncodedQuery() or addOrCondition().

Interview Relevance: addQuery()

Interviewers often ask: "How do you filter records using GlideRecord?" or "How would you find all incidents opened by a specific user?" This is your moment to explain addQuery(), and for extra credit, mention the different operators.

Bonus: "How do you achieve a 'LIKE' clause in GlideRecord?" The answer is the CONTAINS operator.

Streamlining with addEncodedQuery(): When Queries Get Complex

As your filtering criteria grow, stringing together many addQuery() statements can become cumbersome, especially if you need complex AND/OR logic. Enter addEncodedQuery(), your secret weapon for advanced queries.

An encoded query is a string that represents a set of conditions, just like what you see in the filter builder on any list view in ServiceNow. You can build these conditions in the UI and then simply copy the query string.

  1. Navigate to any list view (e.g., Incident).
  2. Apply your desired conditions using the filter builder (e.g., Active = true AND Priority = 1 OR Category = 'software').
  3. Right-click the breadcrumbs (the filter condition string) and select "Copy query."
  4. Paste this string directly into your addEncodedQuery() method.
var inc = new GlideRecord('incident');
// Example Encoded Query: active=true^category=software^ORcategory=hardware^priority=1
inc.addEncodedQuery('active=true^category=software^priority=1');
inc.query();
while(inc.next()){
    gs.print(inc.number);
}
// Result: Prints incidents matching the encoded query.

// You can also store the query in a variable for better readability
var myComplexQuery = 'active=true^category=software^priority=1^ORDERBYDESCnumber';
var incEncoded = new GlideRecord('incident');
incEncoded.addEncodedQuery(myComplexQuery);
incEncoded.query();
while (incEncoded.next()){
    gs.print(incEncoded.number);
}
// Result: Prints incidents matching the variable-held encoded query.

When to use addEncodedQuery(): When you need complex AND/OR logic, dynamic queries based on user input, or when dealing with a large number of filtering conditions. For simple field=value queries, addQuery() is often clearer.

Troubleshooting: addEncodedQuery()

  • Malformed Queries: A single typo in the encoded string can invalidate the entire query. Always copy directly from the UI where possible.
  • Performance: While powerful, overly complex encoded queries can sometimes impact performance. Always test.

Quick Filters: addActiveQuery() and addInactiveQuery()

These are convenient shortcuts for filtering by the active field.

  • addActiveQuery(): Adds active=true to your query.
  • addInactiveQuery(): Adds active=false to your query.
var incActive = new GlideRecord('incident');
incActive.addActiveQuery(); // Filters for active=true
incActive.addQuery('priority', '1');
incActive.query();
while (incActive.next()){
    gs.info(incActive.number);
}
// Result: Prints active, Priority 1 incidents.

var incInactive = new GlideRecord('incident');
incInactive.addInactiveQuery(); // Filters for active=false
incInactive.addQuery('priority', '1');
incInactive.query();
while (incInactive.next()) {
    gs.print(incInactive.number);
}
// Result: Prints inactive, Priority 1 incidents (e.g., closed incidents).

Refining Your Results: Sorting, Limiting, and More

Beyond just filtering, GlideRecord offers methods to control the presentation and quantity of your results.

Sorting Your Data: orderBy() and orderByDesc()

These methods allow you to sort your query results, similar to SQL's ORDER BY clause.

  • orderBy('field_name'): Sorts in ascending order.
  • orderByDesc('field_name'): Sorts in descending order.
var inc = new GlideRecord('incident');
inc.addQuery('priority', '1');
inc.addQuery('category', 'software');
inc.orderBy('short_description'); // Sorts by short description A-Z
inc.query();
while(inc.next()){
    gs.print(inc.number + ' - ' + inc.short_description);
}
// Result: Prints Priority 1, Software incidents, sorted by short description ascending.

var incDesc = new GlideRecord('incident');
incDesc.addQuery('priority', '1');
incDesc.orderByDesc('short_description'); // Sorts by short description Z-A
incDesc.query();
while(incDesc.next()){
    gs.print(incDesc.number + ' - ' + incDesc.short_description);
}
// Result: Prints Priority 1 incidents, sorted by short description descending.

Limiting Results: setLimit()

Just like SQL's LIMIT, this method restricts the number of records returned by your query.

var inc = new GlideRecord('incident');
inc.addQuery('priority', '1');
inc.orderByDesc('sys_created_on'); // Get the latest ones
inc.setLimit(5); // Only retrieve the top 5 records
inc.query();
while(inc.next()){
    gs.print(inc.number + ' - ' + inc.short_description);
}
// Result: Prints the 5 most recently created Priority 1 incidents.

Paginating Results: chooseWindow()

This method lets you select a specific range of records from your query, useful for pagination or processing chunks of data. It takes two arguments: offset (starting record index, 0-based, inclusive) and limit (the number of records to return, exclusive of the end index). So, chooseWindow(3, 7) will return records at index 3, 4, 5, 6 (4 records).

var inc = new GlideRecord('incident');
inc.addQuery('priority', '1');
inc.addActiveQuery();
inc.chooseWindow(3, 7); // Start from the 4th record (index 3), get 4 records (up to index 6)
inc.query();
while(inc.next()){
    gs.print(inc.number);
}
// Result: Prints 4 records, starting from the 4th matched incident.

Retrieving and Manipulating Data: Beyond the Query

Once you have your GlideRecord object, you can access its properties and methods to work with the data.

Fetching a Single Record: get()

The get() method is perfect for retrieving a single record when you know its unique identifier (like sys_id) or a unique field (like number).

var inc = new GlideRecord('incident');
// Fetch by incident number
inc.get('number', 'INC0009005');
gs.print('Sys_id for INC0009005: ' + inc.sys_id);

// Fetch by sys_id
// var specificSysId = 'YOUR_INCIDENT_SYS_ID'; // Replace with an actual sys_id
// inc.get(specificSysId);
// gs.print('Incident number for ' + specificSysId + ': ' + inc.number);
// Result: Prints the sys_id or incident number of the fetched record.

Accessing Field Values: getValue() vs. getDisplayValue()

This is a crucial distinction in ServiceNow development:

  • getValue('field_name'): Returns the actual stored value of the field (e.g., '1' for Critical priority, 'sys_id' for reference fields).
  • getDisplayValue('field_name'): Returns the user-friendly displayed value (e.g., 'Critical' for priority, 'Jane Doe' for a reference field).
var inc = new GlideRecord('incident');
inc.addQuery('priority', '1');
inc.setLimit(1);
inc.query();
if (inc.next()) {
    gs.print('Actual Priority Value: ' + inc.getValue('priority'));          // e.g., '1'
    gs.print('Display Priority Value: ' + inc.getDisplayValue('priority'));  // e.g., 'Critical'
}
// Result: Shows the difference between actual and display values for priority.

Interview Relevance: getValue() vs. getDisplayValue()

This is a classic interview question! Be prepared to explain when and why you'd use one over the other. Typically, for backend processing (logic, integrations), you use getValue(). For user-facing output or messages, getDisplayValue() is your choice.

Counting Records: getRowCount()

This method returns the total number of records found by your query, equivalent to SQL's COUNT().

var inc = new GlideRecord('incident');
inc.query();
gs.print('Total incidents: ' + inc.getRowCount());
// Result: Prints the total count of incident records.

var activeUsers = new GlideRecord('sys_user');
activeUsers.addQuery('active', true);
activeUsers.query();
gs.print('Number of active users: ' + activeUsers.getRowCount());
// Result: Prints the count of active users.

Getting Context: getTableName() and getRecordClassName()

These methods help identify the table you're currently working with.

var cr = new GlideRecord('change_request');
gs.print('Current table name: ' + cr.getTableName());
gs.info('Record Class Name: ' + cr.getRecordClassName());
// Result: Both will typically return 'change_request' for this example.

Debugging Queries: getEncodedQuery()

After you've built a query using addQuery() methods, you can use getEncodedQuery() to see the full encoded string that GlideRecord constructed. This is incredibly useful for debugging complex queries.

var inc = new GlideRecord('incident');
inc.addActiveQuery();
inc.addQuery('priority', '1');
inc.addQuery('category', 'software');
// inc.query(); // You don't need to query to get the encoded string
gs.print('Constructed Encoded Query: ' + inc.getEncodedQuery());
// Result: Prints the full encoded query string (e.g., active=true^priority=1^category=software)

More Field Access: getElement() and getUniqueValue()

  • getElement('field_name'): Returns a GlideElement object for the specified field, allowing access to properties like getDisplayValue() or getValue() on the element itself, or even setting error messages.
  • getUniqueValue(): Returns the sys_id of the current record (or another unique key if configured).
var inc = new GlideRecord('incident');
inc.query();
if (inc.next()) {
    var shortDescElement = inc.getElement('short_description');
    gs.print('Short Description (via getElement): ' + shortDescElement.getValue());
    gs.print('Unique ID of this record: ' + inc.getUniqueValue());
}

Getting Record Links: getLink()

Useful for constructing direct URLs to records, for notifications or external systems.

var inc = new GlideRecord('incident');
inc.addActiveQuery();
inc.setLimit(1);
inc.query();
if(inc.next()){
    // gs.getProperty('glide.servlet.uri') gets the base URL of your instance
    gs.print('Link to incident ' + inc.number + ': ' + gs.getProperty('glide.servlet.uri') + inc.getLink(false));
}
// Result: Returns a clickable URL to the first active incident.

CRUD Operations with GlideRecord

Beyond reading, GlideRecord is your powerhouse for creating, updating, and deleting records.

Creating Records: initialize(), setValue(), insert(), newRecord()

  • initialize(): Prepares a new, empty record for insertion. It's like opening a blank form.
  • setValue('field_name', 'value') or directly gr.field_name = 'value': Sets the values for the fields.
  • insert(): Saves the new record to the database.
  • newRecord(): A shortcut that initializes a new record and assigns it a unique ID, effectively combining initialize() with some default value setting.
var newInc = new GlideRecord('incident');
newInc.initialize(); // Start a new record
newInc.category = 'network'; // Set field values directly
newInc.setValue('short_description', 'Firewall Issue Detected'); // Or use setValue
newInc.priority = 1;
newInc.insert(); // Save the new record
gs.print('New Incident Created: ' + newInc.number);
// Result: Creates a new incident and prints its number.

// Using newRecord()
var anotherInc = new GlideRecord('incident');
anotherInc.newRecord(); // Initializes and assigns a unique ID
anotherInc.short_description = 'Server outage reported';
anotherInc.category = 'hardware';
anotherInc.insert();
gs.print('Another Incident Created: ' + anotherInc.number);
gs.info('Is this a new record? ' + anotherInc.isNewRecord()); // will be false after insert
// Result: Creates another incident and prints its number.

Updating Records: setValue(), update(), updateMultiple()

  • update(): Saves changes to the current record in the GlideRecord object. Use after fetching a single record.
  • updateMultiple(): Updates all records that match the current query. Use with caution!
// Updating a single record
var incToUpdate = new GlideRecord('incident');
if (incToUpdate.get('number', 'INC0000057')) { // Fetch the specific incident
    incToUpdate.state = '2'; // Set state to 'In Progress'
    incToUpdate.update(); // Save changes
    gs.print('Incident INC0000057 updated to In Progress.');
}

// Updating multiple records
var incidentsToModify = new GlideRecord('incident');
incidentsToModify.addQuery('category', 'hardware'); // Query for hardware incidents
incidentsToModify.setValue('category', 'software'); // Change their category to software
incidentsToModify.updateMultiple(); // Apply change to ALL matching records
gs.print('All hardware incidents recategorized to software.');

Use updateMultiple() with Extreme Care! There's no undo for this. Always test on non-production and ensure your query is precise. An error here can mass-update or corrupt thousands of records.

Controlling System Fields and Business Rules: autoSysFields() and setWorkflow()

Sometimes, when updating records programmatically, you don't want to trigger all the usual system behaviors:

  • autoSysFields(false): Prevents the automatic update of system fields like sys_updated_on, sys_updated_by, sys_mod_count. Useful for data migration or when you want to preserve historical stamps.
  • setWorkflow(false): Prevents business rules, workflows, and flow designers from running on the updated record. Use when you're sure your script handles all necessary logic or to avoid infinite loops.
var incSilentUpdate = new GlideRecord('incident');
incSilentUpdate.addQuery('state', '1'); // Find incidents in 'New' state
incSilentUpdate.query();
while (incSilentUpdate.next()) {
    incSilentUpdate.autoSysFields(false); // Don't touch sys_updated_by/on
    incSilentUpdate.setWorkflow(false);   // Don't run business rules/workflows
    incSilentUpdate.state = '2'; // Set state to 'In Progress'
    incSilentUpdate.update();
}
gs.print('Incidents updated silently without affecting system fields or triggering workflows.');

Note: autoSysFields() might behave differently or be unavailable in scoped applications, depending on the ServiceNow version. Always check documentation for your version.

Interview Relevance: autoSysFields(false) and setWorkflow(false)

These are advanced topics that show a deeper understanding of platform behavior. Interviewers love questions like: "How do you update a record without changing its 'updated by' field?" or "How do you prevent business rules from running during a script update?" Knowing these methods demonstrates expertise.

Deleting Records: deleteRecord(), deleteMultiple()

Just like updates, deletions come in single and multiple flavors.

  • deleteRecord(): Deletes the current record in the GlideRecord object.
  • deleteMultiple(): Deletes all records that match the current query.
// Deleting a single record
var incToDelete = new GlideRecord('incident');
if (incToDelete.get('number', 'INC0010013')) { // Replace with a test incident you can delete!
    incToDelete.deleteRecord();
    gs.print('Incident INC0010013 deleted.');
}

// Deleting multiple records
var incidentsToDeleteMultiple = new GlideRecord('incident');
incidentsToDeleteMultiple.addQuery('priority', '4'); // Query for Priority 4 incidents
incidentsToDeleteMultiple.query(); // Important: run query BEFORE deleteMultiple if you want to know how many
var deletedCount = incidentsToDeleteMultiple.getRowCount(); // Get count before deletion
incidentsToDeleteMultiple.deleteMultiple(); // Delete ALL matching records
gs.print(deletedCount + ' Priority 4 incidents deleted.');

Deleting Records is IRREVERSIBLE! Seriously, back up your instance or use test data in non-production environments. There is no 'recycle bin' for deleted records.

Validating and Debugging Your GlideRecord Scripts

Good developers don't just write code; they write robust code that anticipates problems. GlideRecord provides methods for validation and sanity checks.

Checking Existence: isValid(), isValidField(), isValidRecord(), isNewRecord()

  • isValid(): Checks if the GlideRecord object is valid (i.e., if the specified table exists).
  • isValidField('field_name'): Checks if a field exists in the current table.
  • isValidRecord(): Checks if a record was actually found by a get() or query() operation.
  • isNewRecord(): Checks if the current record object has not yet been inserted into the database.
  • hasNext(): Useful within a loop, returns true if there are more records to process.
var incValid = new GlideRecord('incident');
gs.print('Is "incident" a valid table? ' + incValid.isValid()); // True

var nonExistentTable = new GlideRecord('uday_custom_table'); // Hypothetical table
gs.print('Is "uday_custom_table" a valid table? ' + nonExistentTable.isValid()); // False

gs.print('Does "incident" table have a "category" field? ' + incValid.isValidField('category')); // True

var specificInc = new GlideRecord('incident');
specificInc.get('number', 'INC0000001'); // Assuming this exists
gs.print('INC0000001 exists and is valid: ' + specificInc.isValidRecord()); // True

var newIncCheck = new GlideRecord('incident');
newIncCheck.initialize();
gs.print('Is this incident a new record? ' + newIncCheck.isNewRecord()); // True (before insert)

var incHasNext = new GlideRecord('incident');
incHasNext.setLimit(1);
incHasNext.query();
gs.print('Does the query have at least one record? ' + incHasNext.hasNext()); // True if records exist

Handling Null/Empty Fields: addNullQuery() and addNotNullQuery()

These methods allow you to specifically target records where a field's value is either empty or not empty.

  • addNullQuery('field_name'): Finds records where field_name is null/empty.
  • addNotNullQuery('field_name'): Finds records where field_name is NOT null/empty.
var incNullSD = new GlideRecord('incident');
incNullSD.addNullQuery('short_description'); // Find incidents with empty short description
incNullSD.query();
while (incNullSD.next()) {
    gs.print('Incident with null short description: ' + incNullSD.number);
}

var incNotNullSD = new GlideRecord('incident');
incNotNullSD.addNotNullQuery('short_description'); // Find incidents with a value in short description
incNotNullSD.query();
gs.print('Number of incidents with a short description: ' + incNotNullSD.getRowCount());

Advanced GlideRecord Techniques

Simulating SQL JOINs: addJoinQuery()

While GlideRecord doesn't do direct SQL JOINs in the traditional sense, addJoinQuery() allows you to link tables based on a common field and filter results from the "parent" GlideRecord based on conditions met in the "joined" table.

var prob = new GlideRecord('problem');
// Find problems that have at least one associated incident where the incident's caller is the problem's opened_by.
prob.addJoinQuery('incident', 'opened_by', 'caller_id');
prob.query();
while(prob.next()){
    gs.print('Problem with associated incident: ' + prob.number);
}
// Result: Displays problem records that have associated incidents meeting the join condition.

addJoinQuery() is powerful but can be complex. For very intricate join requirements or analytics, consider using database views or GlideAggregate.

Access Control Checks: canCreate(), canRead(), canWrite(), canDelete()

These methods are crucial for building secure applications. They respect ServiceNow's Access Control Lists (ACLs) and return true if the current user has the necessary permissions for the specified operation on the table or record.

var incACL = new GlideRecord('incident');
gs.print('Can current user create incidents? ' + incACL.canCreate());
gs.print('Can current user read incidents? ' + incACL.canRead());
gs.print('Can current user write to incidents? ' + incACL.canWrite());
gs.print('Can current user delete incidents? ' + incACL.canDelete());
// Result: Boolean values based on the logged-in user's ACLs for the incident table.

Deeper Field Control: getGlideObject(), getNumericValue(), setAbortAction()

These methods are for specific, often more advanced, use cases:

  • getGlideObject(): For certain field types (like GlideDateTime, GlideDuration), this returns the underlying Glide object, allowing access to its specific methods.
  • getNumericValue(): On GlideDate/GlideDateTime objects, returns the time in milliseconds since the epoch. Useful for date comparisons.
  • setAbortAction(true): Typically used in business rules to stop the current database transaction. For example, if a validation fails, you might set an error message and then abort the insert/update.
// Example in a Business Rule (context: 'current' record)
// if ((!current.u_date1.nil()) && (!current.u_date2.nil())) {
//     var start = current.u_date1.getGlideObject().getNumericValue();
//     var end = current.u_date2.getGlideObject().getNumericValue();
//     if (start > end) {
//         gs.addInfoMessage('Start date must be before end date.');
//         current.u_date1.setError('Start date must be before end date.');
//         current.setAbortAction(true); // Stop the record from being saved
//     }
// }
// Result: Prevents record save if start date is after end date, displaying an error.

Troubleshooting Common GlideRecord Issues

  • No Results Returned:
    • Check field names for typos.
    • Ensure your query conditions actually match existing data.
    • Did you call query() before while(gr.next())?
    • Are you testing on the correct instance with valid data?
  • Unexpected Results (Too many/too few):
    • Review your addQuery() and addEncodedQuery() for logical errors (AND vs. OR).
    • Use gs.print(gr.getEncodedQuery()) to see the actual query being executed.
    • Temporarily use setLimit() to inspect a few results.
  • Performance Problems:
    • Avoid querying inside loops if possible.
    • Use setLimit() if you only need a few records.
    • Optimize your queries to be as specific as possible.
    • Consider indexing fields that are frequently used in queries.
  • Errors in Scoped Applications:
    • Some global GlideRecord methods might behave differently or be restricted in custom scoped applications due to API protection. Consult the ServiceNow API documentation for your version.

GlideRecord in Interviews: What to Expect

GlideRecord is fundamental, so expect questions! Here's a quick rundown of common interview topics:

  • Basic CRUD Operations: Be ready to write simple scripts to create, read, update, and delete records.
  • addQuery() vs. addEncodedQuery(): Explain the differences, pros, and cons, and when to use each.
  • getValue() vs. getDisplayValue(): A classic. Understand the distinction and practical applications.
  • Advanced Control: Questions about setWorkflow(false) and autoSysFields(false) show you understand platform intricacies.
  • Performance: How would you optimize a GlideRecord script that's running slowly? (Indexing, avoiding loops, specific queries).
  • Scenario-Based Questions: "How would you find all incidents related to a specific problem record?" or "Write a script to close all resolved incidents older than 30 days."
  • Security: How do ACLs interact with GlideRecord? (Answer: GlideRecord respects ACLs automatically for can*() methods and general CRUD operations unless explicitly bypassed in certain contexts like background scripts by an admin).

Conclusion

GlideRecord is more than just a workaround for SQL; it's a powerful, secure, and intuitive way to interact with data in ServiceNow. By mastering its methods, especially the use of SQL-like operators within addQuery() and the flexibility of addEncodedQuery(), you gain immense control over the platform's data. You can build sophisticated custom applications, automate processes, and ensure your solutions are robust and efficient.

The key, as with any scripting, is practice. Get into your personal developer instance, open up a background script, and experiment with these methods. Break things, fix them, and learn from every query. Soon, you'll be wielding GlideRecord like a seasoned pro, transforming complex data challenges into elegant ServiceNow solutions. Happy scripting!


Scroll to Top