Querying ref_ Fields on Extended Tables

When to use them, when to avoid them, and what they cost

ref_ queries are one of those ServiceNow features that are useful, slightly obscure, and easy to misuse.

They show up when you start from a base table such as task, but need to filter on a field that exists only on a child table such as incident.caller_id.

Used well, they help you express the query you actually need. Used carelessly, they make the script harder to read and can add unnecessary cost.

1. What ref_ actually means

Suppose you are querying task, but the filter you need exists only on incident.

This is where ref_ comes in:

var gr = new GlideRecord("task");
gr.addQuery("ref_incident.caller_id", userSysId);
gr.query();

ref_incident.caller_id means:

  • start from the parent table task
  • look at rows whose concrete class is incident
  • apply the condition to the caller_id field from that child table

This is not the same as normal dot-walking on a reference field.

Normal dot-walking follows a reference, for example:

gr.addQuery("caller_id.department", deptSysId);

ref_ is different. It is about table extension, not about a reference relationship.

2. When ref_ is the right tool

ref_ makes sense when the query truly has to start from the parent table.

Typical cases:

  • you are working with a generic task query
  • the API or configuration starts from the parent table
  • you need a combined result set that may include several task-based classes
  • the filter depends on a field that exists only on one child table

Reasonable example:

var gr = new GlideRecord("task");
gr.addActiveQuery();
gr.addQuery("sys_class_name", "incident");
gr.addQuery("ref_incident.caller_id", userSysId);
gr.query();

That is much better than loading a broad set of tasks and figuring it out later in the loop.

3. The common wrong approach

The most expensive mistake is querying the parent table too broadly and then applying the child-table logic in script.

Bad:

var gr = new GlideRecord("task");
gr.addActiveQuery();
gr.query();

while (gr.next()) {
  if (gr.getRecordClassName() == "incident" &&
      gr.ref_incident.caller_id == userSysId) {
    // process record
  }
}

Why this is bad:

  • the database returns more rows than you need
  • the class filter happens too late
  • the child-field condition happens too late
  • the loop does work that the query should have done

If the requirement can be expressed in the query, put it in the query.

Better:

var gr = new GlideRecord("task");
gr.addActiveQuery();
gr.addQuery("sys_class_name", "incident");
gr.addQuery("ref_incident.caller_id", userSysId);
gr.query();

4. The better question: do you need the parent table at all?

Just because ref_ works does not mean it is the best option.

If the business question is really “find incidents for this caller,” query incident directly.

Best in that case:

var gr = new GlideRecord("incident");
gr.addActiveQuery();
gr.addQuery("caller_id", userSysId);
gr.query();

This is usually better because:

  • the intent is clearer
  • the query is narrower
  • you avoid ref_ entirely
  • the platform does not need to start from a broader base table

This is the main rule I keep in mind:

Use ref_ only when you must query from the parent table. If you already know the child table you want, query the child table.

5. Performance implications of ref_

The performance story is not “never use ref_.”

The real issue is that ref_ is usually paired with a parent table that is broader than the actual data you want.

For example, task can represent many classes. If your real target is only incident, then a task query plus ref_incident... often has more work to do than a direct incident query.

That does not automatically make the query bad, but it means you should be deliberate.

Good habits:

  • add the class restriction explicitly
  • add other selective conditions early
  • avoid broad parent-table scans
  • use setLimit() when you only need a small batch
  • do not use ref_ in a hot path if a direct child-table query would do the same job more simply

Safer pattern:

var gr = new GlideRecord("task");
gr.addActiveQuery();
gr.addQuery("sys_class_name", "incident");
gr.addQuery("ref_incident.caller_id", userSysId);
gr.orderByDesc("sys_created_on");
gr.setLimit(100);
gr.query();

The key is not the ref_ syntax itself. The key is how much unnecessary data you ask the platform to consider before it can apply that condition.

6. ref_ also shows up in encoded queries

The same idea can appear in encoded query form:

var gr = new GlideRecord("task");
gr.addEncodedQuery("sys_class_name=incident^ref_incident.caller_id=" + userSysId);
gr.query();

This is useful when:

  • you are copying a filter from a list
  • a module or URL uses sysparm_query
  • a configuration stores a field path as text

But the same guidance still applies: if you only need incident, a direct incident query is still cleaner.

7. Do not confuse ref_ with normal field access

One reason ref_ feels strange is that it looks like a field name, but it is really a notation for reaching into a child table from the parent context.

That is why this thinking is usually safer:

  • ref_ is a query path
  • it is not a sign that the parent table truly owns that field
  • it is a workaround for table hierarchy, not a replacement for choosing the right table

If your script needs to do a lot of work with incident-specific fields, that is another hint that you should probably be querying incident, not task.

8. A practical rule of thumb

When I see a ref_ requirement, I usually ask these questions in order:

  1. Can I query the child table directly?
  2. If not, do I really need the parent table result set?
  3. If yes, have I restricted the class and other filters enough?
  4. Am I filtering in the query instead of in the loop?

That sequence catches most bad ref_ usage.

Final thought

ref_ is a useful tool, especially when you are forced to start from a base table such as task but need child-table fields.

The mistake is not using it. The mistake is using it when a direct child-table query would be simpler, clearer, and cheaper.

Pick the narrowest table that matches the problem. When you truly need the parent table, use ref_ intentionally and keep the query selective.

comments powered by Disqus

Related