Creating complex queries in the new query language for Log Analytics | Quisitive

This series will introduce some tricks and tips for writing more complex queries in Log Analytics and integrating these queries into Microsoft Flow. In this blog post I will showcase an example of how to build a query composed of multiple sub-queries.

The example used for this blog post series will cover what on the top level appears to be a simple question: “Should I open the windows?” At first glance this seems to be a pretty easy item to determine, but once you peel this particular onion it’s more complicated than it looks on the surface. Here’s the first layer to the question:

  1. Is it currently colder outside than it most likely is within the house? (I live in Texas so this example won’t even consider opening up any windows to warm up a house)
  2. Is it raining outside? (Yes, we actually do get rain in Texas!)

But that’s just the first layer. We also need to consider the following:

  1. Is the outside temperature still colder outside than it is within the house? (IE: is it going to stay cold long enough to justify opening the windows)
  2. Is it forecasted to rain outside in the next few hours? (IE: is it not going to rain for long enough to justify opening the windows)
  3. Finally, notification for this should only occur during hours that someone is actually awake and willing to take action on it. For this example we’ll assume no notifications before 7 am and no notifications after 10 pm.

In this blog post series we will unpack the above and show you how you can use Log Analytics to break down a complex query of this nature. Please note, to get the weather data required see this blog post and to get the weather forecasting data see this blog post. In this blog item we will create the query by creating a series of sub-queries for Current Weather and Weather Forecast, assemble these together and then apply an approach to cause it to only fire during specific hours.

Current Weather:

We’ll start this with a query which gathers the first two items listed above. This query identifies the current weather state for temperature (Temp_d) and weather description (Description_s).

let place = "Frisco"; // string

OpenWeather_CL

| where TimeGenerated > now(-1day) and tostring(City_s) == place

| project Description_s, Temp_d, TimeGenerated

| sort by TimeGenerated | top 1 by TimeGenerated

With sample results shown below:

Note a couple of tricks in the above query. First, we’re using the “let” statement to define variables that we will use in the query. Second, we’re limiting our results to only data generated in the last day to minimize the amount of data gathered and to maximize the speed of the query. Third, we are limiting our query to the most recent record using “top 1”.

Now we need to know what options are available for the description field so that we can identify those which indicate rain. The following query looks over time to find what weather conditions have occurred.

OpenWeather_CL | project Description_s | distinct Description_s | sort by Description_s asc

With sample results shown below:

From this set of data, it looks like we are looking to avoid any descriptions which contain “rain” or “drizzle”. And we want to exclude any temperatures outside which are greater than a certain minimum temperature and which are smaller than a certain maximum temperature. The query below shows an example:

let place = "Frisco"; // string

let MinTemp = 65;

let MaxTemp = 89;

OpenWeather_CL

| where TimeGenerated > now(-1day) and tostring(City_s) == place

| project Description_s, Temp_d, TimeGenerated

| sort by TimeGenerated | top 1 by TimeGenerated

| where Description_s !contains "Drizzle" and Description_s !contains "Rain" and Temp_d < MaxTemp and Temp_d > MinTemp

Sample results for a successful condition are shown below:

Now we need to take this condition and make a single variable that reflects the current weather in a yes/no type flag to reuse in our final query (in this case a 0 or 1). We can do this easily by adding a count to the end of the query.

let place = "Frisco"; // string

let MinTemp = 65;

let MaxTemp = 76;

let CurrentWeather = OpenWeather_CL

| where TimeGenerated > now(-1day) and tostring(City_s) == place

| project Description_s, Temp_d, TimeGenerated

| sort by TimeGenerated | top 1 by TimeGenerated

| where Description_s !contains "Drizzle" and Description_s !contains "Rain" and Temp_d < MaxTemp and Temp_d > MinTemp | count;

CurrentWeather

The above example uses a let to define CurrentWeather and note the bolded CurrentWeather above which shows us the results of the variable that we defined.

Sample results for a successful condition are shown below:

Weather Forecast:

We can take what we built for the current weather and apply the same concepts for weather forecasting by using OpenWeatherForecast_CL in place of OpenWeather_CL.

let place = "Frisco"; // string

let MinTemp = 65;

let MaxTemp = 76;

let ForecastWeather = OpenWeatherForecast_CL | sort by ForecastTimeDate_t asc

| where ForecastTimeDate_t > now() and ForecastTimeDate_t < now(+4hours) and tostring(City_s) == place

| project ForecastDescription_s, ForecastTemp_d, ForecastTimeDate_t | top 1 by ForecastTimeDate_t

