Query data in the Viam app | Viam Documentation

Query data in the Viam app

Explore and analyze your captured data using SQL or MQL queries. You can run queries interactively in the Viam app’s query editor, from the command line with the Viam CLI, or programmatically through the SDK. Use CLI or SDK queries for ad-hoc analysis, building dashboards, or integrating with your own tools.

Tabular data (sensor readings, motor positions, encoder ticks, and other structured key-value data) is queryable through SQL and MQL. Binary data (images, point clouds) is stored separately and accessible through the data client API.

Known issue: SQL queries need an explicit lower time bound

SQL queries against readings currently return no rows unless the WHERE clause includes an explicit lower bound on time_received. Every SQL example and troubleshooting step on this page (including short inline examples) includes an explicit lower bound on time_received for this reason. When troubleshooting, do not remove the lower-bound filter entirely: widen it by using a broad lower bound such as AND time_received >= CAST('2000-01-01T00:00:00.000Z' AS TIMESTAMP). MQL queries are not affected. Tracked as APP-10891.

Note

Tabular data queries (TabularDataByMQL and TabularDataBySQL) share a 100 TB monthly processing limit across your organization. Queries that exceed the limit return an error. Contact us to request an increase.

Open the query editor

  1. Go to app.viam.com.
  2. Click the DATA tab in the top navigation.
  3. Click Query to open the query editor.
  4. Select SQL or MQL mode depending on which language you want to use.

SQL is good for straightforward filtering, sorting, and limiting.
MQL (MongoDB Query Language) uses aggregation pipelines and is more powerful for grouping, computing averages, and restructuring nested data.

By default, queries run against the readings collection in the sensorData database.
See Query reference for the full schema.

Explore your data with basic SQL

Start with a broad query to see what data you have:

SELECT time_received, component_name, component_type, data
FROM readings
WHERE time_received >= CAST('2000-01-01T00:00:00.000Z' AS TIMESTAMP)
ORDER BY time_received DESC
LIMIT 10

This shows the 10 most recent readings across all components.
Most of the interesting values are in the data column, which contains your actual readings as nested JSON.

To see the structure of your data, run this query for a specific component:

SELECT data FROM readings
WHERE component_name = 'my-sensor'
  AND time_received >= CAST('2000-01-01T00:00:00.000Z' AS TIMESTAMP)
LIMIT 1

Switch to table view (the table icon in the results area) to see nested fields automatically flattened into dot-notation column headers like data.readings.temperature. These dot-notation paths are exactly what you use in your queries to extract specific values.

For the full schema and per-component examples, see the readings table schema.

To narrow to a specific component:

SELECT time_received, data
FROM readings
WHERE component_name = 'my-sensor'
  AND time_received >= CAST('2000-01-01T00:00:00.000Z' AS TIMESTAMP)
ORDER BY time_received DESC
LIMIT 10

To filter by time range:

SELECT time_received, component_name, data
FROM readings
WHERE time_received > '2025-01-15T00:00:00Z'
  AND time_received < '2025-01-16T00:00:00Z'
ORDER BY time_received ASC

Extract fields from nested JSON

The data column contains JSON, so you need JSON functions to extract specific values. Use dot notation to reach into the nested structure:

SELECT
  time_received,
  data.readings.temperature AS temperature,
  data.readings.humidity AS humidity
FROM readings
WHERE component_name = 'my-sensor'
  AND time_received >= CAST('2000-01-01T00:00:00.000Z' AS TIMESTAMP)
ORDER BY time_received DESC
LIMIT 20

For detection results from a vision service:

SELECT
  time_received,
  data.detections
FROM readings
WHERE component_name = 'my-detector'
  AND component_type = 'rdk:service:vision'
  AND time_received >= CAST('2000-01-01T00:00:00.000Z' AS TIMESTAMP)
ORDER BY time_received DESC
LIMIT 10

To filter by a specific machine:

SELECT time_received, component_name, data
FROM readings
WHERE robot_id = 'YOUR-MACHINE-ID'
  AND time_received >= CAST('2000-01-01T00:00:00.000Z' AS TIMESTAMP)
ORDER BY time_received DESC
LIMIT 10

Write MQL aggregation pipelines

Switch to MQL mode in the query editor. MQL queries are JSON arrays where each element is a pipeline stage.

Get the last 10 readings from a component:

