Back to Blog
5 min read

Mastering IoT Hub Queries for Device Management

IoT Hub provides a powerful SQL-like query language for querying device twins, jobs, and message routing. Mastering these queries is essential for effective device fleet management.

Query Basics

IoT Hub queries support:

  • Device twin queries
  • Job queries
  • Module twin queries

Device Twin Queries

Basic Queries

-- Get all devices
SELECT * FROM devices

-- Get specific device
SELECT * FROM devices WHERE deviceId = 'device001'

-- Get devices by tag
SELECT * FROM devices WHERE tags.location = 'building1'

-- Get devices by reported property
SELECT * FROM devices WHERE properties.reported.firmwareVersion = '1.2.3'

-- Get devices by desired property
SELECT * FROM devices WHERE properties.desired.telemetryInterval = 60

Advanced Filtering

-- Multiple conditions
SELECT * FROM devices
WHERE tags.location = 'building1'
  AND properties.reported.status = 'online'
  AND properties.reported.batteryLevel > 20

-- Using OR conditions
SELECT * FROM devices
WHERE tags.deviceType = 'sensor'
  OR tags.deviceType = 'gateway'

-- Checking for property existence
SELECT * FROM devices
WHERE IS_DEFINED(tags.location)

-- Null checks
SELECT * FROM devices
WHERE tags.owner != null

-- String operations
SELECT * FROM devices
WHERE STARTSWITH(deviceId, 'prod-')

SELECT * FROM devices
WHERE ENDSWITH(tags.location, '-floor1')

SELECT * FROM devices
WHERE CONTAINS(tags.description, 'temperature')

Projection and Aggregation

-- Select specific fields
SELECT deviceId, tags.location, properties.reported.status
FROM devices
WHERE tags.deviceType = 'sensor'

-- Count devices
SELECT COUNT() AS totalDevices FROM devices

-- Count by tag
SELECT tags.location, COUNT() AS deviceCount
FROM devices
GROUP BY tags.location

-- Get top N results
SELECT TOP 10 * FROM devices
WHERE properties.reported.batteryLevel < 30
ORDER BY properties.reported.batteryLevel ASC

Running Queries with Python

from azure.iot.hub import IoTHubRegistryManager
from azure.iot.hub.models import QuerySpecification

class DeviceQueryManager:
    def __init__(self, connection_string):
        self.registry = IoTHubRegistryManager.from_connection_string(connection_string)

    def query_devices(self, query_string):
        """Execute a device twin query"""
        query_spec = QuerySpecification(query=query_string)

        # Get query result with pagination
        query_result = self.registry.query_iot_hub(query_spec)

        devices = []
        continuation_token = None

        while True:
            # Get next page of results
            response = self.registry.query_iot_hub(
                query_spec,
                continuation_token=continuation_token,
                max_items_per_page=100
            )

            devices.extend(response.items)

            if not response.continuation_token:
                break

            continuation_token = response.continuation_token

        return devices

    def get_devices_by_location(self, location):
        """Get all devices in a specific location"""
        query = f"SELECT * FROM devices WHERE tags.location = '{location}'"
        return self.query_devices(query)

    def get_offline_devices(self):
        """Get devices that haven't reported recently"""
        query = """
            SELECT deviceId, properties.reported.lastHeartbeat
            FROM devices
            WHERE properties.reported.status = 'offline'
               OR NOT IS_DEFINED(properties.reported.lastHeartbeat)
        """
        return self.query_devices(query)

    def get_devices_needing_update(self, target_version):
        """Get devices with outdated firmware"""
        query = f"""
            SELECT deviceId, properties.reported.firmwareVersion
            FROM devices
            WHERE properties.reported.firmwareVersion != '{target_version}'
              AND tags.autoUpdate = true
        """
        return self.query_devices(query)

    def get_device_statistics(self):
        """Get device statistics by type"""
        query = """
            SELECT tags.deviceType, COUNT() as count
            FROM devices
            GROUP BY tags.deviceType
        """
        return self.query_devices(query)

# Usage
manager = DeviceQueryManager(IOT_HUB_CONNECTION_STRING)

# Find all sensors in building 1
sensors = manager.get_devices_by_location("building1")
print(f"Found {len(sensors)} devices in building1")

# Find devices needing firmware update
outdated = manager.get_devices_needing_update("2.0.0")
for device in outdated:
    print(f"{device['deviceId']}: {device.get('properties', {}).get('reported', {}).get('firmwareVersion')}")

Job Queries

-- Get all running jobs
SELECT * FROM devices.jobs
WHERE devices.jobs.status = 'running'

-- Get failed jobs
SELECT * FROM devices.jobs
WHERE devices.jobs.status = 'failed'

-- Get jobs for specific device
SELECT * FROM devices.jobs
WHERE devices.jobs.deviceId = 'device001'

-- Get jobs by type
SELECT * FROM devices.jobs
WHERE devices.jobs.type = 'firmwareUpdate'

Module Twin Queries

-- Get all modules
SELECT * FROM devices.modules

-- Get modules by device
SELECT * FROM devices.modules
WHERE deviceId = 'gateway001'

-- Get specific module type
SELECT * FROM devices.modules
WHERE properties.reported.moduleType = 'temperatureSensor'

Query Performance Tips

def efficient_query_patterns():
    """Examples of efficient query patterns"""

    # Good: Use specific filters
    good_query = """
        SELECT deviceId, properties.reported.status
        FROM devices
        WHERE tags.location = 'building1'
          AND tags.floor = 'floor2'
    """

    # Avoid: Selecting all fields when not needed
    # SELECT * FROM devices

    # Good: Use TOP for limited results
    good_query_top = """
        SELECT TOP 100 deviceId, properties.reported.batteryLevel
        FROM devices
        WHERE properties.reported.batteryLevel < 30
        ORDER BY properties.reported.batteryLevel ASC
    """

    # Good: Use pagination for large result sets
    def paginated_query(registry, query, page_size=100):
        continuation = None
        while True:
            result = registry.query_iot_hub(
                QuerySpecification(query=query),
                continuation_token=continuation,
                max_items_per_page=page_size
            )
            yield from result.items
            if not result.continuation_token:
                break
            continuation = result.continuation_token

Real-World Query Scenarios

class FleetManager:
    def __init__(self, registry):
        self.registry = registry

    def find_unhealthy_devices(self):
        """Find devices with issues"""
        query = """
            SELECT deviceId, tags.location,
                   properties.reported.status,
                   properties.reported.errorCount,
                   properties.reported.lastError
            FROM devices
            WHERE properties.reported.errorCount > 0
               OR properties.reported.status = 'error'
        """
        return self.query(query)

    def find_devices_for_maintenance(self, days_threshold=30):
        """Find devices due for maintenance"""
        query = f"""
            SELECT deviceId, tags.location,
                   properties.reported.lastMaintenanceDate
            FROM devices
            WHERE tags.requiresMaintenance = true
        """
        return self.query(query)

    def get_fleet_summary(self):
        """Get summary of device fleet"""
        queries = {
            'total': "SELECT COUNT() FROM devices",
            'online': "SELECT COUNT() FROM devices WHERE properties.reported.status = 'online'",
            'offline': "SELECT COUNT() FROM devices WHERE properties.reported.status = 'offline'",
            'by_type': "SELECT tags.deviceType, COUNT() FROM devices GROUP BY tags.deviceType"
        }
        return {name: self.query(q) for name, q in queries.items()}

Effective use of IoT Hub queries enables proactive device fleet management at scale.

Michael John Peña

Michael John Peña

Senior Data Engineer based in Sydney. Writing about data, cloud, and technology.