| where ForecastDescription_s !contains "Drizzle" and ForecastDescription_s !contains "Rain" and ForecastTemp_d < MaxTemp and ForecastTemp_d > MinTemp | count;

ForecastWeather

An example success result looks just like our success condition for CurrentWeather.

Bringing the queries together:

To bring these queries together we remove the spaces between the queries and then we remove the duplicate copies of the variables that we defined for place, MinTemp, and MaxTemp. Next we create a join on the results where we use extend to create a WeatherFlag. If the WeatherFlag = 2 it indicates a success on both conditions (since there are two conditions for success/fail – each one having a successful value of 1 and a failure value of 0).

let place = "Frisco"; // string

let MinTemp = 65;

let MaxTemp = 76;

let CurrentWeather = OpenWeather_CL

| where TimeGenerated > now(-1day) and tostring(City_s) == place

| project Description_s, Temp_d, TimeGenerated

| sort by TimeGenerated | top 1 by TimeGenerated

| where Description_s !contains "Drizzle" and Description_s !contains "Rain" and Temp_d < MaxTemp and Temp_d > MinTemp | count;

//CurrentWeather

let ForecastWeather = OpenWeatherForecast_CL | sort by ForecastTimeDate_t asc

| where ForecastTimeDate_t > now() and ForecastTimeDate_t < now(+4hours) and tostring(City_s) == place

| project ForecastDescription_s, ForecastTemp_d, ForecastTimeDate_t | top 1 by ForecastTimeDate_t

| where ForecastDescription_s !contains "Drizzle" and ForecastDescription_s !contains "Rain" and ForecastTemp_d < MaxTemp and ForecastTemp_d > MinTemp | count;

//ForecastWeather

let WeatherConditions = CurrentWeather | join (ForecastWeather) on Count

| extend WeatherFlag = Count + Count1

| project WeatherFlag;

WeatherConditions

A sample successful output (IE: Open the Windows) is shown below:

A sample failure output (IE: Don’t open the Windows) is shown below:

Scheduling within the query:

Now we want to schedule this query to run every hour during certain hours of the week. Alerting was recently moved to Azure but we can’t use alerting in Azure to accomplish this yet due to the options which are currently available (see the screenshot below). Currently we can only specify frequency, we can’t also specify what hours not to run within that frequency.

In Microsoft Flow we can currently set a recurrence for a query in Log Analytics but here we can’t specify it to only run during certain hours of the day either.

To work around this, we’ll add the scheduling capabilities directly into the query we have created in this blog post. To add this we’ll go back to a previous blog I wrote on how to restrict on time. To do this we add variables for startDateTime, StartNotification and StopNotification. These variables are then used at the end of the query to restrict the final results to only provide data during those timeframes. The final query is shown below.

let startDatetime = startofday(now());

// StartNotification is 8:00 am (8) plus 6 hours for the UTC Offset (14)

let StartNotification = startDatetime + 14hours;

// StopNotification is 5:00 pm (17) plus 6 hours for the UTC Offset (23)

let StopNotification = startDatetime + 23hours;

let place = "Frisco"; // string

let MinTemp = 65;

let MaxTemp = 76;

let CurrentWeather = OpenWeather_CL

| where TimeGenerated > now(-1day) and tostring(City_s) == place

| project Description_s, Temp_d, TimeGenerated

| sort
by TimeGenerated | top
1
by TimeGenerated

| where Description_s !contains
"Drizzle"
and Description_s !contains
"Rain"
and Temp_d < MaxTemp and Temp_d > MinTemp | count;

//CurrentWeather

let ForecastWeather = OpenWeatherForecast_CL | sort
by ForecastTimeDate_t asc

| where ForecastTimeDate_t > now() and ForecastTimeDate_t < now(+4hours) and tostring(City_s) == place

| project ForecastDescription_s, ForecastTemp_d, ForecastTimeDate_t | top
1
by ForecastTimeDate_t

| where ForecastDescription_s !contains
"Drizzle"
and ForecastDescription_s !contains
"Rain"
and ForecastTemp_d < MaxTemp and ForecastTemp_d > MinTemp | count;

//ForecastWeather

let WeatherConditions = CurrentWeather | join (ForecastWeather) on Count

| extend WeatherFlag = Count + Count1

| extend CurrentTime = now()

| project WeatherFlag, CurrentTime;

WeatherConditions

| where CurrentTime > StartNotification and CurrentTime < StopNotification

Summary: This blog post showed some tricks to use when building more complex queries in Log Analytics. Some of my recommendations from these include:

In the next blog post we’ll use Microsoft Flow to run this query on a regular basis, how to send emails based on the results and how to use approvals to change dat