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 or programmatically through the SDK 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.

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.

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)
    
  2. 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
    
  3. 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
  • Check the component name. Component names are case-sensitive and must match exactly. Run the following to see all available component names:

    SELECT DISTINCT component_name FROM readings
    WHERE time_received >= CAST('2000-01-01T00:00:00.000Z' AS TIMESTAMP)
    
  • Check the time range. If you narrowed the time range, widen it back to the broad lower bound time_received >= CAST('2000-01-01T00:00:00.000Z' AS TIMESTAMP) to confirm data exists, then tighten the range back down. Do not remove the time_received filter entirely: under APP-10891, SQL queries without an explicit lower bound on time_received return no rows.

  • Verify data has synced. Data must sync from the machine to the cloud before it is queryable. Check the DATA tab to confirm entries are visible.

Query returns data but fields are null
  • Check the JSON path. The nested path must match the actual structure of your data. Run the following to see the raw JSON, then build your dot-notation path to match. A common mistake is using data.temperature when the actual path is data.readings.temperature.

    SELECT data FROM readings
    WHERE time_received >= CAST('2000-01-01T00:00:00.000Z' AS TIMESTAMP)
    LIMIT 1
    
MQL pipeline returns unexpected results
  • Build incrementally. Start with just the $match stage and verify it returns the documents you expect. Then add one stage at a time. This makes it easy to identify which stage is producing unexpected output.
  • Check field references. In MQL, field references use $ prefix (for example, $component_name, $data.readings.temperature). Missing the $ is a common source of errors.
Query is slow
  • Add filters early. Always include a $match stage (MQL) or WHERE clause (SQL) to narrow the data before doing expensive operations like grouping or sorting. Filtering by component_name and time_received is particularly effective.
  • Use LIMIT. While developing queries, always include a LIMIT clause (SQL) or $limit stage (MQL) to avoid scanning your entire dataset.

What’s next