In the previous post we finished up the warm path by looking at KQL and how to query IoT telemetry in Azure Data Explorer. Now it’s time to move to the hot path.
Where the warm path is about monitoring and trend analysis, the hot path is about speed and immediate action. The moment a critical condition is detected, something should happen. This post covers Azure Stream Analytics, the service that does that detection.
What the hot path is for #
The warm path answers questions like “what has the temperature been doing over the last 30 minutes?” That’s useful, but it’s not the right tool when a machine is overheating right now and someone needs to know about it.
The hot path is designed for exactly that. You can’t sit in front of a dashboard all day waiting for a value to cross a threshold. The hot path monitors the stream continuously and fires an action automatically the moment a condition is met.
For this series, the scenario is straightforward: a temperature sensor exceeds a threshold, an email alert goes out automatically. Email is a practical choice for a course demo because everyone has an account and nothing extra needs to be set up. In a real-world scenario you would more likely push a notification to a phone or send a message to a team chat.
Three Azure services work together to make this happen:
- Azure Stream Analytics processes the incoming data stream and evaluates the query condition
- Azure Event Hubs bridges Stream Analytics and Logic Apps, because Stream Analytics cannot trigger a Logic App directly
- Azure Logic Apps picks up the event and sends the actual alert
Event Hubs and Logic Apps are outside the main scope of this post. The focus here is Stream Analytics, which is where the real-time processing happens.
What is Azure Stream Analytics? #
Azure Stream Analytics is a fully managed PaaS service for real-time data processing. It’s built for high-volume, fast-moving data, the kind that IoT solutions generate constantly, and can handle millions of events per second.
The core building block is a Stream Analytics job. Every job has three parts: an input, a query, and an output.
Inputs are the data sources the job listens to. For IoT scenarios, IoT Hub is the natural choice. Event Hubs and Azure Blob Storage are also supported.
The query is where the logic lives. It uses SQL syntax: SELECT, FROM, WHERE, GROUP BY. If you’ve written SQL before, the structure will feel familiar. The difference is that you’re querying a live stream of events rather than a static table.
Outputs are where the results go. Stream Analytics supports a wide range of destinations: Azure Data Explorer, Azure Storage, Azure SQL, Azure Cosmos DB, Event Hubs, Service Bus, Azure Functions, and more. A single job can write to multiple outputs at the same time.
The Stream Analytics query language #
The examples below use the same telemetry data as the rest of the series.
Basic selection #
The simplest possible query just passes everything through:
SELECT
*
INTO
[alertOutput]
FROM
[telemetry][telemetry] is the name of the input (the IoT Hub connection). [alertOutput] is the name of the output. This selects all incoming fields and forwards them as-is.
More useful is selecting specific fields. Device ID and enqueue time are stored on the IoT Hub metadata object attached to each message, not directly in the message body:
SELECT
deviceId = telemetry.iothub.connectionDeviceId,
temperature,
humidity,
enqueuedTime = telemetry.iothub.enqueuedTime
INTO
[alertOutput]
FROM
[telemetry]Threshold detection #
To detect when a temperature reading crosses a threshold, add a WHERE clause:
SELECT
deviceId = telemetry.iothub.connectionDeviceId,
temperature,
humidity,
enqueuedTime = telemetry.iothub.enqueuedTime
INTO
[alertOutput]
FROM
[telemetry]
WHERE temperature > 30Now only messages where the temperature exceeds 30°C pass through to the output. Everything else is discarded.
Avoiding alert spam #
There’s a problem with the simple WHERE clause approach. If a temperature sensor stays above 30°C for several minutes and sends readings every few seconds, every single one of those messages passes through to the output. In our architecture, that output is wired to Logic Apps, which sends an email for each message it receives. The recipient ends up with a flood of notifications for what is essentially one sustained condition.
What you actually want is: fire once when the threshold is first crossed, then stay quiet for a cooldown period.
Stream Analytics has a built-in function for this called ISFIRST. It returns 1 for the first event that matches a given condition within a fixed time interval, and 0 for all subsequent matching events in that same interval. Use it in the WHERE clause to only let the first match through:
SELECT
deviceId = telemetry.iothub.connectionDeviceId,
temperature,
humidity,
enqueuedTime = telemetry.iothub.enqueuedTime
INTO
[alertOutput]
FROM
[telemetry]
WHERE
ISFIRST(minute, 10) OVER (PARTITION BY telemetry.iothub.connectionDeviceId WHEN temperature > 30) = 1Breaking this down:
ISFIRST(minute, 10)splits time into fixed 10-minute intervalsWHEN temperature > 30is the condition to evaluatePARTITION BY telemetry.iothub.connectionDeviceIdtracks the intervals independently per device
For each device, only the first message above 30°C in each 10-minute interval passes through. The rest are suppressed until the next interval starts. The intervals are fixed, not started by the alert, so two alerts can still land close together on either side of an interval boundary.
This pattern is called alert de-duplication, and it’s a common approach in real-time IoT monitoring.
Time-windowed aggregation #
Stream Analytics can also aggregate data over time windows. This is useful when you want to detect patterns rather than individual threshold crossings. For example, calculating the average, minimum, and maximum temperature per device over one-minute intervals:
SELECT
deviceId = telemetry.iothub.connectionDeviceId,
avgTemperature = AVG(temperature),
minTemperature = MIN(temperature),
maxTemperature = MAX(temperature),
intervalElapsedTimestamp = System.Timestamp
INTO
[alertOutput]
FROM
[telemetry]
TIMESTAMP BY telemetry.iothub.enqueuedTime
GROUP BY
telemetry.iothub.connectionDeviceId,
TumblingWindow(minute, 1)TIMESTAMP BY tells Stream Analytics which field to use as the event timestamp, in this case the IoT Hub enqueue time. TumblingWindow(minute, 1) groups incoming events into fixed, non-overlapping one-minute buckets. System.Timestamp marks the time the window closed.
The result is one row per device per minute, with aggregate temperature values for that interval. Stream Analytics supports other window types too, such as hopping and sliding windows, but the tumbling window covers most IoT use cases.
How it fits in the hot path #
In this series, the hot path works like this:
- IoT Hub receives a message from the device
- Stream Analytics reads from IoT Hub’s built-in endpoint and evaluates the query
- If the condition is met, the result is forwarded to an Event Hub
- A Logic App triggers on the new Event Hub message and sends an email alert
Stream Analytics connects directly to IoT Hub’s built-in endpoint, the same one Azure Data Explorer uses for the warm path. Each service reads through its own consumer group, so they can read at the same time without interfering with each other. The course covers consumer groups in more detail.
Event Hub is necessary because Stream Analytics has no direct integration with Logic Apps. By routing the output to Event Hub, Logic Apps can pick it up using its built-in Event Hub trigger connector.
What comes next? #
That covers both the warm and hot paths. The cold path gets its own post later in the series. Next, we look at the other direction: sending messages from the cloud back to a device with cloud-to-device messaging.