Skip to content

NQL Basics

Note

Introduction to Nexthink Query Language (NQL) - understanding query structure, basic concepts, and getting started.

Overview

Nexthink Query Language (NQL) is a powerful query language designed for analyzing endpoint data in Nexthink. Unlike traditional SQL, NQL is optimized for time-series data and endpoint analytics, making it perfect for IT troubleshooting and monitoring.

Key characteristics:

  • Pipe-based syntax (similar to PowerShell)
  • Built for time-series endpoint data
  • Optimized for performance at scale
  • Focus on real-time IT insights

Basic Query Structure

Every NQL query follows this pattern:

<table> [time_selection]
| <clause> <parameters>
| <clause> <parameters>

Example:

/* Find devices with high CPU usage */
devices during past 7d
| include device_performance.events
| where cpu_usage.avg > 50
| list device.name, cpu_usage.avg

Key Points

  • Queries always start with a table name
  • Event tables require a time selection (during past Xd or past Xh)
  • Pipe | character chains clauses together
  • Clauses execute in order from top to bottom

Adding Comments to Queries

NQL supports multi-line comments using /* */ syntax:

/* This is a comment explaining the query purpose */
devices during past 7d
/* Filter to high CPU devices */
| include device_performance.events
| where cpu_usage.avg > 50
| list device.name, cpu_usage.avg

Multi-line comments:

/*
This query finds devices experiencing
high CPU usage over the past week
Author: IT Team
*/
devices during past 7d
| include device_performance.events
| where cpu_usage.avg > 50
| list device.name, cpu_usage.avg

Comment Limitation

Comments do NOT work within clauses - they only work between clauses.

/* ✅ CORRECT - Comment between clauses */
devices
/* This filters to Windows */
| where operating_system.platform == windows

/* ❌ WRONG - Comment within clause (will error!) */
| where /* inline comment */ operating_system.platform == windows

Essential Keywords

Core Clauses (in typical order)

Clause Purpose Example
where Filter results (use early!) where os.name = "Windows 11"
with Join events (only objects WITH events) with execution.crashes during past 7d
include Join events (ALL objects, even without events) include execution.events during past 7d
compute Calculate per-record metrics compute total = memory.sum()
summarize Aggregate to single result or groups summarize avg_cpu = cpu.avg()
list Select fields to display list device.name, metric
sort Order results sort metric desc
limit Restrict number of results limit 20

Clause Order Matters!

/* ✅ Correct order - filter early for performance */
devices during past 7d
| where device.name = "LAPTOP-001"
| include execution.events
| compute metric = cpu_time.sum()
| list device.name, metric

/* ❌ Wrong order - bad performance (filters after processing all data) */
devices during past 7d
| include execution.events
| compute metric = cpu_time.sum()
| where device.name = "LAPTOP-001"  /* Too late! */
| list device.name, metric

Critical Rule: Clause Execution Order

The standard order is: wherewith/includecomputesummarizelistsortlimit

Filtering early with where dramatically improves performance!

Common Tables

Primary Tables

devices

  • Device inventory and current state
  • Use for: Hardware info, OS details, current configuration
  • No time selection required

execution.events

  • Application execution data (sampled every 5-15 minutes)
  • Use for: CPU usage, memory consumption, process monitoring
  • Requires time selection
  • Retention: 8 days (high-res), 30 days (low-res)

execution.crashes

  • Application crash data
  • Use for: Crash analysis, stability monitoring
  • Requires time selection

connection.events

  • Network connection data
  • Use for: Network troubleshooting, bandwidth analysis
  • Requires time selection
  • Retention: 8 days (high-res), 30 days (low-res)

device_performance.events

  • System performance metrics
  • Use for: CPU, memory, disk performance
  • Requires time selection

Time Selection

Two Resolutions

Daily (low-resolution):

execution.events during past 7d
- Uses daily aggregated data - Cloud instance timezone - Max 30 days retention - Best for: Trends, reports, dashboards

Hourly (high-resolution):

execution.events during past 48h
- Uses 5-15 minute samples - User's browser timezone - Max 8 days retention - Best for: Troubleshooting, recent analysis

Important: past 2dpast 48h

These are NOT equivalent! - past 2d = Daily aggregated data in cloud timezone - past 48h = High-resolution samples in user timezone

They return different data ranges and different levels of detail.

Real-World Examples

Example: Find Devices with Low Disk Space

Scenario: IT needs to identify devices running low on disk space to prevent issues.

/* Find devices with less than 10GB free disk space */
devices during past 7d
| include device_performance.events
| compute
current_free_space_mb = free_disk_space.last(),
avg_free_space_mb = free_disk_space.avg()
| where current_free_space_mb < 10000  # Less than ~10GB
| list device.name,
   current_free_space_mb,
   avg_free_space_mb
| sort current_free_space_mb asc

Explanation: 1. Start with devices table 2. include performance events to get disk metrics (includes all devices) 3. compute current and average free space 4. where filters for devices below 10GB threshold 5. list displays relevant fields 6. sort shows most critical devices first

Example: Count Outlook Crashes

Scenario: Users report Outlook keeps crashing. How bad is it?

/* Count Outlook crashes in the past week */
execution.crashes during past 7d
| where binary.binary.name in ["outlook.exe", "OUTLOOK.EXE"]
| summarize
total_crashes = count(),
affected_devices = device.name.count(),
affected_users = user.name.count()

Explanation: 1. Query execution.crashes for past 7 days 2. Filter for Outlook executable (case variations) 3. Aggregate crash data: total events, unique devices, unique users

Common Patterns

Pattern 1: Top N Devices by Metric

/* Top 20 devices by CPU usage */
execution.events during past 7d
| summarize avg_cpu = cpu_time.avg() by device.name
| sort avg_cpu desc
| limit 20

Pattern 2: Trend Over Time

/* Daily crash trend */
execution.crashes during past 30d
| where binary.binary.name = "outlook.exe"
| summarize crash_count = count() by 1d
| sort start_time asc

Pattern 3: All Devices with Metric

/* All devices with crash count (including 0) */
devices
| include execution.crashes during past 7d
| compute crash_count = count()
| list device.name, crash_count
| sort crash_count desc

Tips & Tricks

Always Specify Time Windows

Event tables require time selection. Get in the habit of always adding during past Xd or past Xh.

/* GOOD */
execution.events during past 7d

/* BAD - will error */
execution.events

Test with limit During Development

Add | limit 10 while building queries to see results quickly, then remove when finalized.

devices during past 7d
| include execution.events
| compute metric = cpu_time.sum()
| list device.name, metric
| limit 10  # Remove this when ready for full results

Don't Use count() on Sampled Events

/* BAD - Returns sample count (not useful) */
execution.events during past 7d
| summarize total = count()
/* Returns: 1,245,892 (number of samples, not meaningful) */

/* GOOD - Count unique objects */
execution.events during past 7d
| summarize
unique_devices = device.count(),
unique_applications = application.count()
/* Returns: 142 devices, 38 apps (actual business value!) */

Additional Resources