diff --git a/data-explorer/kusto/management/update-table-command.md b/data-explorer/kusto/management/update-table-command.md
index 9b55a09785..7709ac1a73 100644
--- a/data-explorer/kusto/management/update-table-command.md
+++ b/data-explorer/kusto/management/update-table-command.md
@@ -1,15 +1,19 @@
---
-title: .update table command
-description: Learn how to use the .update table command to perform transactional data updates.
+title: The update table command
+description: Learn how to use update table command to perform transactional data updates.
ms.reviewer: vplauzon
ms.topic: reference
ms.date: 11/18/2024
---
-# .update table command
+# `.update table` command
> [!INCLUDE [applies](../includes/applies-to-version/applies.md)] [!INCLUDE [fabric](../includes/applies-to-version/fabric.md)] [!INCLUDE [azure-data-explorer](../includes/applies-to-version/azure-data-explorer.md)]
The `.update table` command performs data updates in a specified table by deleting and appending records atomically.
+There are two options for appending the records:
+
+- Ingest the records based on a provided query. The query is noted using the *AppendIdentifier*.
+- Move extents containing the records to append from another table to the target table. For more information, see [Update using move extents](#update-using-move-extents).
> [!WARNING]
> This command is unrecoverable.
@@ -27,9 +31,20 @@ You must have at least [Table Admin](../access-control/role-based-access-control
[!INCLUDE [syntax-conventions-note](../includes/syntax-conventions-note.md)]
-`.update` `[async]` `table` *TableName* `delete` *DeleteIdentifier* `append` *AppendIdentifier* [`with` `(` *propertyName* `=` *propertyValue* `)`] `<|`
-`let` *DeleteIdentifier*`=` *DeletePredicate*`;`
-`let` *AppendIdentifier*`=` *AppendPredicate*`;`
+**Update using `append` and `delete`:**
+
+```kusto
+.update [async] table TableName delete DeleteIdentifier append AppendIdentifier [with ( propertyName = propertyValue )] <|
+let DeleteIdentifier = DeletePredicate;
+let AppendIdentifier = AppendPredicate;
+```
+
+**Update using `move extents` and `delete`:**
+
+```kusto
+.update [async] table TableName delete DeleteIdentifier move SourceTableName [with ( *propertyName = propertyValue )] <|
+let DeleteIdentifier = DeletePredicate;
+```
### Parameters
@@ -39,21 +54,25 @@ You must have at least [Table Admin](../access-control/role-based-access-control
| *TableName* | `string` | :heavy_check_mark: | The name of the table to update.
| *DeleteIdentifier* | `string` | :heavy_check_mark: | The identifier name used to specify the delete predicate applied to the updated table. |
| *DeletePredicate* | `string` | :heavy_check_mark: | The text of a query whose results are used as data to delete. The predicate has the same [limitations as the soft delete predicate](../concepts/data-soft-delete.md#limitations-and-considerations). |
-| *AppendIdentifier* | `string` | :heavy_check_mark: | The identifier name used to specify the append predicate applied to the updated table. |
-| *AppendPredicate* | `string` | :heavy_check_mark: | The text of a query whose results are used as data to append. |
+| *AppendIdentifier* | `string` || The identifier name used to specify the append predicate applied to the updated table. Required if using update based on ingest from query. |
+| *AppendPredicate* | `string` || The text of a query whose results are used as data to append. Required if using update based on ingest from query. |
+| *SourceTableName* | `string` || The name of the table to move extents from. Must be a local table in current database. Required if using [Update using move extents](#update-using-move-extents).|
> [!IMPORTANT]
+>
> * Both delete and append predicates can't use remote entities, cross-db, and cross-cluster entities. Predicates can't reference an external table or use the `externaldata` operator.
-> * Append and delete queries are expected to produce deterministic results. Nondeterministic queries can lead to unexpected results. A query is deterministic if and only if it would return the same data if executed multiple times.
+> * Append and delete queries are expected to produce deterministic results. Nondeterministic queries can lead to unexpected results. A query is deterministic if and only if it would return the same data if executed multiple times.
> * For example, use of [`take` operator](../query/take-operator.md), [`sample` operator](../query/sample-operator.md), [`rand` function](../query/rand-function.md), and other such operators isn't recommended because these operators aren't deterministic.
> * Queries might be executed more than once within the `update` execution. If the intermediate query results are inconsistent, the update command can produce unexpected results.
+> * The delete and append predicates are based on the same snapshot of the table, and therefore they can't depend on each other. In other words, the append predicate executes on a snapshot of the source table *before* the deletion and vice versa - the delete predicate executes on a snapshot of the source table *before* the append.
## Supported properties
| Name | Type | Description |
| -------- | ---- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| *whatif* | bool | If `true`, returns the number of records that will be appended / deleted in every shard, without appending / deleting any records. The default is `false`. |
-| *distributed* | bool | If `true`, the command ingests from all nodes executing the query in parallel. Default is `false`. See [performance tips](#performance-tips). |
+| `whatif` | bool | If `true`, returns the number of records that are appended / deleted in every shard, without appending / deleting any records. The default is `false`. |
+| `distributed` | bool | If `true`, the command ingests from all nodes executing the query in parallel. This option is relevant only for update based on ingest from query. Default is `false`. See [performance tips](#performance-tips). |
+| `extentTagsToMove` | string | Optional [Extent tags](extent-tags.md) to filter only specific extents, when using update using move extents. The tags are provided as an array, in the same format as in [Ingestion properties](../ingestion-properties.md). See examples in [Update using move extents](#update-using-move-extents).|
> [!IMPORTANT]
> We recommend running in `whatif` mode first before executing the update to validate the predicates before deleting or appending data.
@@ -69,9 +88,15 @@ The result of the command is a table where each record represents an [extent](ex
| ExtentId | `guid` | The unique identifier for the extent created or deleted by the command. |
| RowCount | `long` | The number of rows created or deleted in the specified extent by the command. |
+## Update using move extents
+
+When you use this option, the records to append to target table are moved from the provided *SourceTableName* using [move extents](move-extents.md). The update moves *all* extents from the table, or only those that match the provided *extentTagsToMove*, if *extentTagsToMove* are specified in the command properties. The source table must be a local table in the current database and must the same schema as the target table.
+
+The move extents option is useful when the records to append are already ingested to a staging table in the database, and should replace existing records in the target table. In this case, using the move extents option is more efficient than ingest from query, as this option doesn't require reingesting the records. See examples in [Update rows from a staging table using move extents](#update-rows-from-a-staging-table-using-move-extents).
+
## Choose between `.update table` and materialized views
-There are scenarios where you could use either the `.update table` command or a [materialized view](materialized-views/materialized-view-overview.md) to achieve the same goal in a table. For example, a materialized view could be used to keep the latest version of each record or an update could be used to update records when a new version is available.
+There are scenarios where you could use either the `.update table` command or a [materialized view](materialized-views/materialized-view-overview.md) to achieve the same goal in a table. For example, a materialized view could be used to keep the latest version of each record or an update could be used to update records when a new version is available.
Use the following guidelines to decide which method to use:
@@ -81,9 +106,9 @@ Use the following guidelines to decide which method to use:
## Performance tips
-* Data ingestion is a resource-intensive operation that might affect concurrent activities on the cluster, including running queries. We recommend that you avoid the following resource-intensive actions: running many `.update` commands at once, and intensive use of the *distributed* property.
+* Data ingestion is a resource-intensive operation that might affect concurrent activities on the cluster, including running queries. We recommend that you avoid the following resource-intensive actions: running many `.update` commands at once, and intensive use of the *distributed* property.
* Limit the append data to less than 1 GB per operation. If necessary, use multiple update commands.
-* Set the `distributed` flag to `true` only if the amount of data being produced by the query is large, exceeds 1 GB and doesn't require serialization: multiple nodes can then produce output in parallel. Don't use this flag when query results are small, since it might needlessly generate many small data shards.
+* Set the `distributed` flag to `true` only if the amount of data being produced by the query is large, exceeds 1 GB, and doesn't require serialization: multiple nodes can then produce output in parallel. Don't use this flag when query results are small, since it might needlessly generate many small data shards.
## Examples
@@ -163,7 +188,6 @@ The following example updates multiple columns on all rows with color gray.
| extend Color="";
```
-
### Update rows using another table (reference values)
In this example, the first step is to create the following mapping table:
@@ -206,7 +230,7 @@ Sometimes values to update are known without being stored in a table, and the [d
| where true;
```
-### Update rows with a staging table
+### Update rows from a staging table using move extents
A popular pattern is to first land data in a staging table before updating the main table.
@@ -220,17 +244,47 @@ The first command creates a staging table:
| extend Color = tostring(dynamic(["Red", "Blue", "Gray"])[Id %3])
```
-The next command updates the main table with the data in the staging table:
+The next command updates the main table with the data in the staging table, by moving all extents from staging table to main table:
```kusto
-.update table Employees delete D append A <|
- let A = MyStagingTable;
+.update table Employees delete D move MyStagingTable <|
let D = Employees
| join kind=leftsemi MyStagingTable on Id
| where true;
```
-Some records in the staging table didn't exist in the main table (that is, had `Id>100`) but were still inserted in the main table (upsert behavior).
+Some records in the staging table didn't exist in the main table (that is, had `Id>100`) but were still inserted in the main table (upsert behavior). Since the command uses move extents, MyStagingTable will be empty after the update.
+
+#### Update from a staging table using move extents with extent tags
+
+In this example, the extents in the staging table include extent tags, and we're only interested in updating based on a subset of the tags:
+
+```kusto
+.set-or-replace MyStagingTable with(tags='["drop-by:tag1"]')<|
+ range i from 70 to 100 step 5
+ | project Id=i
+ | extend Code = tostring(dynamic(["Customer", "Employee"])[Id %2])
+ | extend Color = tostring(dynamic(["Red", "Blue", "Gray"])[Id %3])
+
+.set-or-replace MyStagingTable with(tags='["drop-by:tag2"]')<|
+ range i from 100 to 150 step 5
+ | project Id=i
+ | extend Code = tostring(dynamic(["Customer", "Employee"])[Id %2])
+ | extend Color = tostring(dynamic(["Red", "Blue", "Gray"])[Id %3])
+```
+
+The following command updates the main table with the data from the first ingestion ("drop-by:tag1") in the staging table.
+The delete part must filter on the extent tags as well, if you would like to only delete records based on this tag.
+The entire MyStagingTable is considered for the delete query, not only the records in the extents with "drop-by:tag1" so the filter
+must be explicitly added to the delete query.
+
+```kusto
+.update table Employees delete D move MyStagingTable with (extentTagsToMove='["drop-by:tag1"]') <|
+ let D = Employees | where Id in (MyStagingTable | where extent_tags() has "drop-by:tag1" | project Id);
+```
+
+At the end of the command, MyStagingTable includes only the records from the second ingestion ("drop-by:tag2"), as the command
+moves the extents with `drop-by:tag1`.
### Compound key
diff --git a/data-explorer/kusto/query/graph-exploration-basics.md b/data-explorer/kusto/query/graph-exploration-basics.md
index 9141f0208f..2530052fb3 100644
--- a/data-explorer/kusto/query/graph-exploration-basics.md
+++ b/data-explorer/kusto/query/graph-exploration-basics.md
@@ -342,6 +342,64 @@ graph('BloodHound_Entra')
|---|
|{
"onpremsyncenabled": "bool",
"system_tags": "string",
"lastcollected": "string",
"pwdlastset": "string",
"usertype": "string",
"userprincipalname": "string",
"email": "string",
"tenantid": "guid",
"name": "string",
"lastseen": "string",
"displayname": "string",
"enabled": "bool",
"title": "string",
"onpremid": "string",
"objectid": "guid",
"whencreated": "string"
}|
+**Find all properties of all nodes by label**:
+
+This advanced schema discovery query identifies all property names that exist across nodes of each label type. Unlike the previous query that shows the schema structure, this query aggregates property names across all nodes of the same type, helping you understand which properties are consistently available and which might be optional or rare. This example uses the [`LDBC_SNB_Interactive` graph](graph-sample-data.md#ldbc-snb-interactive) to explore the complete property landscape of different entity types in the social network dataset.
+
+:::moniker range="azure-data-explorer"
+> [!div class="nextstepaction"]
+> Run the query
+::: moniker-end
+
+```kusto
+graph('LDBC_SNB_Interactive')
+| graph-match (node)
+ project properties = node, labels = labels(node)
+| mv-apply properties on (
+ mv-expand kind=array properties
+ | where isnotempty(properties[1])
+ | summarize properties = make_set(properties[0])
+ )
+| mv-expand label = labels to typeof(string)
+| summarize properties =make_set(properties) by label
+| take 3
+```
+
+|label|properties|
+|---|---|
+|TAGCLASS|[
"id",
"node_id",
"lbl",
"name",
"url"
]|
+|TAG|[
"id",
"node_id",
"lbl",
"name",
"url"
]|
+|FORUM|[
"id",
"creationDate",
"node_id",
"lbl",
"title"
]|
+
+**Find all properties of all edges by label**:
+
+This query performs schema discovery for edge (relationship) properties, showing what information is stored with each type of relationship in your graph. Understanding edge properties is crucial for analyzing relationship metadata such as timestamps, weights, confidence scores, or other attributes that provide context about connections. This example uses the [`BloodHound_AD` graph](graph-sample-data.md#bloodhound-active-directory-dataset) to explore the properties available on different types of Active Directory privilege relationships.
+
+:::moniker range="azure-data-explorer"
+> [!div class="nextstepaction"]
+> Run the query
+::: moniker-end
+
+```kusto
+graph('BloodHound_AD')
+| graph-match ()-[e]-()
+ project properties = e, labels = labels(e)
+| mv-apply properties on (
+ mv-expand kind=array properties
+ | where isnotempty(properties[1])
+ | summarize properties = make_set(properties[0])
+ )
+| mv-expand label = labels to typeof(string)
+| summarize properties =make_set(properties) by label
+| take 3
+```
+
+|label|properties|
+|---|---|
+|GetChangesAll|[
"id",
"lbl",
"src",
"dst",
"properties",
"lastseen"
]|
+|OwnsRaw|[
"id",
"lbl",
"src",
"dst",
"properties",
"lastseen"
]|
+|AddKeyCredentialLink|[
"id",
"lbl",
"src",
"dst",
"properties",
"lastseen"
]|
+
**Find nodes with specific property values**:
Use this pattern to locate entities with particular characteristics or to validate data quality by checking for expected property values. This example uses the [`BloodHound_Entra` graph](graph-sample-data.md#bloodhound-entra-dataset) to find nodes with specific name properties in Microsoft Entra environments.
@@ -365,6 +423,42 @@ graph('BloodHound_Entra')
|10|CJACKSON@PHANTOMCORP.ONMICROSOFT.COM|
|12|RHALL@PHANTOMCORP.ONMICROSOFT.COM|
+### Topology of the graph
+
+Understanding the overall topology of your graph reveals the types of connections that exist between different entity types. This analysis helps you understand the data model, identify the most common relationship patterns, and discover potential paths for traversal queries. The topology query shows which node labels connect to which other node labels through specific edge types, providing a comprehensive view of your graph's structure.
+
+:::moniker range="azure-data-explorer"
+> [!div class="nextstepaction"]
+> Run the query
+::: moniker-end
+
+```kusto
+//Topology of the graph - What's connected to what?
+graph('LDBC_Financial')
+| graph-match (src)-[e]->(dst)
+ project SourceLabels = labels(src), EdgeLabels = labels(e), DestinationLabels = labels(dst)
+| mv-expand EdgeLabel = EdgeLabels to typeof(string)
+| mv-expand SourceLabel = SourceLabels to typeof(string)
+| mv-expand DestinationLabel = DestinationLabels to typeof(string)
+| summarize Count = count() by SourceLabel, EdgeLabel, DestinationLabel
+```
+
+|SourceLabel|EdgeLabel|DestinationLabel|Count|
+|---|---|---|---|
+|COMPANY|GUARANTEE|COMPANY|202|
+|COMPANY|APPLY|LOAN|449|
+|PERSON|APPLY|LOAN|927|
+|ACCOUNT|REPAY|LOAN|2747|
+|LOAN|DEPOSIT|ACCOUNT|2758|
+|ACCOUNT|TRANSFER|ACCOUNT|8132|
+|ACCOUNT|WITHDRAW|ACCOUNT|9182|
+|PERSON|GUARANTEE|PERSON|377|
+|COMPANY|OWN|ACCOUNT|671|
+|COMPANY|INVEST|COMPANY|679|
+|PERSON|OWN|ACCOUNT|1384|
+|MEDIUM|SIGN_IN|ACCOUNT|2489|
+|PERSON|INVEST|COMPANY|1304|
+
## Related content
- [Graph semantics overview](graph-semantics-overview.md)
diff --git a/data-explorer/kusto/query/graph-query-language-reference.md b/data-explorer/kusto/query/graph-query-language-reference.md
new file mode 100644
index 0000000000..7091129098
--- /dev/null
+++ b/data-explorer/kusto/query/graph-query-language-reference.md
@@ -0,0 +1,434 @@
+---
+title: Graph Query Language (GQL) Reference
+description: Reference for Graph Query Language (GQL) including fundamentals, functions, operators, and technical concepts
+ms.reviewer: herauch
+ms.topic: reference
+ms.date: 09/02/2025
+
+#CustomerIntent: As a developer, I want to understand GQL fundamentals and reference materials so that I can effectively query graph data.
+---
+# Graph Query Language (GQL) reference (preview)
+
+> [!INCLUDE [applies](../includes/applies-to-version/applies.md)] [!INCLUDE [fabric](../includes/applies-to-version/fabric.md)] [!INCLUDE [azure-data-explorer](../includes/applies-to-version/azure-data-explorer.md)]
+
+> [!NOTE]
+> GQL support is in preview. Features and syntax can change based on feedback and ongoing development.
+
+## Introduction
+
+This reference covers the fundamental concepts, functions, and operators of Graph Query Language (GQL). Graph Query Language (GQL) is built on mathematical graph theory concepts that provide a solid foundation for querying graph data. Understanding these fundamentals helps you write more effective queries and better understand how GQL processes your data. GQL also provides a rich set of functions and operators to work with graph patterns, nodes, edges, and properties.
+
+## Fundamental concepts
+
+This section covers the core concepts that form the foundation of graph data analysis with GQL.
+
+### Graph patterns
+
+Graph patterns are the core building blocks of GQL queries. They describe the structure you want to find in your graph data using a declarative syntax that mirrors the visual representation of graphs.
+
+#### Node patterns
+
+Node patterns specify how to match individual nodes in your graph:
+
+```gql
+(n) -- Any node
+(n:Person) -- Node with Person label
+(n:Person&City) -- Node with Person AND City label
+(:Person) -- Person node, don't bind variable
+```
+
+**Key concepts:**
+
+- **Variable binding**: `(n)` creates a variable `n` that you can reference later in the query
+- **Anonymous nodes**: `(:Person)` matches nodes without creating a variable
+- **Label filtering**: `:Person` restricts matches to nodes with the Person label
+- **Label combinations**: Use `&` for AND, `|` for OR operations
+
+#### Edge patterns
+
+Edge patterns define how nodes connect to each other:
+
+```gql
+-[e]-> -- Directed outgoing edge, any label
+-[e:works_at]-> -- Directed edge, works_at label
+-[e:knows|likes]-> -- knows OR likes edge
+<-[e]- -- Directed incoming edge
+-[e]- -- Undirected (any direction)
+```
+
+**Key concepts:**
+
+- **Direction**: `->` for outgoing, `<-` for incoming, `-` for any direction
+- **Edge types**: Use labels like `:works_at` to filter by relationship type
+- **Multiple types**: `knows|likes` matches either relationship type
+
+#### Label expressions
+
+Labels provide semantic meaning to nodes and edges. GQL supports complex label expressions:
+
+```gql
+:Person&Company -- Both Person AND Company labels
+:Person|Company -- Person OR Company labels
+:!Company -- NOT Company label
+:(Person|!Company)&City -- Complex expressions with parentheses
+```
+
+**Operators:**
+
+- `&` (AND): Node must have all specified labels
+- `|` (OR): Node must have at least one specified label
+- `!` (NOT): Node must not have the specified label
+- `()`: Parentheses for grouping complex expressions
+
+#### Path patterns
+
+Path patterns describe multi-hop relationships in your graph:
+
+```gql
+(a)-[e1]->(b)-[e2]->(c) -- 2-hop path
+(a)-[e]->{2,4}(b) -- 2 to 4 hops
+(a)-[e]->{1,}(b) -- 1 or more hops
+(a)-[:knows|likes]->{1,3}(b) -- 1-3 hops via knows/likes
+p=()-[:works_at]->() -- Binding a path variable
+```
+
+**Variable-length paths:**
+
+- `{2,4}`: Exactly 2 to 4 hops
+- `{1,}`: 1 or more hops (unbounded)
+- `{,3}`: Up to 3 hops
+- `{5}`: Exactly 5 hops
+
+**Path variables:**
+
+- `p=()->()`: Captures the entire path for later analysis
+- Access with `NODES(p)`, `RELATIONSHIPS(p)`, `PATH_LENGTH(p)`
+
+#### Multiple patterns
+
+GQL supports complex, non-linear graph structures:
+
+```gql
+(a)->(b), (a)->(c) -- Multiple edges from same node
+(a)->(b)<-(c), (b)->(d) -- Non-linear structures
+```
+
+**Pattern composition:**
+
+- Use commas `,` to separate multiple patterns
+- All patterns must match simultaneously
+- Variables can be shared across patterns
+
+### Match modes
+
+GQL supports different path matching modes that control how patterns are matched against graph data. These modes affect performance, result completeness, and the types of paths that are returned.
+
+Match modes control how graph elements can be reused across pattern variables within a single MATCH clause.
+
+#### DIFFERENT EDGES (default)
+
+The default mode in Kusto. A matched edge cannot bind to more than one edge variable, but nodes can be reused freely.
+
+```gql
+MATCH (a)-[r1]->(b)-[r2]->(c)
+-- r1 and r2 must be different edges
+-- a, b, c can be the same or different nodes
+```
+
+#### REPEATABLE ELEMENTS
+
+Allows both edges and nodes to be reused across pattern variables without restrictions.
+
+```gql
+MATCH REPEATABLE ELEMENTS (a)-[r1]->(b)-[r2]->(c)
+-- r1 and r2 can be the same edge
+-- a, b, c can be the same or different nodes
+```
+
+### Path modes
+
+Path modes control which types of paths are included in results based on repetition constraints.
+
+#### WALK (default)
+
+The default GQL path mode. Includes all possible paths with no restrictions on node or edge repetition.
+
+```gql
+MATCH WALK (a)-[]->{1,3}(b)
+-- Allows paths with repeating nodes and edges
+```
+
+#### TRAIL
+
+Filters out paths that have repeating edges. Nodes may repeat, but each edge can only appear once per path.
+
+```gql
+MATCH TRAIL (a)-[]->{1,3}(b)
+-- No edge can appear twice in the same path
+-- Nodes may repeat
+```
+
+#### ACYCLIC
+
+Filters out paths that have repeating nodes. Each node can only appear once per path.
+
+```gql
+MATCH ACYCLIC (a)-[]->{1,3}(b)
+-- No node can appear twice in the same path
+-- Prevents cycles entirely
+```
+
+#### SIMPLE
+
+Same as ACYCLIC but allows the first and last nodes in the path to be the same (forming a simple cycle).
+
+```gql
+MATCH SIMPLE (a)-[]->{1,3}(b)
+-- No node repetition except first/last can match
+-- Currently not supported in implementation
+```
+
+### Mode combinations and Kusto cycles parameter
+
+Different combinations of match modes and path modes map to specific Kusto `cycles` parameter values:
+
+#### DIFFERENT EDGES mode
+
+| Path Mode | Single Path | Multi-path ("star" pattern) |
+|-----------|-------------|----------------------------|
+| WALK | `cycles=unique_edges` | `cycles=unique_edges` (only if all paths are WALK/TRAIL) |
+| TRAIL | `cycles=unique_edges` | `cycles=unique_edges` (only if all paths are WALK/TRAIL) |
+| ACYCLIC | `cycles=none` | Not supported |
+| SIMPLE | Not supported | Not supported |
+
+#### REPEATABLE ELEMENTS mode
+
+| Path Mode | Single Path | Multi-path ("star" pattern) |
+|-----------|-------------|----------------------------|
+| WALK | `cycles=all` | `cycles=all` (only if all paths are WALK) |
+| TRAIL | `cycles=unique_edges` | Not supported |
+| ACYCLIC | `cycles=none` | Not supported |
+| SIMPLE | Not supported | Not supported |
+
+#### Syntax examples
+
+##### Basic path modes
+
+```gql
+-- Default (WALK with DIFFERENT EDGES)
+MATCH (n)-[]->(m)
+RETURN n, m
+
+-- Explicit WALK mode
+MATCH WALK (n)-[]->(m)
+RETURN n, m
+
+-- TRAIL mode - no repeating edges
+MATCH TRAIL (n)-[]->{1,3}(m)
+RETURN n, m
+
+-- ACYCLIC mode - no repeating nodes
+MATCH ACYCLIC (n)-[]->{1,3}(m)
+RETURN n, m
+```
+
+##### Match mode combinations
+
+```gql
+-- DIFFERENT EDGES with WALK (default)
+MATCH DIFFERENT EDGES WALK (n)-[]->(m)
+RETURN n, m
+
+-- REPEATABLE ELEMENTS with WALK
+MATCH REPEATABLE ELEMENTS WALK (n)-[]->(m)
+RETURN n, m
+
+-- REPEATABLE ELEMENTS with TRAIL
+MATCH REPEATABLE ELEMENTS TRAIL (n)-[]->(m)
+RETURN n, m
+```
+
+##### Multi-pattern queries
+
+```gql
+-- Multiple WALK patterns (star pattern)
+MATCH WALK (n)-[]->(a), WALK (n)-[]->(b)
+RETURN n, a, b
+
+-- Mixed path modes (both must be WALK/TRAIL for multi-path)
+MATCH WALK (n)-[]->(a), TRAIL (n)-[]->(b)
+RETURN n, a, b
+```
+
+## Functions and operators reference
+
+Graph Query Language (GQL) provides a rich set of functions and operators to work with graph patterns, nodes, edges, and properties.
+
+### Core GQL functions and operators
+
+This table lists the core GQL functions and operators, along with their Kusto Query Language (KQL) equivalents and examples.
+
+| GQL Function/Operator | Description | GQL Example |
+|---|---|---|
+| `MATCH` | Find graph patterns | `MATCH (a)-[r]->(b)` |
+| `OPTIONAL MATCH` | Find patterns that might not exist | `OPTIONAL MATCH (p)->(c:City)` |
+| `WHERE` | Filter patterns and properties | `WHERE person.age > 25` |
+| `FILTER` | Equivalent to WHERE but used without MATCH clauses | `FILTER p.name = 'Carol' OR c.name = 'Seattle'` |
+| `IS NULL` | Check for null values | `WHERE person.age IS NULL` |
+| `IS NOT NULL` | Check for non-null values | `WHERE person.age IS NOT NULL` |
+| `RETURN` | Project results | `RETURN person.name, person.age` |
+| `DISTINCT` | Return unique values | `RETURN DISTINCT person.name` |
+| `COUNT(*)` | Count all rows | `RETURN COUNT(*)` |
+| `COUNT()` | Count non-null values | `RETURN COUNT(person.name)` |
+| `SUM()` | Sum numeric values | `RETURN SUM(person.age)` |
+| `MIN()` | Minimum value | `RETURN MIN(person.age)` |
+| `MAX()` | Maximum value | `RETURN MAX(person.age)` |
+| `AVG()` | Average value | `RETURN AVG(person.age)` |
+| `COLLECT_LIST()` | Collect values into array | `RETURN COLLECT_LIST(person.name)` |
+| `SIZE()` | Array length | `RETURN SIZE(COLLECT_LIST(n.firstName))` |
+| `Labels()` | Show labels for a node or edge | `RETURN labels(entity)` |
+| `UPPER()` | Convert to uppercase | `RETURN UPPER(person.name)` |
+| `LOWER()` | Convert to lowercase | `RETURN LOWER(person.name)` |
+| `LEFT()` | Extract left substring | `WHERE LEFT(person.name, 3) = 'Tom'` |
+| `RIGHT()` | Extract right substring | `WHERE RIGHT(person.name, 5) = 'Hanks'` |
+| `STARTS WITH` | String starts with pattern | `WHERE person.name STARTS WITH 'Tom'` |
+| `ENDS WITH` | String ends with pattern | `WHERE person.name ENDS WITH 'Hanks'` |
+| `CONTAINS` | String contains pattern | `WHERE person.name CONTAINS 'Tom'` |
+| `\|\|` | String concatenation | `RETURN n.firstName \|\| ' ' \|\| n.lastName` |
+| `TRIM()` | Remove whitespace from both ends | `RETURN TRIM(' abc ')` |
+| `BTRIM()` | Remove whitespace from both ends (alias for TRIM) | `RETURN BTRIM(' abc ')` |
+| `LTRIM()` | Remove whitespace from left end | `RETURN LTRIM(' abc ')` |
+| `RTRIM()` | Remove whitespace from right end | `RETURN RTRIM(' abc ')` |
+| `TRIM(BOTH FROM)` | Remove whitespace from both ends (explicit) | `RETURN TRIM(BOTH FROM ' abc ')` |
+| `TRIM(LEADING FROM)` | Remove whitespace from left end (explicit) | `RETURN TRIM(LEADING FROM ' abc')` |
+| `TRIM(TRAILING FROM)` | Remove whitespace from right end (explicit) | `RETURN TRIM(TRAILING FROM 'abc ')` |
+| `TRIM(chars FROM)` | Remove specified characters from both ends | `RETURN TRIM('_' FROM '_abc_')` |
+| `TRIM(BOTH chars FROM)` | Remove specified characters from both ends (explicit) | `RETURN TRIM(BOTH '_' FROM '_abc_')` |
+| `TRIM(LEADING chars FROM)` | Remove specified characters from left end | `RETURN TRIM(LEADING 'd' FROM 'dddabc')` |
+| `TRIM(TRAILING chars FROM)` | Remove specified characters from right end | `RETURN TRIM(TRAILING 't' FROM 'abcttt')` |
+| `STRING_JOIN()` | Join array elements with delimiter | `RETURN STRING_JOIN(["a", "b" \|\| "c"], "-")` |
+| `CAST()` | Convert data types | `CAST(person.age AS STRING)` |
+| `ZONED_DATETIME()` | Create datetime from string | `ZONED_DATETIME('2024-01-01')` |
+| `CURRENT_TIMESTAMP` | Current timestamp | `WHERE created < CURRENT_TIMESTAMP` |
+| `DURATION()` | Create timespan from time units (days to nanoseconds) | `DURATION({days: 3, hours: 2})` |
+| `DURATION_BETWEEN()` | Calculate duration between two datetime values | `DURATION_BETWEEN(start_time, end_time)` |
+| `NODES()` | Extract nodes from a path | `RETURN NODES(path_variable)` |
+| `RELATIONSHIPS()` | Extract edges from a path | `RETURN RELATIONSHIPS(path_variable)` |
+| `PATH_LENGTH()` | Get the length of a path | `RETURN PATH_LENGTH(path_variable)` |
+| `ORDER BY` | Sort results | `ORDER BY person.age DESC` |
+| `LIMIT` | Limit result count | `LIMIT 10` |
+| `&` (AND) | Label intersection | `MATCH (p:Person & Male)` |
+| `\|` (OR) | Label union | `MATCH (n:Person \| Movie)` |
+| `!` (NOT) | Label negation | `MATCH (p:!Female)` |
+
+### Best practices
+
+- KQL supports dynamic types, but GQL does not clearly define how these should be handled. To avoid runtime errors, explicitly cast nested fields to their expected type (see `CAST`).
+
+- Because GQL runs on KQL, some operations inherit KQL semantics. For instance, `COLLECT_LIST` (the GQL equivalent of `make_list`) will flatten arrays if the field is already an array. If results differ from expectations, consult the KQL documentation for the equivalent function.
+
+### Performance optimization
+
+Use these strategies to optimize GQL query performance in production environments:
+
+> [!TIP]
+> Start with simple patterns, then increase complexity if needed. Monitor query performance, and adjust path lengths and filters to improve results.
+
+**Limit path matching scope**:
+
+- Use specific label filters to reduce the search space: `MATCH (start:SpecificType)` instead of `MATCH (start)`
+- Limit variable length paths with reasonable bounds: `MATCH (a)-[]->{1,3}(b)` instead of unbounded paths
+- Apply `WHERE` clauses early to filter results before expensive operations.
+
+**Use COUNT(*) for existence checks**:
+
+If you only need to check if a pattern exists, use `COUNT(*)` instead of returning full results.
+
+```gql
+MATCH (user:User)-[:SUSPICIOUS_ACTIVITY]->(target)
+WHERE user.id = 'user123'
+RETURN COUNT(*) > 0 AS HasSuspiciousActivity
+```
+
+### Limitations
+
+- **Reserved keywords**: Some GQL keywords can't be used as identifiers in queries. Some reserved keywords aren't immediately obvious (for example, `DATE` is a reserved keyword). If your graph data has property names that conflict with GQL reserved keywords, use different property names in your graph schema or rename them to avoid parsing conflicts.
+
+ > [!IMPORTANT]
+ > When you design your graph schema, some common property names might conflict with GQL reserved keywords. Avoid or rename these property names.
+
+- **No `INSERT`/`CREATE` support**: Operations to change graph structures are not supported. Instead, use KQL for all graph creation, change, and management tasks.
+
+- **Optional matches**: Supported only for node patterns (not edges).
+
+- **Entity equivalence checks not supported**: GQL's`(MATCH (n)-[]-(n2) WHERE n1 <> n2)` is not supported. Use explicit field comparisons instead, for example, `n.id <> n2.id`.
+
+- **Time and timezone**: The engine operates in UTC. Datetime literals must use zoned datetime; only the UTC zone is supported via `ZONED_DATETIME("2011-12-31 23:59:59.9")`.
+
+- **Duration granularity**: Durations support up to days and smaller units down to nanoseconds. Larger-than-day units (for example, weeks, months, years) aren't supported.
+
+- **Traversal modes**: GQL defines configurable traversal modes for matching and paths. For `MATCH`, the modes are `'DIFFERENT EDGES'` and `'REPEATABLE EDGES'`; for `PATH`, the modes are `'WALK'`, `'TRAIL'`, `'ACYCLIC'`, and `'SIMPLE'`. The current implementation defaults to `'DIFFERENT EDGES'` and `'WALK'`, respectively.
+Some combinations are not supported.
+
+### Labels() custom GQL function
+
+The `labels()` function shows the labels for a node or edge as an array.
+
+**Syntax:**
+
+`labels(`*entity*`)
+
+**Parameters:**
+
+- `entity`: A node or edge variable from a matched pattern.
+
+**Returns:**
+
+Returns an array of strings with all labels for the specified entity.
+
+**Examples:**
+
+Show labels for matched nodes:
+
+```gql
+MATCH (entity)
+RETURN entity.name, labels(entity)
+```
+
+#### Output
+
+This query shows the name and all labels for each node in the graph.
+
+| entity.name | labels(entity) |
+|--|--|
+| john.doe | ["User"] |
+| admin.user | ["User"] |
+| web-server | ["System"] |
+| database | ["System"] |
+| domain-controller | ["System"] |
+
+Show labels in projections with aliases:
+
+```gql
+MATCH (n)-[e]->(target)
+RETURN n.name, labels(n) AS n_labels, labels(e) AS edge_labels, target.name
+```
+
+This query shows node names, their labels, and the labels of connecting edges.
+
+| n.name | n_labels | edge_labels | target.name |
+|--|--|--|--|
+| john.doe | ["User"] | ["CAN_ACCESS"] | web-server |
+| admin.user | ["User"] | ["CAN_ACCESS"] | domain-controller |
+| web-server | ["System"] | ["CAN_ACCESS"] | database |
+| domain-controller | ["System"] | ["CAN_ACCESS"] | database |
+
+## Related content
+
+- [Graph Query Language (GQL) overview](graph-query-language.md)
+- [GQL query patterns, examples, and use cases](graph-query-language-use-cases.md)
+- [Graph operators overview](graph-operators.md)
+- [make-graph operator](make-graph-operator.md)
+- [graph-match operator](graph-match-operator.md)
+- [Tutorial: Create your first graph](tutorials/your-first-graph.md)
+- [Graph functions reference](graph-function.md)
diff --git a/data-explorer/kusto/query/graph-query-language-use-cases.md b/data-explorer/kusto/query/graph-query-language-use-cases.md
new file mode 100644
index 0000000000..16c18ba172
--- /dev/null
+++ b/data-explorer/kusto/query/graph-query-language-use-cases.md
@@ -0,0 +1,764 @@
+---
+title: GQL Query Patterns, Examples, and Use Cases
+description: GQL query patterns, examples, and common use case scenarios. Find out how to use MATCH, WHERE, and RETURN clauses to analyze graph relationships. GQL use cases alongside KQL. See how to build queries for security, social networks, and organizational analysis with step-by-step examples.
+ms.reviewer: herauch
+ms.topic: reference
+ms.date: 08/24/2025
+
+#CustomerIntent: As a , I want so that .
+---
+# GQL query patterns, examples, and common scenarios (preview)
+
+This article gives Graph Query Language examples focusing on core query patterns, and shows common real world use cases for GQL using realistic graph schemas and queries.
+
+The following examples show the GQL syntax supported, from simple to complex patterns.
+
+> [!NOTE]
+>
+> Before you try these examples, set up your environment to use GQL. See [Getting Started](graph-query-language.md#getting-started) for details. Ensure you set the client request properties to use GQL, and set the graph reference function to your graph data source.
+>
+> GQL support is in **preview**. Features and syntax can change based on feedback and ongoing development.
+
+## Core GQL query patterns
+
+[MATCH](#basic-pattern-matching-examples)
+
+* [WHERE](#filter-by-properties-with-where): Use standard comparison and logical operators similar to KQL `where` clauses. `WHERE` clauses filter patterns based on node and edge properties. They work like KQL or SQL WHERE clauses but operate on graph patterns.
+
+* [RETURN](#return-specific-properties)): Use `RETURN` statements to project results from matched patterns. They specify what data to output from your graph query.
+
+* [Advanced patterns](#advanced-patterns-examples)
+
+* [Complex multi-pattern queries](#complex-multi-pattern-examples)
+
+* [Temporal data analysis with DURATION functions](#temporal-data-analysis-with-duration-functions)
+
+For a full list of supported GQL functions and operators, see [Graph Query Language (GQL) reference](graph-query-language-reference.md).
+
+## Basic pattern matching examples
+
+### Basic pattern matching without variables
+
+The simplest pattern matches any relationship without referencing the matched values.
+
+
+```gql
+MATCH ()-[]-()
+RETURN COUNT(*)
+```
+
+This query finds all relationships in the graph. The empty parentheses `()` represent anonymous nodes, and `[]` represents anonymous edges. Because we don't assign variables, we can only count the matches but can't access their properties.
+
+### Pattern matching with variables
+
+To access the matched nodes and edges, assign them to variables.
+
+
+```gql
+MATCH (n)-[e]->(n2)
+RETURN COUNT(*)
+```
+
+`n` represents the source node, `e` represents the _directed_ edge, and `n2` represents the target node. You reference these variables to access properties, but in this example you're still just counting matches.
+
+### Access node properties
+
+Once you have variables, you access properties of matched nodes.
+
+
+```gql
+MATCH (person)-[e]->(target)
+RETURN person.name, target.name, e.lbl
+ORDER BY person.name
+LIMIT 2
+```
+
+**Output**
+
+This query returns the names of connected entities and the type of relationship between them, ordered by person.name and limited to two results to manage output size.
+Although those entities are named `person` and `target`, there's no restriction that ensures they're actually a person.
+
+| person.name | target.name | e.lbl |
+|-------------|-------------|------------------|
+| Alice | TechCorp | works_at |
+| Alice | Seattle | located_at |
+
+### Filter by node labels
+
+Use labels to match specific types of nodes. Specify labels with a colon after the variable name.
+
+
+```gql
+MATCH (person:Person)
+RETURN person.name
+ORDER BY person.name
+LIMIT 5
+```
+
+**Output**
+
+This query matches only nodes with the "Person" label and returns their names, limited to five results to avoid large result sets.
+
+| person.name |
+|-------------|
+| Alice |
+| Bob |
+| Carol |
+| David |
+| Emma |
+
+### Filter by edge labels
+
+
+```gql
+MATCH (person:Person)-[works:works_at]->(company:Company)
+RETURN person.name, company.name
+```
+
+### Filter by edge labels without variables
+
+For this example, switch back to the original G_Doc_Transient() graph, which has the "knows" relationship:
+
+
+```gql
+#crp query_graph_reference=G_Doc_Transient()
+```
+
+Filter by edge labels without assigning the edge to a variable when you don't need to access its properties.
+
+
+```gql
+MATCH (p1:Person)-[:knows]->(p2:Person)
+RETURN p1.name, p2.name
+ORDER BY p1.name
+```
+
+**Output**
+
+This query finds Person nodes connected to other Person nodes through "knows" relationships. The edge is filtered by its label but isn't assigned to a variable because you only need the connected nodes.
+
+| p1.name | p2.name |
+|----------|----------|
+| Alice | Bob |
+| Bob | Carol |
+| Carol | David |
+| David | Emma |
+
+### Filter by properties with WHERE
+
+Use WHERE clauses to filter based on property values.
+
+
+```gql
+MATCH (person:Person)
+WHERE person.properties.age > 25
+RETURN person.name, person.properties.age
+```
+
+This query finds people over 25 years old and returns their names and ages. The WHERE clause filters the matched nodes by the age property.
+
+### Inline property filters
+
+Filter by properties directly in the pattern using inline conditions.
+
+
+```gql
+MATCH (person:Person {name: 'Bob'})
+RETURN person.properties.age
+```
+
+This query finds the person named "Bob" and returns their age. The `{name: 'Bob'}` syntax filters nodes where the name property equals 'Bob'.
+
+### Multiple inline conditions
+
+Specify multiple property conditions inline.
+
+
+```gql
+MATCH (person:Person {name: 'Bob'})
+WHERE person.properties.age = 30
+RETURN person as Bob
+```
+
+This query finds the person with both the specified name and age. It returns the person node as "Bob."
+
+### Variable length paths
+
+Use variable length path patterns with quantifiers for multi-hop relationships:
+
+
+```gql
+MATCH (s)-[]->{1,3}(e)
+RETURN s.name, e.name
+```
+
+This query finds paths with 1 to 3 hops. The `{1,3}` quantifier sets the minimum and maximum path length.
+
+### Unbounded variable length paths
+
+Specify open ranges for paths of variable length:
+
+
+```gql
+MATCH (center)-[]->{1,}(connected)
+WHERE center.name = 'Alice'
+RETURN DISTINCT connected.name
+```
+
+This query finds all nodes you can reach from Alice through paths of one or more hops. The `{1,}` quantifier means "one or more hops."
+
+### Path functions
+
+Use path functions to extract information about the matched paths, including the nodes and relationships that make up the path:
+
+
+```gql
+MATCH fof = (person)-[:knows]->{2,2}()
+RETURN fof, NODES(fof) AS NodesOfPath, RELATIONSHIPS(fof) AS EdgesOfPath
+LIMIT 1
+```
+
+This query finds paths exactly 2 hops long using the "knows" relationship. The path variable `fof` contains the entire path as an array of alternating nodes and edges. The `NODES()` function extracts just the nodes from the path, and `RELATIONSHIPS()` extracts just the edges.
+
+**Output**
+
+| fof | NodesOfPath | EdgesOfPath |
+|-----|-------------|-------------|
+| `[{"id": "p3", "lbl": "Person", "name": "Carol", "properties": {"age": 28}, "$labels": ["Person"]}, {"source": "p3", "target": "p4", "lbl": "knows", "since": 2022, "$labels": ["knows"]}, {"id": "p4", "lbl": "Person", "name": "David", "properties": {"age": 35}, "$labels": ["Person"]}, {"source": "p4", "target": "p5", "lbl": "knows", "since": 2023, "$labels": ["knows"]}, {"id": "p5", "lbl": "Person", "name": "Emma", "properties": {"age": 26}, "$labels": ["Person"]}]` | `[{"id": "p3", "lbl": "Person", "name": "Carol", "properties": {"age": 28}, "$labels": ["Person"]}, {"id": "p4", "lbl": "Person", "name": "David", "properties": {"age": 35}, "$labels": ["Person"]}, {"id": "p5", "lbl": "Person", "name": "Emma", "properties": {"age": 26}, "$labels": ["Person"]}]` | `[{"source": "p3", "target": "p4", "lbl": "knows", "since": 2022, "$labels": ["knows"]}, {"source": "p4", "target": "p5", "lbl": "knows", "since": 2023, "$labels": ["knows"]}]` |
+
+## Basic property filtering
+
+Filter nodes based on a single property condition:
+
+
+```gql
+MATCH (person:Person)
+WHERE person.properties.age > 26
+RETURN person.name, person.properties.age
+ORDER BY person.name
+```
+
+**Output**
+
+This query finds all `Person` nodes where the `age` property is greater than 26.
+
+| person.name | person.properties.age |
+|--------------|-------------|
+| Bob | 30 |
+| Carol | 28 |
+| David | 35 |
+
+### Range filtering with AND
+
+To create a range, combine multiple conditions:
+
+
+```gql
+MATCH (person:Person)
+WHERE person.properties.age >= 28 AND person.properties.age <= 35
+RETURN person.name, person.properties.age
+```
+
+This query shows people whose ages are between 28 and 35, inclusive.
+
+### Edge property filtering
+
+Filter by edge properties to find specific types of relationships.
+
+
+```gql
+MATCH (person:Person)-[wa:works_at]->(c:Company)
+WHERE wa.since >= 2022
+RETURN person.name, c.name, wa.since
+```
+
+This query shows people who start working at companies in 2022 or later. It filters by the `since` property of the `works_at` relationship.
+
+### String pattern matching
+
+Use string functions to match text patterns.
+
+
+```gql
+MATCH (person:Person)
+WHERE person.name STARTS WITH 'Al'
+RETURN COUNT(*)
+```
+
+This query counts people whose names start with 'Al'. It provides a quick summary without returning a large result set.
+
+### String contains matching
+
+Check if a string has a specific substring.
+
+
+```gql
+MATCH (person:Person)
+WHERE person.name CONTAINS 'i'
+RETURN person.name
+```
+
+This query finds people whose names have 'i' anywhere in the string. This match is case sensitive.
+
+### Inequality comparisons
+
+Use comparison operators to exclude specific values:
+
+
+```gql
+MATCH (person:Person)-[wa:works_at]->(company:Company)
+WHERE person.properties.age > 25 AND company.name <> 'TechCorp'
+RETURN person.name, company.name
+```
+
+This query shows people over 25 who work at companies other than 'TechCorp'.
+
+### Null value checking
+
+Check for the presence or absence of property values.
+
+
+```gql
+MATCH (person:Person)
+WHERE person.properties.age IS NOT NULL
+RETURN person.name, person.properties.age
+```
+
+This query finds all people who have an age recorded (non-null age property).
+
+### Logical OR operations
+
+Use OR to match multiple conditions
+
+
+```gql
+MATCH (person:Person)
+WHERE person.properties.age > 30 OR person.name CONTAINS 'a'
+RETURN person.name, person.properties.age
+```
+
+This query finds people who are over 30 years old or have 'a' in their name.
+
+### Return specific properties
+
+Return individual properties from matched nodes.
+
+
+```gql
+MATCH (person:Person)
+RETURN person.name, person.properties.age
+```
+
+This query returns the name and age properties for each Person node. Each row in the result shows these two values.
+
+### Return with aliases
+
+Use aliases to rename output columns for clarity.
+
+
+```gql
+MATCH (person:Person)-[employment:works_at]->(company:Company)
+RETURN person.name AS Employee, company.name AS Company, employment.since AS WorkingSince
+```
+
+This query returns employee names, company names, and employment start dates with descriptive column headers.
+
+### Return property bags (JSON objects)
+
+Create custom JSON objects by combining multiple properties in the RETURN clause.
+
+
+```gql
+MATCH (person:Person)
+RETURN {age: person.properties.age, name: person.name} AS myPropertyBag
+LIMIT 1
+```
+
+**Output**
+
+This query creates a custom JSON object (property bag) containing selected properties from matched Person nodes. The property bag combines the person's age and name into a single structured object.
+
+| myPropertyBag |
+|---|
+| { "age": 28, "name": "Carol" } |
+
+### Return entire nodes and edges
+
+
+```gql
+MATCH (person:Person)-[e]->(company:Company)
+WHERE person.name = 'Alice'
+RETURN person, e, company
+```
+
+**Output**
+
+This query returns the complete node and edge objects for Alice's relationship with companies, including all properties.
+
+| person | e | company |
+|--------|------|-------|
+{"id":"p1","lbl":"Person","name":"Alice","properties":{"age": 25}} |{"source":"p1","target":"c1","lbl":"works_at","since":2020} | {"id":"c1","lbl":"Company","name":"TechCorp","properties":{}}
+
+### Count matching patterns
+
+Use COUNT(*) to count the number of pattern matches.
+
+
+```gql
+MATCH (person:Person)-[:likes]->(p2:Person)
+RETURN person.name, COUNT(*) AS LikesGiven
+```
+
+This query counts how many people each person likes and groups the results by person name.
+
+### Aggregate with MIN and MAX
+
+Find minimum and maximum values across all matches.
+
+
+```gql
+MATCH (person:Person)
+RETURN MIN(cast(person.properties.age as int)) AS Youngest, MAX(cast(person.properties.age as int)) AS Oldest
+```
+
+This query finds the youngest and oldest ages among all people in the graph.
+
+### Collect values into arrays
+
+Use COLLECT_LIST to gather multiple values into arrays.
+
+
+```gql
+MATCH (person:Person)
+RETURN COLLECT_LIST(person.name) AS AllNames
+```
+
+This query collects all person names into a single array.
+
+### Return distinct values
+
+Remove duplicates from your results.
+
+
+```gql
+MATCH (person:Person)-[]->(company:Company)
+RETURN DISTINCT company.name
+```
+
+This query returns each unique company name only once, even if multiple people are connected to the same company.
+
+### Return ordered results
+
+Sort your results using ORDER BY.
+
+
+```gql
+MATCH (person:Person)
+RETURN person.name, person.properties.age
+ORDER BY cast(person.properties.age as int) DESC
+```
+
+This query returns people sorted by age in descending order, oldest first.
+
+### Limit result count
+
+Restrict the number of results returned.
+
+
+```gql
+MATCH (person:Person)
+WHERE person.properties.age > 25
+RETURN person.name
+ORDER BY cast(person.properties.age as int)
+LIMIT 5
+```
+
+This query returns only the first five people over 25 years old, ordered by age.
+
+**Comparable KQL:** Similar to KQL `project`, `summarize`, `sort`, and `take` operators.
+
+## Advanced patterns examples
+
+Advanced patterns let you match complex graph structures and label combinations.
+
+### Label unions (OR)
+
+Match nodes that have any of the specified labels:
+
+
+```gql
+MATCH (entity:Person | Company)
+RETURN entity
+```
+
+This query matches nodes that have either the "Person" or "Company" label. The pipe symbol `|` means logical OR for labels.
+
+### Label intersections (AND)
+
+Match nodes that have all of the specified labels:
+
+
+```gql
+MATCH (person:Person & Male)
+RETURN person.name
+```
+
+This query matches nodes that have both the "Person" and "Male" labels. The ampersand `&` means logical AND for labels.
+
+### Label negation (NOT)
+
+Match nodes that do NOT have the specified label:
+
+
+```gql
+MATCH (person:!Female)
+RETURN person.name
+```
+
+This query matches all nodes that don't have the "Female" label. The exclamation mark `!` means logical NOT for labels.
+
+### Variable length paths with exact range
+
+Match paths with a specific number of hops:
+
+
+```gql
+MATCH (s)-[es]->{2,2}(e)
+RETURN s, es, e
+```
+
+This query finds paths that are exactly two hops long. The `{2,2}` quantifier sets both minimum and maximum path length to 2.
+
+### Variable length paths with open range
+
+Match paths with a minimum number of hops but no maximum:
+
+
+```gql
+MATCH (s)-[p]->{1,}(e)
+RETURN s, e, p
+```
+
+This query finds paths that are one or more hops long. The `{1,}` quantifier means one or more hops with no upper limit.
+
+### Zero-length paths
+
+Match nodes with themselves (identity relationships):
+
+
+```gql
+MATCH (n)-[]->{0,0}(same_n)
+RETURN n
+```
+
+This query matches each node with itself through a zero-length path. The `{0,0}` quantifier sets the path length to zero, so each node is paired with itself.
+
+### Named path variables
+
+Assign entire paths to variables for later use:
+
+
+```gql
+MATCH p = (person:Person)-[:works_at]->(company:Company)
+RETURN p
+```
+
+This query assigns the entire pattern to the variable `p`, which you can return or use in other parts of the query. The path variable has the complete sequence of nodes and edges.
+
+### Optional matching
+
+Use `OPTIONAL MATCH` to find patterns that might not exist, returning empty values when patterns don't match:
+
+
+```gql
+MATCH (p:Person)
+ WHERE p.properties.age < 30
+OPTIONAL MATCH (p)->(c:City)
+ WHERE c.name <> 'Seattle'
+RETURN p.name as Person, c.name as City
+```
+
+This query finds all `Person` nodes where age is less than 30, and optionally matches cities they're connected to (excluding Seattle). If a person isn't connected to any city (or only to Seattle), the City column returns empty, but the person is still included in the results.
+
+**Output**
+
+The optional match ensures that people without city connections are still returned:
+
+|Person|City|
+|---|---|
+|Carol|Portland|
+|Emma|Portland|
+|Alice||
+
+
+
+### Multi-hop named paths
+
+Create named paths that span multiple relationships:
+
+
+```gql
+MATCH full_path = (s)-[first_edge]->(middle)-[second_edge]->(e)
+RETURN full_path, s.name, e.name
+```
+
+This query creates a named path variable `full_path` that captures a two-hop pattern and returns specific properties from the s and e nodes.
+
+**Comparable KQL:** Uses advanced `graph-match` operator features for complex pattern matching.
+
+## Complex multi-pattern examples
+
+### Cross product filtering with FILTER
+
+When using multiple separate `MATCH` patterns, GQL creates a cross product of all matching combinations. The `FILTER` keyword allows you to filter this cross product based on conditions involving variables from different patterns.
+
+This example shows how to filter the cross product of persons and cities:
+
+
+```gql
+MATCH (p:Person)
+MATCH (c:City)
+FILTER p.name = 'Carol' or c.name = 'Seattle'
+RETURN p.name as Person, c.name as City
+LIMIT 2
+```
+
+**Output**
+
+| Person | City |
+|---|---|
+| Carol | Portland |
+| David | Seattle |
+
+This query demonstrates how the `FILTER` keyword operates on the cross product of all Person nodes and all City nodes, returning only the combinations where either the person's name is 'Carol' or the city's name is 'Seattle'.
+
+### Cross-town *likes* with company filter
+
+This example combines multiple patterns and filters in one statement:
+
+1. Find a pair of people where **p1 _likes_ p2**.
+1. Link each person to their home city. p1's city name must **start with "San"**, and p2 must live in a **different city**.
+1. Make sure p2 **works at TechCorp**.
+1. Only include pairs where the two people have **different ages**.
+
+The first `RETURN` shows every qualifying match—both people, their cities, and the company—so you can review the raw results.
+The second `RETURN` aggregates all matches to output a single value: the **average age of all "liked" people** (p2) who meet these criteria.
+
+
+```gql
+MATCH (p1:Person)-[:likes]->(p2:Person),
+ (p1)-[:located_at]->(home:City),
+ (p2)-[:located_at]->(home2:City),
+ (p2)-[:works_at]->(work:Company {name: 'TechCorp'})
+WHERE cast(p1.properties.age as int) <> cast(p2.properties.age as int) and home.name <> home2.name and home.name starts with 'San'
+RETURN p1.name, p2.name, home.name, work.name, home2.name
+```
+
+**Output**
+
+| p1.name | p2.name | home.name | work.name | home2.name |
+|---------|---------|-----------|-----------|------------|
+| David | Alice | San Francisco | TechCorp | Seattle |
+
+
+```gql
+MATCH (p1:Person)-[:likes]->(p2:Person),
+ (p1)-[:located_at]->(home:City),
+ (p2)-[:located_at]->(home2:City),
+ (p2)-[:works_at]->(work:Company {name: 'TechCorp'})
+WHERE cast(p1.properties.age as int) <> cast(p2.properties.age as int) and home.name <> home2.name and home.name starts with 'San'
+RETURN AVG(cast(p2.properties.age as int)) AS AvgAgeLikedAcrossTowns
+```
+
+**Output**
+
+| AvgAgeLikedAcrossTowns |
+|---------------------|
+| 25 |
+
+This example shows how GQL lets you express complex multi-pattern queries with cross-variable filtering, inline property matching, string pattern matching, and aggregation—all in one readable statement.
+
+## Temporal data analysis with DURATION functions
+
+GQL provides comprehensive support for temporal data analysis using duration functions. These functions enable you to perform time-based filtering, calculations, and comparisons on graph data with timestamps.
+
+### Supported duration units
+
+The `DURATION()` function supports a wide range of time units with flexible, case-insensitive syntax and returns a `timespan` object:
+
+| Time Unit | Supported Names | Example | Timespan Output |
+|-----------|----------------|---------|-----------------|
+| **Days** | `days`, `day` | `DURATION({days: 7})` | `7.00:00:00` |
+| **Hours** | `hours`, `hour`, `HOURS` | `DURATION({hours: 24})` | `1.00:00:00` |
+| **Minutes** | `minutes`, `minute`, `MINUTES` | `DURATION({minutes: 30})` | `00:30:00` |
+| **Seconds** | `seconds`, `second`, `SECONDS`, `secOnd` | `DURATION({seconds: 45})` | `00:00:45` |
+| **Milliseconds** | `milliseconds`, `millisecond` | `DURATION({milliseconds: 500})` | `00:00:00.5000000` |
+| **Microseconds** | `microseconds`, `microsecond`, `micRosecond` | `DURATION({microseconds: 1000})` | `00:00:00.0010000` |
+| **Nanoseconds** | `nanoseconds`, `nanosecond`, `nanoSecond` | `DURATION({nanoseconds: 1000000})` | `00:00:00.0010000` |
+
+You can combine multiple units in a single duration object: `DURATION({days: 1.8, minutes: 8, seconds: 7})` returns `1.00:08:07`.
+
+### Complex temporal boundary analysis
+
+Combine duration functions with timestamp arithmetic for precise temporal filtering:
+
+
+```gql
+MATCH (system:System)-[event:generated]->(alert:Alert)
+WHERE event.event_timestamp <= zoned_datetime("2012-01-01 08:00:00.0") + DURATION({days: 3})
+ AND event.event_timestamp > zoned_datetime("2012-01-01 08:00:00.0") + DURATION({minutes: 1})
+RETURN
+ system.name,
+ alert.severity,
+ event.event_timestamp,
+ DURATION_BETWEEN(zoned_datetime("2012-01-01 08:00:00.0"), event.event_timestamp) AS time_since_baseline
+ORDER BY event.event_timestamp
+```
+
+This query finds alerts generated within a specific time window (between 1 minute and 3 days after a baseline date) and calculates the duration between the baseline and each event.
+
+**Output**
+
+| system.name | alert.severity | event_timestamp | time_since_baseline |
+|-------------|----------------|-----------------|---------------------|
+| WebServer01 | Warning | 2012-01-01T08:01:30.0Z | 00:01:30 |
+| Database02 | Critical | 2012-01-02T20:15:45.0Z | 1.12:15:45 |
+| LoadBalancer | Info | 2012-01-04T16:30:00.0Z | 3.08:30:00 |
+
+## Social networks use case: Friend recommendations
+
+Social media platforms use GQL to suggest potential friends based on mutual relationships.
+We will be using the LDBC Social Network Benchmark dataset (see [GQL Sample Data](graph-sample-data.md#ldbc-snb-interactive)).
+
+1. Set up the graph reference to point to the LDBC dataset.
+
+
+ ```gql
+ #crp query_graph_reference=graph('LDBC_SNB_Interactive')
+ ```
+
+1. Find potential friends through mutual connections.
+
+
+ ```gql
+ MATCH (p1:PERSON {firstName: 'Karl', lastName: 'Muller'})-[:KNOWS]-(p2:PERSON)-[:KNOWS]-(p3:PERSON),
+ (p1)-[:IS_LOCATED_IN]-(c1:PLACE),
+ (p3)-[:IS_LOCATED_IN]-(c1)
+ WHERE p1.id <> p3.id
+ RETURN DISTINCT p3.firstName, p3.lastName
+ ```
+
+**Output**
+
+This query suggests friends for Karl who have mutual connections and live in the same location.
+
+| p3.firstName | p3.lastName |
+|---------------|--------------|
+| Alfred | Hoffmann |
+| Hans | Becker |
+| Wilhelm | Muller |
+
+## Related topics
+
+* [Graph Query Language (GQL)](graph-query-language.md)
+* [Graph Query Language (GQL) reference](graph-query-language-reference.md)
diff --git a/data-explorer/kusto/query/graph-query-language.md b/data-explorer/kusto/query/graph-query-language.md
new file mode 100644
index 0000000000..9bbef1b498
--- /dev/null
+++ b/data-explorer/kusto/query/graph-query-language.md
@@ -0,0 +1,171 @@
+---
+title: GQL Graph Query Language (preview)
+description: This article describes Graph Query Language (GQL)
+ms.reviewer: herauch
+ms.topic: reference
+ms.date: 08/24/2025
+---
+# Graph Query Language (GQL) (preview)
+
+> [!INCLUDE [applies](../includes/applies-to-version/applies.md)] [!INCLUDE [fabric](../includes/applies-to-version/fabric.md)] [!INCLUDE [azure-data-explorer](../includes/applies-to-version/azure-data-explorer.md)]
+
+Graph Query Language (GQL) lets you use standardized graph pattern matching. GQL follows the [ISO GQL standard](https://www.iso.org/obp/ui/en/#iso:std:iso-iec:39075:ed-1:v1:en) for graph database queries.
+
+> [!NOTE]
+> GQL support is in preview. Features and syntax can change based on feedback and ongoing development.
+
+## Introduction
+
+Graph Query Language (GQL) is an emerging ISO standard for querying graph databases. GQL lets you use SQL-like syntax for graph pattern matching, so it's easier to analyze relationships in your data. This article explains how to use GQL, its benefits, and key features.
+
+GQL provides standardized graph pattern matching capabilities for analyzing relationships in your data using the ISO standard syntax.
+
+## Getting started
+
+To use GQL, you need:
+
+- A graph data source that's either a [graph model](graph-operators.md) or a function returning a transient graph ending with a [make-graph](make-graph-operator.md) operator (see step 1).
+- Set specific client request properties (see step 2).
+
+## Step 1: Create a graph reference
+
+Before you use GQL, create a graph data source. This article uses an in-memory make-graph operator, but we recommend using a graph snapshot for production scenarios.
+
+
+```gql
+.create-or-alter function G_doc() {
+ let nodes = datatable(id:string, lbl:string, name:string, properties:dynamic)
+ [
+ "p1","Person","Alice",dynamic({"age": 25}),
+ "p2","Person","Bob",dynamic({"age": 30}),
+ "p3","Person","Carol",dynamic({"age": 28}),
+ "p4","Person","David",dynamic({"age": 35}),
+ "p5","Person","Emma",dynamic({"age": 26}),
+ "c1","Company","TechCorp",,
+ "c2","Company","DataSoft",,
+ "c3","Company","CloudInc",,
+ "ct1","City","Seattle",,
+ "ct2","City","Portland",,
+ "ct3","City","San Francisco",
+ ];
+ let edges = datatable(source:string, target:string, lbl:string, since:int)
+ [
+ "p1","c1","works_at",2020,
+ "p2","c1","works_at",2022,
+ "p3","c2","works_at",2023,
+ "p4","c3","works_at",2021,
+ "p5","c1","works_at",2024,
+ "p1","ct1","located_at",2019,
+ "p2","ct1","located_at",2021,
+ "p3","ct2","located_at",2022,
+ "p4","ct3","located_at",2020,
+ "p5","ct2","located_at",2023,
+ "c1","ct1","located_at",2015,
+ "c2","ct2","located_at",2018,
+ "c3","ct3","located_at",2017,
+ "p1","p2","knows",2019,
+ "p2","p3","knows",2021,
+ "p3","p4","knows",2022,
+ "p4","p5","knows",2023,
+ "p1","p4","likes",2020,
+ "p4","p1","likes",2020,
+ "p5","p2","likes",2022
+ ];
+ edges
+ | make-graph source --> target with nodes on id
+}
+```
+
+## Step 2: Configure client request properties
+
+::: moniker range="azure-data-explorer"
+To run GQL queries, set three client request properties. Set these properties through the SDK, API, or directly in the [Kusto Explorer](../tools/kusto-explorer.md) or [Azure Data Explorer web UI](/azure/data-explorer/web-ui-query-overview) by using directives.
+::: moniker-end
+::: moniker range="microsoft-fabric"
+To run GQL queries, set three client request properties. Set these properties through the SDK, API, or directly in the [Kusto Explorer](../tools/kusto-explorer.md) or [KQL queryset](/fabric/real-time-intelligence/kusto-query-set) by using directives.
+::: moniker-end
+
+### Set client request properties
+
+> [!IMPORTANT]
+> Run each directive separately before you run your GQL query. The directives set up the query environment for GQL execution.
+
+
+```kql
+#crp query_language=gql
+```
+
+
+```kql
+#crp query_graph_reference=G_doc()
+```
+
+To use labels in GQL, set the label column name:
+
+
+```kql
+#crp query_graph_label_name=lbl
+```
+
+> [!TIP]
+> Labels are optional in GQL, but they're often used to filter nodes and edges by type. Set the label column name to use labels in your GQL queries.
+
+### Set client request properties in programmatically
+
+For programmatic access, set these client request properties:
+
+- `query_language`: Set to `"gql"`.
+- `query_graph_reference`: Set to your graph function name (for example, `"G_doc()"`).
+- `query_graph_label_name`: Set to your label column name (for example, `"lbl"`).
+
+## Step 3: Run GQL queries
+
+After you finish setup, run GQL queries using standard GQL syntax. Use the examples below to explore basic and advanced GQL features.
+
+## Examples
+
+Find basic examples for pattern matching and labels here. For more complex examples, see [GQL query patterns, examples, and common scenarios](graph-query-language-use-cases.md). For reference documentation including fundamentals and function details, see [Graph Query Language (GQL) reference](graph-query-language-reference.md).
+
+### Example GQL query for basic pattern matching
+
+```gql
+MATCH (n)-[e]->(n2)
+RETURN COUNT(*) as CNT
+```
+
+**Output**
+
+The following table shows the result of the query.
+
+| CNT |
+|-----|
+| 20 |
+
+### Example GQL query with labels
+
+
+```gql
+MATCH (p:Person)-[e]->(target)
+RETURN p.name, target.name, e.lbl
+ORDER BY p.name, target.name
+LIMIT 2
+```
+
+**Output**
+
+The following table shows the result of the query.
+
+| p.name | target.name | e.lbl |
+|--------|-------------|-------|
+| Alice | Bob | knows |
+| Alice | David | likes |
+
+## Related content
+
+- [Graph Query Language (GQL) reference](graph-query-language-reference.md)
+- [GQL query patterns, examples, and use cases](graph-query-language-use-cases.md)
+- [Graph operators overview](graph-operators.md)
+- [make-graph operator](make-graph-operator.md)
+- [graph-match operator](graph-match-operator.md)
+- [Tutorial: Create your first graph](tutorials/your-first-graph.md)
+- [Graph functions reference](graph-function.md)
diff --git a/data-explorer/kusto/query/toc.yml b/data-explorer/kusto/query/toc.yml
index a08684079b..7f1be5e2c4 100644
--- a/data-explorer/kusto/query/toc.yml
+++ b/data-explorer/kusto/query/toc.yml
@@ -1329,6 +1329,16 @@ items:
href: graph-scenarios.md
- name: Best practices
href: graph-best-practices.md
+ - name: Additional query languages
+ items:
+ - name: Graph query language (GQL)
+ items:
+ - name: Query data using GQL
+ href: graph-query-language.md
+ - name: GQL reference
+ href: graph-query-language-reference.md
+ - name: GQL examples and use cases
+ href: graph-query-language-use-cases.md
- name: Geospatial
items:
- name: Geospatial clustering
diff --git a/data-explorer/toc.yml b/data-explorer/toc.yml
index 31c10c227f..b78a3117b0 100644
--- a/data-explorer/toc.yml
+++ b/data-explorer/toc.yml
@@ -325,6 +325,8 @@ items:
href: data-lake-query-data.md
- name: Query data in Azure Monitor
href: query-monitor-data.md
+ - name: Query data using GQL
+ href: /kusto/query/graph-query-language?view=azure-data-explorer&preserve-view=true
- name: Query data in MATLAB
href: query-matlab.md
- name: Query data using Power Apps