| title | Tutorial: Create your first graphs in Kusto Query Language |
|---|---|
| description | Learn how to model and query interconnected data using graph semantics in Kusto Query Language (KQL). Build transient and persistent graphs to analyze organizational hierarchies. |
| ms.service | azure-data-explorer |
| ms.topic | tutorial |
| ms.custom | mvc |
| ms.date | 05/26/2025 |
[!INCLUDE applies] [!INCLUDE fabric] [!INCLUDE azure-data-explorer] [!INCLUDE monitor] [!INCLUDE sentinel]
Graph semantics in Kusto enables you to model and query data as interconnected networks, making it intuitive to analyze complex relationships like organizational hierarchies, social networks, and attack paths. Unlike traditional relational queries that rely on joins, graphs use direct relationships between entities to traverse connections efficiently.
In this tutorial, you learn how to: ::: moniker range="microsoft-fabric || azure-data-explorer"
[!div class="checklist"]
Create a transient graph using the make-graph operator
Query graphs to find relationships using graph-match
Build persistent graph models for reusable analysis
Compare transient versus persistent graph approaches ::: moniker-end ::: moniker range="azure-monitor || microsoft-sentinel" [!div class="checklist"]
Create a transient graph using the make-graph operator
Query graphs to find relationships using graph-match
Compare transient versus persistent graph approaches ::: moniker-end
If you don't have an Azure Data Explorer cluster, create a free cluster before you begin the tutorial.
- A Microsoft account or Microsoft Entra user identity to sign in to the help cluster
::: moniker range="microsoft-fabric"
- A Fabric workspace with a Microsoft Fabric-enabled capacity
::: moniker-end
::: moniker range="azure-data-explorer" Open the Azure Data Explorer Web UI to access the help cluster for this tutorial. ::: moniker-end
::: moniker range="microsoft-fabric" Navigate to your Microsoft Fabric workspace and open a KQL database to run the queries. ::: moniker-end
::: moniker range="microsoft-sentinel" Navigate to the advanced hunting page to start querying Microsoft Sentinel data. ::: moniker-end
::: moniker range="azure-monitor" Navigate to Logs or to a Logs Analytics workspace in the Azure portal to start querying Azure Monitor data. ::: moniker-end
In this section, you'll create your first graph using sample organizational data. Transient graphs are created dynamically during query execution using the make-graph operator, making them perfect for ad-hoc analysis and exploration.
You'll work with a simple company structure where employees report to managers. This organizational hierarchy provides an intuitive example for understanding graph relationships:
:::image type="content" source="../media/graphs/tutorial-first-graph.png" alt-text="A diagram showing the organization hierarchy.":::
Create the organizational graph structure using employee and reporting relationship data:
:::moniker range="azure-data-explorer"
[!div class="nextstepaction"] Run the query ::: moniker-end
// Create sample employee data
let employees = datatable(name:string, role:string, age:long)
[
"Alice", "CEO", 45,
"Bob", "Engineering Manager", 35,
"Carol", "Marketing Manager", 38,
"Dave", "Developer", 28,
"Eve", "Developer", 26,
"Frank", "Marketing Specialist", 30
];
// Create reporting relationships
let reports = datatable(employee:string, manager:string)
[
"Bob", "Alice",
"Carol", "Alice",
"Dave", "Bob",
"Eve", "Bob",
"Frank", "Carol"
];
// Build the graph and explore it
reports
| make-graph employee --> manager with employees on name
| graph-to-table nodes | name | role | age |
|---|---|---|
| Alice | CEO | 45 |
| Bob | Engineering Manager | 35 |
| Carol | Marketing Manager | 38 |
| Dave | Developer | 28 |
| Eve | Developer | 26 |
| Frank | Marketing Specialist | 30 |
Now you'll learn to use the graph-match operator to find specific patterns in your organizational graph. The graph-match operator searches for relationships and connections within the graph structure.
First, find all employees who directly report to Alice by matching the immediate reporting relationship pattern:
:::moniker range="azure-data-explorer"
[!div class="nextstepaction"] Run the query ::: moniker-end
let employees = datatable(name:string, role:string, age:long)
[
"Alice", "CEO", 45,
"Bob", "Engineering Manager", 35,
"Carol", "Marketing Manager", 38,
"Dave", "Developer", 28,
"Eve", "Developer", 26,
"Frank", "Marketing Specialist", 30
];
let reports = datatable(employee:string, manager:string)
[
"Bob", "Alice",
"Carol", "Alice",
"Dave", "Bob",
"Eve", "Bob",
"Frank", "Carol"
];
reports
| make-graph employee --> manager with employees on name
| graph-match (alice)<-[reports]-(employee)
where alice.name == "Alice"
project employee = employee.name, role = employee.role, age = employee.age| employee | role | age |
|---|---|---|
| Bob | Engineering Manager | 35 |
| Carol | Marketing Manager | 38 |
Next, find all employees in Alice's entire organization, including indirect reports, using variable length edges with *1..3 to traverse multiple levels of the hierarchy:
:::moniker range="azure-data-explorer"
[!div class="nextstepaction"] Run the query ::: moniker-end
let employees = datatable(name:string, role:string, age:long)
[
"Alice", "CEO", 45,
"Bob", "Engineering Manager", 35,
"Carol", "Marketing Manager", 38,
"Dave", "Developer", 28,
"Eve", "Developer", 26,
"Frank", "Marketing Specialist", 30
];
let reports = datatable(employee:string, manager:string)
[
"Bob", "Alice",
"Carol", "Alice",
"Dave", "Bob",
"Eve", "Bob",
"Frank", "Carol"
];
reports
| make-graph employee --> manager with employees on name
| graph-match (alice)<-[reports*1..3]-(employee)
where alice.name == "Alice"
project employee = employee.name, role = employee.role, reportingLevels = array_length(reports)| employee | role | reportingLevels |
|---|---|---|
| Bob | Engineering Manager | 1 |
| Carol | Marketing Manager | 1 |
| Dave | Developer | 2 |
| Eve | Developer | 2 |
| Frank | Marketing Specialist | 2 |
::: moniker range="azure-data-explorer || microsoft-fabric"
Note
This feature is currently in public preview. Functionality and syntax are subject to change before General Availability.
Persistent graphs are stored in the database and can be queried repeatedly without rebuilding the graph structure. You'll now create the same organizational structure as a persistent graph for better performance and reusability.
Create functions that return your sample data, then define a graph model structure:
// Create a function that returns employee data
.create function Employees() {
datatable(name: string, role: string, age: long)
[
"Alice", "CEO", 45,
"Bob", "Engineering Manager", 35,
"Carol", "Marketing Manager", 38,
"Dave", "Developer", 28,
"Eve", "Developer", 26,
"Frank", "Marketing Specialist", 30
]
}
// Create a function that returns reporting relationships
.create function Reports() {
datatable(employee: string, manager: string)
[
"Bob", "Alice",
"Carol", "Alice",
"Dave", "Bob",
"Eve", "Bob",
"Frank", "Carol"
]
}Define the graph model with node and edge schemas:
.create-or-alter graph_model OrganizationGraph ```
{
"Schema": {
"Nodes": {
"Employee": {
"name": "string",
"role": "string",
"age": "long"
}
},
"Edges": {
"ReportsTo": {
}
}
},
"Definition": {
"Steps": [
{
"Kind": "AddNodes",
"Query": "Employees()",
"NodeIdColumn": "name",
"Labels": ["Employee"]
},
{
"Kind": "AddEdges",
"Query": "Reports()",
"SourceColumn": "employee",
"TargetColumn": "manager",
"Labels": ["ReportsTo"]
}
]
}
}
```Create a graph snapshot to materialize the model into a queryable structure:
.make graph_snapshot OrganizationGraph_v1 from OrganizationGraphQuery the persistent graph using the same patterns as transient graphs. Find all employees who report to Alice:
graph("OrganizationGraph")
| graph-match (alice)<-[reports]-(employee)
where alice.name == "Alice"
project employee = employee.name, role = employee.role, age = employee.ageFind all employees in Alice's organization including indirect reports:
graph("OrganizationGraph")
| graph-match (alice)<-[reports*1..3]-(employee)
where alice.name == "Alice"
project employee = employee.name, role = employee.role, reportingLevels = array_length(reports)Query a specific snapshot version if needed:
graph("OrganizationGraph", "OrganizationGraph_v1")
| graph-match (alice)<-[reports*1..3]-(employee)
where alice.name == "Alice"
project employee = employee.name, role = employee.role::: moniker-end
Understanding when to use each approach helps you choose the right method for your analysis needs:
| Aspect | Transient Graphs | Persistent Graphs |
|---|---|---|
| Creation | make-graph operator during query |
.create-or-alter graph_model + .make graph_snapshot |
| Storage | In-memory during query execution | Stored in database |
| Reusability | Must rebuild for each query | Query repeatedly without rebuilding |
| Performance | Good for smaller datasets | Optimized for large, complex graphs |
| Use cases | Ad-hoc analysis, exploration | Production analytics, repeated queries |
| Memory limits | Limited by node memory | Can handle larger datasets |
::: moniker range="azure-data-explorer || microsoft-fabric" If you're not going to continue using the persistent graph models, delete them with the following commands:
-
Drop the graph model:
.drop graph_model OrganizationGraph
-
Drop the helper functions:
.drop function Employees .drop function Reports
::: moniker-end
The transient graphs are automatically cleaned up when the query completes, so no additional cleanup is needed for those examples.
Now that you understand the basics of graph semantics in Kusto, advance to more complex scenarios and optimizations:
[!div class="nextstepaction"] Graph best practices
You can also explore these related articles: ::: moniker range="azure-data-explorer || microsoft-fabric"
-
Graph operators reference - Complete guide to all available graph operators
-
Graph model management - Deep dive into persistent graph models
-
Graph shortest paths - Find optimal paths between entities
-
Advanced graph queries - Complex analysis patterns and use cases ::: moniker-end ::: moniker range="microsoft-sentinel || azure-monitor"
-
Graph operators reference - Complete guide to all available graph operators
-
Graph shortest paths - Find optimal paths between entities
-
Advanced graph queries - Complex analysis patterns and use cases ::: moniker-end