Transitioning to Advanced Search query language
If you're familiar with SQL queries in Sophos Central Live Discover, this guide helps you translate common query patterns to the Data Lake Search Advanced Search query language in Sophos XDR.
Overview of changes
The following table summarizes what has changed between Sophos Central Live Discover and Sophos XDR Advanced Search query language.
| Before (Live Discover) | After (Sophos XDR) | |
|---|---|---|
| Query language | Presto/Trino-dialect SQL (SELECT ... FROM xdr_data WHERE ...) | Advanced Search query language using a flat filter and pipe language |
| Where tables live | A named table (xdr_data, mdr_ioc_all, xgfw_data, ...) | A schema (process, auth, netflow, detection, ...), which is broader and normalized across products |
| Where queries are run | Central's Live Query or Data Lake query type, saved to the query table, launched via UI or automation, polled, results paginated | Data Lake Search UI, including Query Editor, Query Builder, and AI Search |
Navigating Data Lake Search
In Sophos XDR, Data Lake Search is the primary interface for querying detections and events in your tenant. To access it, go to Security Operations > Data Lake Search.
Data Lake Search offers three main search modes: Query Editor, Query Builder, and AI Search. It also includes a Schema Library and Data Exports.
Query Editor
With Query Editor, write advanced, flexible queries using a custom syntax. For full documentation, see Advanced Search query language.
When to use Query Editor
Query Editor is the closest experience to what you used before in Live Discover, and what the rest of this guide primarily focuses on.
Query Editor features the following capabilities:
- Operators for string matching, regex, wildcards, and CIDR notation.
- Inline help, schema library, and query examples.
- Save and add searches to cases.
- Column customization and result filtering in the UI.
- Support for logical types (like
@ip,@user, and@host) to simplify cross-schema searches. For more information, see Logical types.
Query Builder
Query Builder offers a visual, guided interface for constructing queries without writing code. For full documentation, see Query Builder.
When to use Query Builder
Query Builder is useful for the parts of your old workflow that utilized filling in filters, rather than hand-crafted SQL.
Query Builder features the following capabilities:
- Select detections or events and choose event types, such as process, auth, and netflow.
- Add multiple criteria and use AND/OR logic.
- Support for nested queries and logical types.
- Save and add searches to cases.
AI Search
Use natural language to generate queries with AI Search, making it easier to retrieve relevant data without knowing the query syntax. For full documentation, see AI Search.
When to use AI Search
AI Search is useful for creating first drafts that you can then hand-tune in Query Editor.
Schema Library
Schema Library provides a tabular reference of searchable schemas, fields, and logical types in Sophos XDR. For full documentation, see Schema Library.
When to use the Schema Library
For an SQL-fluent analyst, browsing the Schema Library is similar to inspecting a table's columns.
Data Exports
Click the Actions menu above a query language results table to export the full search results or a selected subset. You can check the status of the export and download the file on the Data Exports page. For more information, see Data Exports.
Differences between SQL and query language
SQL and the Advanced Search query language may look similar because they use familiar elements, such as FROM, WHERE, and quoted strings. However, the underlying models are very different. Advanced Search query language uses a filter over a fixed, normalized event schema rather than a general-purpose relational query engine.
This table shows SQL concepts you are already familiar with and the query language equivalent.
| SQL concept | Query language equivalent | How query language is different |
|---|---|---|
SELECT col1, col2 | Not supported in any search user interface. Supported only through the API and SDK. \| fields pipe is API only. | You don't choose columns up front in the UI. Instead, results come back with a standard column set per schema. |
FROM table | FROM schema (for example, process, auth, netflow, detection, etc.) | Schemas are Sophos XDR's own normalized categories, not raw product tables. FROM is optional entirely if you're only using @logical_types. See Logical types. |
WHERE col = 'x' | WHERE field = 'x' | Follows the same format. Case insensitive by default, except for hostname. See Special case: hostname. |
LIKE '%x%' | CONTAINS 'x' | Uses a substring match. Case insensitive. |
| Glob-style wildcards | MATCHES 'glob' | Uses glob syntax (*, ?), not SQL LIKE wildcards. Case sensitive. |
| Regex functions | MATCHES_REGEX 'pattern' | Uses two different regex engines, depending on what you're querying: Java regex for events and Lucene regex for detections. Make sure you test patterns against the right engine. |
time > '2024-01-01' in WHERE | earliest= / latest= clause | Time is a first-class part of the query grammar, not a filter predicate, so there's no WHERE condition. Time format can be relative (-24h, -7d) or absolute ISO 8601. See ISO 8601. |
GROUP BY + SUM/COUNT/AVG/MIN/MAX | \| aggregate sum(field), count(field) BY field | Supported aggregates include sum, min, max, avg, count, and cardinality. BY also accepts a time bucket (for example, BY 1h) instead of a field. Aggregates apply only to event queries, not to detections or logical types. |
ORDER BY | \| sort field ASC/DESC | Single-schema queries can sort by any field. Multi-schema queries can only sort by event_time_usec or ingest_time_usec. Detections can't be sorted at all. |
LIMIT n | \| head n for first n or \| tail n for last n (order reversed first) | head returns the first N results from each event type in search order. tail returns the last N results from each event type and reverses the order of the results before returning the last N. |
Column functions (such as LOWER(), inside GROUP BY) | tolower(field) / toupper(field), but only inside a BY clause | Used specifically for case-normalizing aggregation grouping, not general-purpose string functions. |
| Arbitrary table or column names | A fixed, curated set of schemas and their fields | You're now working against Sophos's own cross-product normalization, not whatever names a raw ingestion pipeline may have used. See Schema Library. |
JOIN | No equivalent. | A multi-schema query (FROM process, auth) runs an independent subquery per schema and joins the results. It doesn't correlate rows across schemas. There's no way to join process to auth on a shared key within one query. You can only use shared field names or logical types in multi-schema WHERE clauses. |
WITH ... AS (...) (CTE) | No equivalent. | There are no subqueries or named intermediate result sets. Everything is one flat query plus pipes. |
UNION ALL SELECT <literal>, ... (an inline lookup table) | No equivalent. | There's no way to define an ad-hoc lookup table inside a query. |
CASE WHEN ... THEN ... | No equivalent. | Conditional labeling of results has to happen after the data comes back, either on the client side or by hand in the exported results. |
ARRAY_AGG(...) / GROUP_CONCAT(...) (build a delimited string list) | No equivalent. | This must be implemented by the client on the returned result from the data export or via API or SDK response. |
Logical types
Logical types are special fields that map to field names under the appropriate data schemas for a given field category, so you don't have to remember and specify each individual field name for every schema. They're denoted with the @ prefix.
The table below summarizes the field mappings of each logical type. For full field mappings, see Logical types.
| Logical Type | Summary of field mappings |
|---|---|
@command | Any field representing a process or command-line string (commandline, commandline_decoded) across apicall, auth, filemod, process, and threadinjection schemas. |
@domain | Any field representing a domain name, such as DNS query names, and auth source/target domain fields. |
@hash | Any field representing a file hash (MD5/SHA1/SHA256/SHA512) across detection, auth, filemod, and process schemas. |
@host | Any field representing a hostname or computer name across detection, auth, managementevent, and process schemas. |
@ip | Any field representing a source, destination, or general IP address across detection, auth, cloudaudit, DNS, HTTP, netflow, and NIDS schemas. |
@mac | Any field representing a source or destination MAC address across HTTP and netflow schemas. |
@path | Any field representing a file system path, such as image path, file path, script path, shortcut path, and related fields across a wide range of schemas. |
@port | Any field representing a source or destination network port across auth, HTTP, netflow, and NIDS schemas. |
@raw | The full raw log or message data for an event (original_data), available for all event types. Results are limited to the last 20 days. If your query's overall time range exceeds that window, the query is rejected outright, not silently truncated or modified. |
@url | Any field representing a URL or resource identifier (in the case of cloud audit, resources.resource_id). |
@user | Any field representing a username or user identity across detection, auth, cloudaudit, managementevent, and process schemas. |
Example queries using logical types
Cross-schema IP hunt, aggregated by destination port
FROM netflow, http WHERE @ip = '203.0.113.44' earliest=-7d | aggregate count BY destination_port
@ip searches both the netflow and http schemas at once without needing to know each schema's specific source or destination address field names, and the result is grouped by port to show where that address was communicating. Schema-agnostic hunt combining two logical types, no FROM at all
@user CONTAINS 'admin' AND @domain MATCHES '*.ru' earliest=-24h
Since both @user and @domain are logical types, the engine determines which schemas to search on its own, which is useful when you're looking for a pattern of activity rather than starting from a specific data type.
Combining two logical types within one schema, then sorting and limiting results
FROM process WHERE @hash = 'd41d8cd98f00b204e9800998ecf8427e' AND @path CONTAINS 'temp' earliest=-30d | sort event_time_usec DESC | head 25
This example finds a known file hash specifically when it was executed from a path containing "temp," sorted by most recent first, and capped at 25 results.
Special case: hostname
hostname behaves differently from all other logical types in Advanced Search query language in the following ways:
- It silently translates to the underlying
host_idfield. - It supports only literal operators, such as
=,!=,IN,!IN. It doesn't supportCONTAINS,MATCHES, orMATCHES_REGEX. - Hostname matching is case sensitive, unlike query language's usual case-insensitive default.
Authentication events from the last 24 hours with a shared hostname
FROM auth WHERE hostname = 'Demo-PC01' earliest=-24h
Scenarios and example queries
Each of the following scenarios walks you through the original SQL you might have used, how compatible it is with Advanced Search query language, the best query language equivalent, what still has to happen on the client side, and a verification checklist.
Scenario 1: Log4j process hunt
The original SQL
SELECT meta_hostname AS ep_name, name, cmdline, path, query_name, sophos_pid, pid
FROM xdr_data
WHERE query_name IN ('running_processes_linux_events', 'running_processes_osx_events', 'running_processes_windows_sophos')
AND LOWER(cmdline) LIKE '%log4j%'
Partially compatible with query language
This SQL query can be translated to Advanced Search query language, but some functionality requires modification or can't be reproduced exactly.
The filter logic in this SQL query is fully expressible because the cmdline CONTAINS 'log4j' condition maps cleanly. However, the following caveats apply:
- Caveat A: The three
query_namevalues (running_processes_linux_events,running_processes_osx_events, andrunning_processes_windows_sophos) aren't part of any documentedquery_nametoFROM <schema>mapping. Mapping all three toFROM processis a reasonable inference because they're OS-specific "list running processes" snapshot sources, but this mapping isn't confirmed. Verify it against the live Sophos XDR event schema before relying on it. - Caveat B: Query language doesn't support column selection or aliasing such as
AS ep_name. Column selection occurs on the client side after retrieving the full result set, so this is expected behavior rather than a compatibility gap.
Best query language equivalent
FROM process WHERE commandline CONTAINS 'log4j' earliest=-30d
Note the following details about this query:
CONTAINSis a case-insensitive substring match, so the originalLOWER(cmdline) LIKE '%log4j%'collapses tocommandline CONTAINS 'log4j'with noLOWER()needed.- Instead of the former
cmdlinename in SQL, the query language field namecommandlineis the canonical field name acrossprocess,auth,filemod,apicall, andthreadinjectionschemas. It's also reachable via the@commandlogical type usingcommandline. - The three
query_name IN (...)values are dropped fromWHEREand folded intoFROM process, due toquery_nameselecting the event source, not a content filter. See Caveat A for more information. - The original query has no time bound applied. In query language, there's no default time window when neither
earliestnorlatestis given — an explicit window (such asearliest=-30din the example) must be chosen deliberately, not assumed.
Client-side requirements
- Column projection and renaming (such as
meta_hostname AS ep_name,name,path,query_name,sophos_pid, andpid). This must be done after fetching results. - If the "collapse three query_names into
FROM process" inference from Caveat A turns out wrong, the OS-specific split may need to be reconstructed client-side by filtering the results on an OS or platform field.
Verify the query
Follow these steps to verify that the new query is correctly prepared:
- Confirm
running_processes_{linux,osx,windows_sophos}all correspond to Sophos XDR'sprocessevent type and verify against live schema. - Confirm
commandlineis the correct field name (notcmdline) for this event type. - Choose and apply an explicit
earliestorlatestwindow, as the original has none. - Confirm case-insensitive
CONTAINSmatches originalLOWER(...) LIKE '%...%'semantics. - Reconstruct the seven selected or aliased columns client-side from returned rows.
- Paginate through results if the result count is large.
Scenario 2: Malware/PUA score triage
Original SQL
SELECT ARRAY_JOIN(ARRAY_AGG(DISTINCT meta_hostname), CHR(10)) AS ep_list,
COUNT(DISTINCT meta_hostname) AS ep_count,
DATE_FORMAT(FROM_UNIXTIME(MIN(time)), '%Y-%m-%dT%H:%i:%SZ') AS first_seen,
DATE_FORMAT(FROM_UNIXTIME(MAX(time)), '%Y-%m-%dT%H:%i:%SZ') AS last_seen,
sha256, path,
CASE WHEN ml_score >= 30 THEN 'Malicious : ' || CAST(ml_score AS varchar)
WHEN ml_score >= 27 THEN 'High suspicion : ' || CAST(ml_score AS varchar)
WHEN ml_score >= 24 THEN 'Medium suspicion : ' || CAST(ml_score AS varchar)
WHEN ml_score >= 20 THEN 'Low suspicion : ' || CAST(ml_score AS varchar)
ELSE '' END AS ml_suspicion,
CASE WHEN pua_score > 20 THEN 'Suspect PUA : ' || CAST(pua_score AS varchar) ELSE '' END AS pua_suspicion,
local_rep, global_rep,
ARRAY_JOIN(ARRAY_AGG(DISTINCT sophos_pid), CHR(10)) AS sophos_pid_list,
ARRAY_JOIN(ARRAY_AGG(DISTINCT parent_sophos_pid), CHR(10)) AS parent_sophos_pid_list
FROM xdr_data
WHERE query_name = 'running_processes_windows_sophos' AND (ml_score >= 20 OR pua_score > 20) AND (local_rep < 91)
GROUP BY sha256, path, local_rep, global_rep, ml_score, pua_score
ORDER BY ml_score DESC, last_seen DESC
Partially compatible with query language
This SQL query can be translated to Advanced Search query language, but some functionality requires modification or can't be reproduced exactly.
The row-level WHERE filter (ml_score >= 20 OR pua_score > 20, local_rep < 91) is fully expressible, but the following caveats apply:
- Caveat A:
running_processes_windows_sophosisn't in any documented mapping. InferringFROM processfor a single-OS variant is more defensible than the three-way collapse in Scenario 1, but it's still unconfirmed. For more details about this caveat, see Scenario 1: Log4j process hunt. - Caveat B: Everything after the
WHERE—GROUP BY(bothARRAY_AGG/ARRAY_JOINhost/pid roll-ups,MIN/MAX(time),DATE_FORMAT(FROM_UNIXTIME(...)), bothCASEexpressions, andORDER BYon derived or aggregated columns) has no direct equivalent.
Best query language equivalent
FROM process WHERE (sophos_endpoint_process_info.malware_score >= 20 OR sophos_endpoint_process_info.pua_score > 20) AND sophos_endpoint_process_info.local_reputation < 91 earliest=-30d | aggregate count BY program_hash.sha256
Note the following details about this query:
>=,>,<,OR, and parentheses all map directly.| aggregate count BY program_hash.sha256gives the count-per-hash grouping and is the only piece of the originalGROUP BYthat has a direct equivalent.- The original query has no time bound applied. In query language, there's no default time window when neither
earliestnorlatestis given — an explicit window (such asearliest=-30din the example) must be chosen deliberately, not assumed.
Client-side requirements
- The host/pid roll-ups (
ARRAY_AGG(DISTINCT meta_hostname),sophos_pid,parent_sophos_pid,ARRAY_JOIN(..., CHR(10))) need per-group unique-value collection and a newline join. COUNT(DISTINCT meta_hostname)needs a client-side count of the unique-hostname set per group (which is distinct from the plain row-count the| aggregate countpipe gives).MIN(time)/MAX(time),FROM_UNIXTIME, andDATE_FORMAT(..., '%Y-%m-%dT%H:%i:%SZ')require epoch > ISO 8601 formatting on the client side. Note thatmin/maxaggregates do exist in query language, but they're not combinable withcount BYin the same aggregate pipe. Only that combination, not the aggregate's availability, is the actual gap. For more details, see Differences between SQL and query language.- No conditional-labeling pipe command exists for either
CASEexpression (ml_suspicionfour-tier label andpua_suspicionlabel). Reimplement as plainif/elifper row or group after fetch.
Verify the query
Follow these steps to verify that the new query is correctly prepared:
- Confirm
running_processes_windows_sophos>FROM processmapping against the live schema. - Choose an explicit
earliest/latestwindow. - Reconcile the informal field names used above (
ml_score,pua_score,local_rep,sha256) with the corrected, namespaced field names in the "Best QL equivalent" query (sophos_endpoint_process_info.*,program_hash.sha256). - Reimplement both
CASElabel ladders exactly (score-threshold boundaries: 20/24/27/30 for ml_suspicion, 20 for pua_suspicion) client-side. - Reimplement the multi-field
ARRAY_AGG/ARRAY_JOINroll-ups client-side, preservingDISTINCT. - Confirm whether the final sort (
ml_scoreDESC,last_seenDESC) can be pushed server-side via a second| sortpipe on the derived count column, rather than assuming client-side sort is required.
Scenario 3: CALDERA/MITRE lookup table hunt
Original SQL
-- Hunt for CALDERA IOCS
-- VARIABLE $$Any Field String Match$$ / $$Device Name$$ / $$MITRE ATT&CK ID$$ / $$Tactic$$ / $$Technique$$ STRING
WITH Detection_Counts AS (
WITH Detections AS (
WITH Mitre_map (ID, Tactic, Technique, SubTechnique, Mitre_Link, Method, Condition, SubCondition, Refrence) AS (
SELECT 'T1002','exfiltration','Compress staged directory','Data Compressed',
'https://attack.mitre.org/techniques/T1002','Process_Cmd','%powershell%','%Compress-Archive%',
'Compress a directory on the file system'
UNION ALL
SELECT 'T1003','Process Memory-access','Leverage Procdump','Memory Dump',
'https://attack.mitre.org/techniques/T1003','Process_Cmd','%powershell%','%procdump%',
'Dump a processes memory'
-- ... ~150 total UNION ALL SELECT rows mapping MITRE technique IDs to
-- Process_Cmd LIKE-pattern conditions (name/cmdline patterns), omitted here for brevity ...
)
SELECT xdr_data.meta_hostname Device_Name, xdr_data.meta_os_type Device_Type, xdr_data.unix_time Date_Time,
map.ID Mitre_ID, map.Tactic, map.Technique, map.SubTechnique,
map.condition || ' + ' || map.subcondition Hunt_Rule, map.Mitre_Link, map.Refrence,
xdr_data.username User_Name, xdr_data.name Process_Name, xdr_data.cmdline Cmd_Line,
xdr_data.sophos_PID SophosPID, xdr_data.path Path_Name
FROM xdr_data
JOIN Mitre_map map
ON LOWER(xdr_data.name) LIKE LOWER(map.condition) AND LOWER(xdr_data.cmdline) LIKE LOWER(map.SubCondition)
WHERE LOWER(xdr_data.meta_hostname) LIKE LOWER('%$$Device Name$$%')
AND LOWER(map.Tactic) LIKE LOWER('%$$Tactic$$%')
AND LOWER(map.Technique) LIKE LOWER('%$$Technique$$%')
AND LOWER(map.ID) LIKE LOWER('%$$MITRE ATT&CK ID$$%')
AND map.Method = 'Process_Cmd'
)
SELECT Mitre_ID, Tactic, Technique, COUNT(Device_Name) Instances, Process_Name, Cmd_Line,
array_join(array_agg(DISTINCT User_Name), CHR(10)) User_List, Hunt_Rule,
array_join(array_agg(DISTINCT Device_Name), CHR(10)) Device_LIST,
array_join(array_agg(DISTINCT Device_Type), CHR(10)) Device_Type,
MIN(Date_Time) First_Seen, MAX(Date_Time) LAST_Seen, SubTechnique,
array_join(array_agg(DISTINCT SophosPID), CHR(10)) SophosPID_LIST,
array_join(array_agg(DISTINCT Path_Name), CHR(10)) Path_Name_LIST, MITRE_Link, Refrence
FROM Detections
GROUP BY Mitre_ID, Tactic, Technique, Process_Name, Cmd_Line, Hunt_Rule, SubTechnique, User_Name, MITRE_Link, Refrence
)
SELECT * FROM Detection_Counts WHERE LOWER(...) LIKE '%$$Any Field String Match$$%' ORDER BY Instances DESC
Not compatible with query language
The original SQL doesn't reduce to Advanced Search query language in any useful sense for the following reasons:
- No safe
FROMinference exists. Unlike Scenarios 1 and 2, this query doesn't restrictxdr_databy anyquery_nameat all — it implicitly assumes a process-execution-shaped row (it hasname,cmdline,sophos_PID,path,username) but gives no basis to pick a single Sophos XDR event type. GuessingFROM processhere would be much less certain than in Scenarios 1 and 2, since the original author deliberately left it unscoped. - The ~150-row literal
Mitre_maplookup table has no representation. Query language is a live-query filter language against event data. It has no construct for an inline literal-value virtual table (noUNION ALLof literal rows, noWITH). - The
JOIN ... ON LOWER(name) LIKE LOWER(map.condition) AND LOWER(cmdline) LIKE LOWER(map.subcondition)pattern-match join has no equivalent. In principle, since query language supportsAND,OR, and parentheses, one could try to flatten all ~150 lookup rows into one giant "OR-of-AND" pairs boolean expression ((name CONTAINS 'x1' AND commandline CONTAINS 'y1') OR (name CONTAINS 'x2' AND commandline CONTAINS 'y2') OR ...). This isn't recommended and not attempted here. There's no documented limit onWHEREclause size or complexity, so whether the platform would even accept a 150-clause expression is unverified, and it would hardcode the entire MITRE lookup table into the query string rather than treating it as data, which would be brittle and against the spirit of the migration. The recommended approach is the hybrid one: a broad fetch plus client-side matching. - All of
GROUP BY,COUNT, bothARRAY_AGG/array_joinroll-ups,MIN/MAX(Date_Time), and the finalORDER BY Instances DESCare not expressible. See Scenario 2: Malware/PUA score triage for more details. - The
$$Tactic$$/$$Technique$$/$$MITRE ATT&CK ID$$bind variables filter the lookup table, not event fields. They can never become query languageWHEREclauses. Instead, they filter the client-side lookup structure before or after the match step.
Best query language equivalent
FROM process earliest=-7d
This is a broad, best-guess, time-bounded fetch of process events. The FROM process choice itself is an unconfirmed inference. Nothing more from the original SQL is expressible.
Client-side requirements
- Load the ~150-row
Mitre_maptable as a plain data structure (a list of records, or a small CSV or JSON file shipped alongside the query) instead of a SQL literal table. - Translate each row's SQL
LIKEpattern (%powershell%,%procdump%, etc.) into a substring/glob/regex check, and for each fetched event, checkname/cmdlineagainstcondition/subconditionfor every lookup row. This reimplements theJOIN ... ON LIKEas a nested loop, or as a vectorized merge if performance matters. - Reimplement
Device Name/Tactic/Technique/MITRE ATT&CK IDbind-variable filtering against the lookup table and fetched device metadata on the client side. - Reimplement
GROUP BY,COUNT, allARRAY_AGG/array_joinroll-ups (users, devices, device types, PIDs, paths), andMIN/MAX(Date_Time)on the client side. - Reimplement the final
Any Field String Matchfree-text filter andORDER BY Instances DESCon the client-side aggregated result.
Verify the query
Follow these steps to verify that the new query is correctly prepared:
- Determine the correct Sophos XDR event type for unscoped process-execution rows before doing anything else. There's no safe inference available here, unlike Scenarios 1 and 2.
- Port the full ~150-row MITRE technique lookup table faithfully into a client-side data structure. Verify that none of the 150 rows is lost or altered in translation.
- Convert every SQL
LIKEpattern in the lookup table to correct matching semantics (convert%to substring wildcard). - Confirm performance of a client-side nested match (rows x 150 patterns) is acceptable, or vectorize.
- Reimplement all aggregation and roll-up columns, and confirm that the output shape matches what downstream consumers of this hunt query expect.
- Confirm with a security or detection engineering stakeholder whether this hunt logic should instead become a native Sophos XDR detector or alert rule, rather than an ad-hoc query at all.
Verification checklist
As the person hand-writing and running a query, walk through this checklist before you trust the results:
- Schemas are chosen correctly. You're querying the schema(s) that actually contain the fields you need, documented in the Schema Library.
- Time window is respected. Event queries are ≤ 31 days. If your old query spanned longer, split it into multiple runs.
LIKEtoCONTAINS/MATCHES/MATCHES_REGEXis chosen deliberately. These functions aren't interchangeable.CONTAINSis a substring,MATCHESis a case-sensitive glob, andMATCHES_REGEXis regex. The dialect differs between event and detection.- No join is assumed. If your old query correlated two tables, confirm you actually need a union of independent results (which is acceptable), as opposed to a true correlation (which isn't possible as it needs client-side joining after fetching both schemas separately).
- Aggregation is checked against the real pipe syntax (
| aggregate ... BY ...). Don't assume aggregation must be done on the client-side. Refer to Differences between SQL and query language. - String-list building and CASE labeling are routed on the client side. These functions have no query language equivalent.
hostnameis treated as a special case.hostnameis the one field that's case sensitive and only supports=/!=/IN/!IN. See Special case: hostname.- Reserved words avoided as bare field or value names. Quote reserved words as string literals if needed.
- Detections and events not mixed into one query. Always run detections and events into separate queries.
Common pitfalls
Avoid the following pitfalls and misconceptions:
- Query language isn't SQL. Don't assume operator or function names map directly just because the surrounding syntax looks similar.
hostnameis a special case. It silently translates to the underlyinghost_idfield, is case sensitive, and only supports literal operators (noCONTAINS/MATCHES).- IPv6 literal comparisons are string comparisons. For example,
2001:db8::1and its fully-expanded form won't match each other with=/CONTAINS/MATCHES. Use CIDR notation (such as/128for a single address) to match reliably across representations. - Regex dialect depends on what you're querying. The system uses Java regex for events, Lucene regex for detections. A pattern that works on one may not work on the other.
@rawandoriginal_dataare limited to the last 20 days. If your overall query's time range exceeds that window, the query is rejected outright, not silently modified or truncated.- Query length limits are rare but exist. The system automatically expands logical types and special characters. A short-looking query can still hit a length limit at execution, though this is uncommon.
- Aggregation is event-only. Aggregation isn't supported on detection queries or logical types (
@user, etc.). It only applies to concrete schema fields. - The UI caps displayed results at 10,000 rows. This is separate from the 100,000-row-per-event-type export cap referenced in Navigating Data Lake Search. If you need to see more than 10,000 rows directly in the results table, narrow your query or use export.
- The source data you're migrating from may itself be mislabeled. Don't assume every query you're reorienting is the SQL dialect its label claims.
- Field names differ. Sophos Central and Sophos XDR use different field naming conventions. Always cross-reference the CQL logical types and event schemas.
- Severity scales differ. Sophos Central detections use a 1-10 integer scale. Sophos XDR alerts use a 0.0-1.0 float plus an enum (CRITICAL/HIGH/MEDIUM/LOW/INFO).
Additional examples for reference
Below are some concrete query language examples for quick reference.
Basic query language syntax
FROM process EARLIEST=-24h
FROM process WHERE image_path CONTAINS 'powershell.exe' EARLIEST=-24h
FROM process, auth EARLIEST=-12h
An SQL query you would have used before
SELECT * FROM xdr_data
WHERE query_name = 'sophos_process_activity'
AND name LIKE '%powershell%'
AND meta_hostname = 'WORKSTATION01'
AND time > '2024-01-01T00:00:00.000Z'
Auth/environment discovery pattern (Python), for anyone automating rather than using the UI
# KEEP: existing OAuth token acquisition
token = get_sophos_token(client_id, client_secret)
# NEW: discover Taegis environment
whoami = requests.get(
"https://api.central.sophos.com/whoami/v1",
headers={"Authorization": f"Bearer {token}"},
)
taegis_url = whoami.json()["apiHosts"]["taegis"]
# NEW: initialize SDK with discovered URL and existing token
from taegis_sdk_python import GraphQLService
service = GraphQLService()
with service(url=taegis_url, access_token=token):
# ... Taegis SDK calls here
pass