Homeโ€บ๐Ÿ” Phase 1: Learn DQLโ€บModule 43 min read ยท 5/21

DQL Crash Course

Hands-on

DQL โ€” The One Language to Learn

DQL (Dynatrace Query Language) is the single replacement for metric expressions, USQL, entity selectors, and the Data Explorer. Learning DQL is the prerequisite for every migration step that follows.

๐Ÿ›  Try it now: Open your Dynatrace environment โ†’ press Ctrl+K โ†’ type "Notebooks" โ†’ open a new Notebook. This is where you'll write DQL.

The Pipeline Pattern

Every DQL query follows the same pattern โ€” a pipeline of commands connected with | (pipe):

fetch โ†’ filter โ†’ fields โ†’ summarize โ†’ sort โ†’ limit

Two Starting Commands

Every query starts with one of two commands:

fetch      โ†’ rows of data (logs, events, entities, spans, problems)
timeseries โ†’ metrics over time (for charts)

Your First Queries

Open a Notebook and try these one by one:

1. See your hosts

fetch dt.entity.host
| fields entity.name, osType, cpuCores
| limit 10

๐Ÿ›  Try it: Paste this into a Notebook DQL section. You should see your hosts with their OS and CPU count.

2. Check your logs

fetch logs, from:now() - 1h
| filter loglevel == "ERROR"
| fields timestamp, content, dt.process.name
| sort timestamp desc
| limit 20

3. See CPU over time (chart)

timeseries avg(dt.host.cpu.usage), by:{dt.entity.host}

๐Ÿ’ก In Notebooks, timeseries queries automatically render as line charts. fetch queries render as tables. You can change the visualization type in the UI.

4. Count problems by category

fetch dt.davis.problems, from:now() - 7d
| filter not(dt.davis.is_duplicate) and event.status == "ACTIVE"
| summarize problem_count = count(), by:{event.category}
| sort problem_count desc

Common Gotchas

โš ๏ธ These will bite you. Memorize them:

WRONG                                   RIGHT
โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€  โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
filter log.level == "ERROR"             filter loglevel == "ERROR"
  (no dot โ€” it's loglevel)               โœ“

sort count() desc                       summarize cnt=count() | sort cnt desc
  (must alias aggregations)              โœ“

fields name, id | sort name             sort name | fields name, id
  (sort BEFORE fields!)                  โœ“

filter status in ["A","B"]              filter in(status, "A", "B")
  (no bracket syntax)                    โœ“

fetch metrics                           timeseries avg(dt.host.cpu.usage)
  (no "fetch metrics")                   โœ“

What You Can Query

Data Object              What's In It                    Example Query Start
โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€  โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€  โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
dt.entity.host           Hosts                           fetch dt.entity.host
dt.entity.service        Services                        fetch dt.entity.service
logs                     Application & system logs       fetch logs
events                   Davis events                    fetch events
dt.davis.problems        Davis problems                  fetch dt.davis.problems
spans                    Distributed traces              fetch spans
bizevents                Business events                 fetch bizevents
dt.system.events         Billing & usage                 fetch dt.system.events
user.sessions            RUM sessions                    fetch user.sessions
user.events              RUM events                      fetch user.events

๐Ÿ’ก Discovery trick: Run describe dt.entity.host in a Notebook to see ALL available fields for any data object. This is how you explore the schema.

๐Ÿ’ก Davis CoPilot: In a Notebook, add a Prompt section and ask in plain English: "Show me error logs from the last hour grouped by service". CoPilot generates and runs the DQL for you โ€” great for learning the syntax.

Advanced Commands (Power User)

Command          What It Does                              Example
โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€  โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€  โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
lookup           Enrich data from another table             lookup [load dt.entity.host], sourceField:id, lookupField:id
join             Correlate two data sources                 join [fetch logs], on:{dt.entity.host}
append           Combine results from two queries           append [fetch dt.entity.service]
makeTimeseries   Create time chart from non-metric data     makeTimeseries count(), by:{loglevel}, interval:15m
expand           Flatten arrays into rows                   expand tags

Lookup: Enrich with Context

// Add host names to log records
fetch logs, from:now() - 1h
| lookup [fetch dt.entity.host | fields id, entity.name], sourceField:dt.entity.host, lookupField:id, prefix:"host."
| fields timestamp, host.entity.name, content

๐Ÿ’ก Lookup tables: Dynatrace now supports uploading CSV files as lookup tables in Grail. Use load to query them โ€” perfect for enriching data with business context (cost centers, team owners, SLA tiers).

makeTimeseries: Charts from Any Data

// Error rate chart from logs (not metrics!)
fetch logs, from:now() - 2h
| makeTimeseries count(), by:{loglevel}, interval:15m

โš ๏ธ makeTimeseries works on fetched data (logs, events, spans). timeseries works on metrics. Don't confuse them โ€” timeseries is a starting command, makeTimeseries is a pipe command.

โš ๏ธ Named parameters required โ€” many DQL functions need named params, not positional:

WRONG: substring(content, 0, 100)     RIGHT: substring(content, from:0, to:100)
WRONG: if(x > 10, "high", "low")      RIGHT: if(x > 10, then:"high", else:"low")

๐Ÿ’ก HTTP error rates: status == "ERROR" is unreliable for HTTP services. Use countIf(http.response.status_code >= 500) instead.