top of page

KQL in SecOps (2) - Mastering Filtering, Projection, and Sorting

  • GK
  • Jan 5
  • 4 min read

Welcome to Part 2 of our KQL in Security Operations Series. Last time, we introduced Kusto Query Language (KQL) and explored why it is critical for SOC analysts using Microsoft Sentinel.


We’ll build on that foundation and dive into the fundamentals of querying data—filtering logs, selecting the right fields, and sorting results. Mastering these skills is essential for writing efficient, actionable KQL queries that power threat hunting, alert investigation, and detection engineering.


By the end of this post, you’ll be able to write your first meaningful Sentinel queries and start analyzing security events like a pro.


Why Filtering, Projection, and Sorting Matter


In a typical SOC, analysts face millions of logs per day. Without narrowing the data, queries can:


  • Run slowly or time out

  • Return overwhelming results

  • Make threat detection difficult


KQL provides three fundamental operators to manage data efficiently:

  1. Filtering (where) – Limit logs to relevant events

  2. Projection (project) – Select only the columns you need

  3. Sorting (sort) – Organize results for analysis


These operations are the building blocks for all KQL queries, from simple lookups to complex detection rules.

Step 1: Filtering Data with where


The where operator is used to filter rows in a table based on conditions. It’s typically the first operator in a query.


Filtering by Event Type

Example: Find failed login attempts in Windows Security Events:

SecurityEvent
| where EventID == 4625
  • SecurityEvent is the table containing Windows logs

  • EventID == 4625 filters only failed logon events



Filtering by Time

Security investigations often focus on a specific time window. KQL provides the TimeGenerated column and ago() function for relative filtering:

SecurityEvent
| where TimeGenerated > ago(24h)
| where EventID == 4625

This query retrieves failed logons in the last 24 hours.


Filtering by String Values

You can filter by usernames, IP addresses, or other string fields:

SigninLogs
| where UserPrincipalName == "jdoe@company.com"

For partial matches, use:

  • contains – matches anywhere in the string

  • startswith – matches the beginning

  • endswith – matches the end


Example: Find sign-ins containing “admin”:

SigninLogs
| where UserPrincipalName contains "admin"

Multiple Conditions

Combine conditions with logical operators:

  • and – both must be true

  • or – either can be true

  • not – negates a condition


Example: Failed logins for a specific account on a specific computer:

SecurityEvent
| where EventID == 4625
| where Account == "jdoe" and Computer == "SERVER01"

Step 2: Selecting Columns with project

By default, KQL queries return all columns in a table, which can be overwhelming. Use project to select only relevant fields:

SecurityEvent
| where EventID == 4625
| project TimeGenerated, Account, Computer, IpAddress
  • TimeGenerated – timestamp of the event

  • Account – the user account

  • Computer – the device name

  • IpAddress – source IP


You can also rename columns for clarity:

| project Timestamp = TimeGenerated, User = Account, Device = Computer

Excluding Columns

Use project-away to remove unnecessary columns:

SecurityEvent
| project-away EventData, LogonType

This simplifies output without manually listing all columns.


Step 3: Sorting Results with sort and top

Sorting makes it easier to spot anomalies and trends.


Sort by Timestamp

SecurityEvent
| where EventID == 4625
| project TimeGenerated, Account, Computer
| sort by TimeGenerated desc
  • desc – descending (most recent first)

  • asc – ascending (oldest first)

Top N Results

To quickly identify the most active accounts or devices, use top:

SecurityEvent
| where EventID == 4625
| summarize FailedLogins = count() by Account
| top 5 by FailedLogins desc

This query shows the 5 users with the most failed logins, a common brute-force indicator.


Combining Filtering, Projection, and Sorting

Here’s a full example:

SecurityEvent
| where EventID == 4625
| where TimeGenerated > ago(24h)
| where Account contains "admin"
| project TimeGenerated, Account, Computer, IpAddress
| sort by TimeGenerated desc
  • Filters by failed logons in the last 24 hours

  • Only includes admin accounts

  • Shows relevant columns

  • Orders results by most recent first


This query is fast, readable, and actionable—exactly what SOC analysts need for investigations.

Practical Security Use Cases

1. Detecting Suspicious Sign-ins

Identify accounts with multiple failed logins:

SigninLogs
| where ResultType != 0
| summarize FailedAttempts = count() by UserPrincipalName, bin(TimeGenerated, 1h)
| where FailedAttempts > 5
  • bin() groups events into hourly intervals

  • This helps detect brute-force or credential-stuffing attempts


2. Investigating Endpoint Activity

Look for recent administrative logons on a critical server:

SecurityEvent
| where EventID == 4624
| where Account startswith "admin"
| where Computer == "SERVER01"
| project TimeGenerated, Account, Computer, LogonType
| sort by TimeGenerated desc

3. Preparing Data for Threat Hunting

Select only the necessary fields to feed into advanced queries:

DeviceProcessEvents
| where ProcessName contains "powershell"
| project TimeGenerated, DeviceName, Account, CommandLine
| sort by TimeGenerated desc

This query is a starting point for hunting malicious PowerShell activity.

Common Beginner Mistakes to Avoid

  1. Not filtering by time – querying all logs can slow queries and return irrelevant data

  2. Selecting too many columns – makes analysis harder

  3. Ignoring case sensitivity – string comparisons in KQL are case-sensitive by default

  4. Failing to combine operators logically – can lead to incomplete results

  5. Not testing queries incrementally – build queries step by step for accuracy


Tips for Writing Efficient Queries

  • Always filter early to reduce the number of rows processed

  • Only project necessary columns

  • Use summarize and bin() when grouping events

  • Test queries interactively before turning them into alerts

  • Document your queries for future SOC team members


Looking Ahead: Part 3 Preview

Next month, we’ll expand on aggregation and summarization in KQL, including:

  • Counting events by user, device, or IP

  • Time-based grouping with bin()

  • Using summarize for SOC metrics

  • Detecting anomalies and trends in security events


By Part 3, you’ll be able to analyze security patterns across time and build actionable alerts.

Conclusion

Filtering, projection, and sorting are the cornerstones of effective KQL queries. They allow SOC analysts to narrow down massive log volumes, focus on the relevant data, and uncover security threats quickly.


In this part, you learned:

  • How to filter events by type, time, or string values

  • How to project only the necessary columns

  • How to sort and highlight critical events

  • Practical security use cases to start using these techniques today


Mastering these fundamentals prepares you for Part 3, where we will dive into aggregation, summarization, and time-based analysis—a crucial step for threat hunting and SOC reporting.


Key Takeaways for Part 2

  1. Filtering with where is your first step in every query.

  2. Project only the columns you need for clarity and speed.

  3. Sort and summarize data to identify anomalies efficiently.

  4. Test queries incrementally and document them for team use.

  5. Combining filtering, projection, and sorting is the foundation of all KQL queries.


 
 
 

Comments


bottom of page