[\
  { "$match": { "component_name": "my-sensor" } },\
  { "$sort": { "time_received": -1 } },\
  { "$limit": 10 },\
  {\
    "$project": {\
      "time_received": 1,\
      "data": 1,\
      "_id": 0\
    }\
  }\
]

Count readings per component:

[\
  {\
    "$group": {\
      "_id": "$component_name",\
      "count": { "$sum": 1 }\
    }\
  },\
  { "$sort": { "count": -1 } }\
]

Count readings per component over a specific time window:

[\
  {\
    "$match": {\
      "time_received": {\
        "$gte": { "$date": "2025-01-15T00:00:00Z" },\
        "$lt": { "$date": "2025-01-16T00:00:00Z" }\
      }\
    }\
  },\
  {\
    "$group": {\
      "_id": "$component_name",\
      "count": { "$sum": 1 }\
    }\
  },\
  { "$sort": { "count": -1 } }\
]

Compute average, min, and max of a sensor value:

[\
  { "$match": { "component_name": "my-sensor" } },\
  {\
    "$group": {\
      "_id": null,\
      "avg_temperature": { "$avg": "$data.readings.temperature" },\
      "min_temperature": { "$min": "$data.readings.temperature" },\
      "max_temperature": { "$max": "$data.readings.temperature" },\
      "total_readings": { "$sum": 1 }\
    }\
  }\
]

Group readings by hour to see trends over time:

[\
  { "$match": { "component_name": "my-sensor" } },\
  {\
    "$group": {\
      "_id": {\
        "$dateToString": {\
          "format": "%Y-%m-%d %H:00",\
          "date": "$time_received"\
        }\
      },\
      "avg_temperature": { "$avg": "$data.readings.temperature" },\
      "count": { "$sum": 1 }\
    }\
  },\
  { "$sort": { "_id": 1 } }\
]

Find all detections above a confidence threshold:

[\
  { "$match": { "component_name": "my-detector" } },\
  { "$unwind": "$data.detections" },\
  { "$match": { "data.detections.confidence": { "$gte": 0.9 } } },\
  {\
    "$project": {\
      "time_received": 1,\
      "class_name": "$data.detections.class_name",\
      "confidence": "$data.detections.confidence",\
      "_id": 0\
    }\
  },\
  { "$sort": { "time_received": -1 } },\
  { "$limit": 20 }\
]

The $unwind stage is important when your data contains arrays. It flattens the array so each element becomes its own document, which you can then filter and project individually.

Save and reuse queries

You can save MQL queries for reuse. To save a query, switch to MQL mode, click Save query, and give it a name. It appears under the Saved tab in the editor sidebar, next to a Recent tab of your recent queries. Saved queries are available to your whole organization. You can also run a saved query from code by passing its name to TabularDataByMQL. Saving is not available for SQL queries.

Get help writing queries with the Query Assistant

The Query Assistant (Beta) is a chat panel that turns a plain-language description into a query. It works in both SQL and MQL: click Assistant, describe the data you want, and click Insert to add the suggested query to the editor. It helps whenever you are not sure how to express what you want, from a simple filter to a multi-stage MQL aggregation pipeline.

Query from code

You can run the same SQL and MQL queries from Python or Go using the data client API. See Query data from code for setup instructions and examples.

Try it

To get oriented with your own data:

  1. Open the query editor and run the following to see what components have captured data:
   SELECT DISTINCT component_name FROM readings
   WHERE time_received >= CAST('2000-01-01T00:00:00.000Z' AS TIMESTAMP)
  1. Pick a component and run the following to see the JSON structure of its readings:
   SELECT data FROM readings
   WHERE component_name = 'YOUR-COMPONENT'
        AND time_received >= CAST('2000-01-01T00:00:00.000Z' AS TIMESTAMP)
   LIMIT 1
  1. Use the field names from step 2 to write a query that extracts a specific value with dot notation (for example, data.readings.temperature).

For the full schema of the readings table, see Query reference.

Troubleshooting

Query returns empty results
   SELECT DISTINCT component_name FROM readings
   WHERE time_received >= CAST('2000-01-01T00:00:00.000Z' AS TIMESTAMP)
Query returns data but fields are null
   SELECT data FROM readings
   WHERE time_received >= CAST('2000-01-01T00:00:00.000Z' AS TIMESTAMP)
   LIMIT 1
MQL pipeline returns unexpected results
Query is slow

What’s next