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:
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 Xdorpast 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.
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: where → with/include → compute → summarize → list → sort → limit
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):
- Uses daily aggregated data - Cloud instance timezone - Max 30 days retention - Best for: Trends, reports, dashboardsHourly (high-resolution):
- Uses 5-15 minute samples - User's browser timezone - Max 8 days retention - Best for: Troubleshooting, recent analysisImportant: past 2d ≠ past 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.
Test with limit During Development
Add | limit 10 while building queries to see results quickly, then remove when finalized.
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!) */
Related Topics
- Tables & Data Model - Deep dive into Nexthink tables
- Time Selection - Understanding time windows and retention
- where - Filtering Data - Filter early for better performance
- with vs include - Critical difference between join types
- Query Performance Guide - Write fast, efficient queries
Additional Resources
- Nexthink NQL Documentation (in Nexthink Portal)
- NQL Syntax Cheat Sheet - Quick reference
- Common Query Templates - Fill-in-the-blank templates