diff --git a/data-explorer/kusto-tocs/query/toc.yml b/data-explorer/kusto-tocs/query/toc.yml index a1618cf54d..a2362fed79 100644 --- a/data-explorer/kusto-tocs/query/toc.yml +++ b/data-explorer/kusto-tocs/query/toc.yml @@ -1539,14 +1539,16 @@ items: href: /kusto/query/graph-best-practices?view=azure-data-explorer&preserve-view=true - name: Additional query languages items: - - name: Graph query language (GQL) + - name: GQL (Graph query language) items: - - name: Query data using GQL + - name: GQL href: /kusto/query/graph-query-language?view=azure-data-explorer&preserve-view=true - - name: GQL reference - href: /kusto/query/graph-query-language-reference?view=azure-data-explorer&preserve-view=true - - name: GQL examples and use cases - href: /kusto/query/graph-query-language-use-cases?view=azure-data-explorer&preserve-view=true + - name: GQL Guide + href: /kusto/query/graph-query-language-guide?view=azure-data-explorer&preserve-view=true + - name: GQL Clauses + href: /kusto/query/graph-query-language-clauses?view=azure-data-explorer&preserve-view=true + - name: GQL Functions + href: /kusto/query/graph-query-language-functions?view=azure-data-explorer&preserve-view=true - name: openCypher href: /kusto/query/opencypher-graph-query-language?view=azure-data-explorer&preserve-view=true - name: Geospatial diff --git a/data-explorer/kusto/.openpublishing.redirection.json b/data-explorer/kusto/.openpublishing.redirection.json index 809b2c94db..64cf6fd336 100644 --- a/data-explorer/kusto/.openpublishing.redirection.json +++ b/data-explorer/kusto/.openpublishing.redirection.json @@ -94,6 +94,16 @@ "source_path": "query/ai-embed-text-plugin.md", "redirect_url": "/kusto/query/ai-embeddings-plugin", "redirect_document_id": false + }, + { + "source_path": "query/graph-query-language-reference.md", + "redirect_url": "/kusto/graph-query-language-guide", + "redirect_document_id": false + }, + { + "source_path": "query/graph-query-language-use-cases.md", + "redirect_url": "/kusto/graph-query-language-guide", + "redirect_document_id": false } ] -} +} \ No newline at end of file diff --git a/data-explorer/kusto/query/graph-query-language-clauses.md b/data-explorer/kusto/query/graph-query-language-clauses.md new file mode 100644 index 0000000000..0479c71f5f --- /dev/null +++ b/data-explorer/kusto/query/graph-query-language-clauses.md @@ -0,0 +1,525 @@ +--- +title: GQL Clauses +description: This article describes GQL clauses +ms.reviewer: mbrichko +ms.topic: reference +ms.date: 07/09/2026 +--- + +# GQL clauses + +> [!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)] + +This article lists the clauses supported in [GQL (Graph Query Language)](graph-query-language.md). + +The examples use the movies graph `G()` from [Create a graph and set a graph reference](run-graph-query-with-graph-reference.md): `Person` nodes (`Name`, `Born`), `Movie` nodes (`Title`, `Description`, `Year`), and `ACTED_IN` / `DIRECTED` edges (`Role`). + +## WHERE + +`WHERE` filters with a Boolean expression. Place it inside a node, inside an edge, or after the pattern. Inline `{property: value}` is shorthand for equality. + +### Filter by properties + +Use `WHERE` after the pattern to compare element properties with operators such as `=`, `<>`, `>`, `>=`, `<`, and `<=`. Combine conditions with `AND`, `OR`, and `NOT`, and group them with parentheses. + + +```gql +match (p:Person)-[:ACTED_IN]->(m:Movie) +where (p.Name = 'Actor1' or p.Name = 'Actor3') and m.Title <> 'Movie1' and p.Born > 1901 +return p.Name as name, m.Title as title +``` + +|name|title| +|---|---| +|Actor1|Movie3| +|Actor1|Movie2| + +### Negate a condition with NOT + +`NOT` reverses the result of a Boolean condition. Write it as a prefix, `NOT (condition)`, and combine it with `AND` and `OR`. Many predicates also accept an inline `NOT`, such as `NOT STARTS WITH`, `NOT ENDS WITH`, `NOT CONTAINS`, and `IS NOT NULL`. + + +```gql +match (p:Person) +where not (p.Born < 1960) +return p.Name as name +``` + +|name| +|---| +|Actor4| + +The following two queries are equivalent. The first uses prefix `NOT`; the second uses the inline `NOT CONTAINS` predicate: + + +```gql +match (p:Person) +where not (p.Name contains 'n') +return p.Name as name +``` + +|name| +|---| +|Actor4| + + +```gql +match (p:Person) +where p.Name not contains 'n' +return p.Name +``` + +|name| +|---| +|Actor4| + +### Inline property filters + +An inline `{property: value}` inside an element pattern is shorthand for an equality test on that property. The following two queries are equivalent: + + +```gql +match (p:Person {Name: 'Actor1'}) +return p.Name as name, p.Born as born +``` + +|name|born| +|---|---| +|Actor1|1956| + + +```gql +match (p:Person) +where p.Name = 'Actor1' +return p.Name as name, p.Born as born +``` + +|name|born| +|---|---| +|Actor1|1956| + +Inline filters apply to edges as well as nodes: + + +```gql +match (p:Person)-[:ACTED_IN {Role: 'Role2'}]->(m:Movie) +return p.Name as name, m.Title as title +``` + +|name|title| +|---|---| +|Actor2|Movie1| + +### Multiple inline conditions + +List several properties in one inline filter to require all of them; the pairs are combined with `AND`. This pattern matches only `Person` nodes where both `Name` is `'Actor1'` and `Born` is `1956`: + + +```gql +match (p:Person {Name: 'Actor1', Born: 1956}) +return p.Name as name, p.Born as born +``` + +|name|born| +|---|---| +|Actor1|1956| + +Apply inline filters to several elements in the same pattern to constrain each node and edge at once: + + +```gql +match (p:Person {Name: 'Actor2'})-[:ACTED_IN {Role: 'Role2'}]->(m:Movie {Title: 'Movie1'}) +return p.Name as name, m.Title as title +``` + +|name|title| +|---|---| +|Actor2|Movie1| + +Inline filters and `WHERE` combine freely: use inline `{...}` for the equality checks and `WHERE` for everything else, such as ranges or `OR`: + + +```gql +match (p:Person {Name: 'Actor1'})-[:ACTED_IN]->(m:Movie) +where m.Year >= 1990 +return p.Name as name, m.Title as title +``` + +|name|title| +|---|---| +|Actor1|Movie1| +|Actor1|Movie2| +|Actor1|Movie3| + +Inside a node or edge: + + +```gql +match (p:Person)-[e where 'DIRECTED' in labels(e)]->(m:Movie where m.Title <> 'Movie1') +return p.Name as name, m.Title as title +``` + +|name|title| +|---|---| +|Actor1|Movie2| + +The `IN` predicate tests whether a value is one of the items in a list: + + +```gql +match (p:Person) +where p.Name in ['Actor1', 'Actor2'] +return p.Name as name +``` + +|name| +|---| +|Actor1| +|Actor2| + +To compare entities, use `element_id(n)` or property comparisons (`n.Name <> m.Name`); direct entity equality isn't supported. + +## RETURN + +`RETURN` shapes the output. Project properties, rename with `AS`, return all bound variables with `*`, and deduplicate with `DISTINCT`. + + +```gql +match (p:Person)-[:ACTED_IN]->(m:Movie) +return m.Title as Title, p.Name as Name +``` + +|Title|Name| +|---|---| +|Movie3|Actor1| +|Movie2|Actor1| +|Movie1|Actor1| +|Movie1|Actor2| + +Return every bound variable with `*`: + + +```gql +match (p:Person)-[:ACTED_IN]->(m:Movie where m.Description starts with 'W') +return * +``` + +|p|m| +|---|---| +|{"Name": "Actor1", .. ,"Description": "War film","Year": 2020
}| + +## DISTINCT + +`DISTINCT` removes duplicate rows from a `RETURN`. It applies to scalar values; to deduplicate entities (nodes, edges, paths) either use some object identifier or `element_id()` function or convert the whole entity to a string, for example with `to_json_string(object_variable_name)` or with `CAST(object_variable_name AS string)`. + +Deduplicate projected rows: + + +```gql +match (p:Person)-[:ACTED_IN]->(m:Movie) +return distinct p.Name as name +``` + +|name| +|---| +|Actor1| +|Actor2| + +`DISTINCT` also works inside aggregations, to aggregate over distinct values only: + + +```gql +match (p:Person)-[:ACTED_IN]->(m:Movie) +return m.Title as Title, count(p.Name) as Actors, collect_list(distinct p.Name) as Names +``` + +|Title|Actors|Names| +|---|---|---| +|Movie3|1|["Actor1"]| +|Movie2|1|["Actor1"]| +|Movie1|2|["Actor1","Actor2"]| + +## ORDER BY + +`ORDER BY` sorts rows. Add `ASC` (default) or `DESC` per column. It can sit after `RETURN` or directly after the `MATCH` pattern. + + +```gql +match (p:Person) return p.Name as name, p.Born as born order by p.Born desc +``` + +|name|born| +|---|---| +|Actor4|1967| +|Actor2|1958| +|Actor1|1956| +|Actor3|1954| + +Sort by multiple keys, each with its own direction: + + +```gql +match (p:Person)-[:ACTED_IN]->(m:Movie) +return p.Name as Name, m.Title as Title +order by Name desc, Title asc +``` + +|Name|Title| +|---|---| +|Actor1|Movie1| +|Actor1|Movie3| +|Actor1|Movie2| +|Actor2|Movie1| + +> [!NOTE] +> Using `ORDER BY` directly after `MATCH` might not have an effect if the query has subsequent `NEXT` or join statements. + +## LIMIT + +`LIMIT` caps the number of rows. Placed after `RETURN` it bounds the final output; placed directly after the `MATCH` pattern it bounds the matched rows before projection and aggregation, which can change the result. + + +```gql +match (p:Person) return p.Name as name order by p.Born asc limit 2 +``` + +|name| +|---| +|Actor3| +|Actor1| + + +```gql +match (p:Person) order by p.Born asc limit 2 return p.Name as name +``` + +|name| +|---| +|Actor3| +|Actor1| + +## OFFSET + +`OFFSET` skips a number of rows before returning the rest; `SKIP` is a synonym. The value must be a positive integer. + + +```gql +match (m:Movie) return m.Title as title order by m.Title offset 1 limit 1 +``` + +|title| +|---| +|Movie3| + +Like `LIMIT`, `OFFSET` can also sit directly after the `MATCH` pattern, before `RETURN`. In that position it skips matched rows before projection: + + +```gql +match (m:Movie) order by m.Title offset 1 return m.Title as title +``` + +|title| +|---| +|Movie3| + +## FOR + +`FOR` iterates over the items of a list, producing one row per item. Two optional clauses expose the item's position: `WITH OFFSET` binds a zero-based index, and `WITH ORDINALITY` binds a one-based index. + + +```gql +match () limit 1 +for i in [5, 7] +return i +``` + +|i| +|---| +|5| +|7| + + +```gql +match () limit 1 +for c in ['a', 'b'] with offset idx +return c, idx +``` + +|c|idx| +|---|---| +|a|0| +|b|1| + + +```gql +match () limit 1 +for c in ['a', 'b'] with ordinality idx +return c, idx +``` + +|c|idx| +|---|---| +|a|1| +|b|2| + +## LET + +`LET` binds a name to a value so you can reuse it later in the query. Separate multiple bindings with commas; a later binding can reference an earlier one. Because every query begins with `MATCH`, `LET` can't be the first statement — place it after a `MATCH` (and after `WHERE`). + +Bind a per-row value, then return it: + + +```gql +match (p:Person) +where p.Name starts with 'K' +let BirthYear = p.Born +return p.Name as Name, BirthYear +``` + +|Name|BirthYear| +|---|---| +|Actor2|1958| + +Bind a constant and use it to filter: + + +```gql +match (m:Movie) +let threshold = 2000 +filter m.Year >= threshold +return m.Title as title +``` + +|Title| +|---| +|Movie2| +|Movie3| + +Define multiple values, where one builds on another: + + +```gql +match (p:Person) +let a = 1 +let b = a + 3 +return p.Name as name, a, b +``` + +|name|a|b| +|---|---|---| +|Actor1|1|4| +|Actor2|1|4| +|Actor3|1|4| +|Actor4|1|4| + +## NEXT + +`NEXT` chains statements into a linear pipeline. The rows produced by one statement become the input of the next, similar to passing a table from one step to another. Only the columns projected by the preceding `RETURN` (either `RETURN *` or explicitly named with `AS`) are visible to the following statement; renaming carries the new name forward. After `NEXT`, you can start another `MATCH`, a `FILTER`, or a `RETURN`. You can chain `NEXT` multiple times. + +Project columns, then filter and reshape them: + + +```gql +match (p:Person)-[:ACTED_IN]->(m:Movie) +return p.Name as Actor, m.Title as Title, m.Year as `Year` +next +filter `Year` = 2020 +return Actor, Title +``` + +|Actor|Title| +|---|---| +|Actor1|Movie3| + +Carry a path forward, then compute over it: + + +```gql +match p = (n0:Person)-[:DIRECTED]->(:Movie {Title: 'Movie1'}) +return * +next +return p, path_length(p) as Hops +order by Hops +``` + +Pass a node variable into a later `MATCH`: + + +```gql +match (p:Person {Name: 'Actor1'}) +return p +next +match (p)-[:ACTED_IN]->(m:Movie) +return m.Title +``` + +Aggregate the result of an earlier statement: + + +```gql +match (p:Person)-[:ACTED_IN]->(m:Movie) +return p.Name as Actor +next +return count(*) as Rows +``` + +## FILTER + +`FILTER` is a standalone statement that keeps only the rows satisfying a condition, much like [`WHERE`](#where) but written as its own step in a linear query. It's commonly used after `NEXT` to filter the rows produced by a previous statement, and can also follow a `MATCH` pattern. + +> [!TIP] +> Whenever a condition can be expressed against the matched pattern, prefer [`WHERE`](#where) over a standalone `FILTER`. `WHERE` is coupled to `MATCH` and is evaluated as part of pattern matching, so the whole query runs more efficiently. Use `FILTER` only when you need to filter on values produced by a later stage, such as after `NEXT` or an aggregation. + +Filter the rows passed forward by `NEXT`: + + +```gql +match (p:Person)-[:ACTED_IN]->(m:Movie) +return p.Name as Actor, m.Year as Year +next +filter Year >= 2000 +return Actor, Year +``` + +> [!NOTE] +> The `Year` variable is escaped because year is a reserved keyword. To differentiate between GQL syntax and user variables, you can escape user variables. Alternatively, add a prefix or suffix `_`. + +Filter directly after a `MATCH` pattern: + + +```gql +match (p:Person) +filter p.Born = 1956 +return p.Name as name +``` + +|name| +|---| +|Actor1| + +## UNION + +`UNION` combines the results of two or more queries; use `UNION ALL` to keep duplicates. Each query must return matching columns, and each `UNION` leg must end with a `RETURN` statement. + + +```gql +match (p:Person) return p.Name as name +union +match (m:Movie) return m.Title as name +``` + +|name| +|---| +|Actor1| +|Actor2| +|Actor3| +|Actor4| +|Movie1| +|Movie2| +|Movie3| + +## Related content + +- [GQL (Graph Query Language)](graph-query-language.md) +- [GQL guide](graph-query-language-guide.md) +- [Create a graph and set a graph reference](run-graph-query-with-graph-reference.md) \ No newline at end of file diff --git a/data-explorer/kusto/query/graph-query-language-functions.md b/data-explorer/kusto/query/graph-query-language-functions.md new file mode 100644 index 0000000000..29dd63ee12 --- /dev/null +++ b/data-explorer/kusto/query/graph-query-language-functions.md @@ -0,0 +1,563 @@ +--- +title: GQL Functions +description: This article describes GQL scalar functions, operators, and predicates +ms.reviewer: mbrichko +ms.topic: reference +ms.date: 07/09/2026 +--- + +# GQL functions, operators and predicates + +> [!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)] + +This article lists the scalar functions, operators, and predicates supported in [GQL (Graph Query Language)](graph-query-language.md). + +The examples use the movies graph `G()` from [Create a graph and set a graph reference](run-graph-query-with-graph-reference.md): `Person` nodes (`Name`, `Born`), `Movie` nodes (`Title`, `Description`, `Year`), and `ACTED_IN` / `DIRECTED` edges (`Role`). + +## Access operators + +Use these operators to read a property or element from a node, edge, map, list, or path. + +| Operator | Description | +|---|---| +| `x.property` | Access a property of a node, edge, or map by name. | +| `x[i]` | Access a list or path element by zero-based position, or a map member by key. | + + +```gql +match () limit 1 return [10, 20, 30][1] as byIndex, {a: 1, b: 2}.a as byDot, {a: 1, b: 2}['b'] as byKey +``` + +|byIndex|byDot|byKey| +|---|---|---| +|20|1|2| + +## String functions + +| Function | Description | +|---|---| +| `LEFT(s, n)` | First `n` characters of `s`. | +| `RIGHT(s, n)` | Last `n` characters of `s`. | +| `UPPER(s)` | Convert `s` to uppercase. | +| `LOWER(s)` | Convert `s` to lowercase. | +| `TRIM(s)`, `BTRIM(s)` | Remove spaces from both ends of `s`. `BTRIM` is a synonym of `TRIM(s)`. | +| `LTRIM(s)` | Remove spaces from the start (left) of `s`. | +| `RTRIM(s)` | Remove spaces from the end (right) of `s`. | +| `TRIM([BOTH \| LEADING \| TRAILING] [chars] FROM s)` | Remove `chars` (or spaces when `chars` is omitted) from the chosen end of `s`. The default is `BOTH`. | +| `CHAR_LENGTH(s)`, `CHARACTER_LENGTH(s)` | Number of characters in `s`. | +| `STRING_JOIN(list, separator)` | Concatenate list items into a string. | +| `s1 \|\| s2` | Concatenate strings `s1` and `s2`. | + +String predicates: + +| Predicate | Description | +|---|---| +| `s STARTS WITH prefix` | `true` when `s` begins with `prefix`. Add `NOT` to negate: `s NOT STARTS WITH prefix`. | +| `s ENDS WITH suffix` | `true` when `s` ends with `suffix`. Add `NOT` to negate: `s NOT ENDS WITH suffix`. | +| `s CONTAINS substring` | `true` when `substring` occurs anywhere in `s`. Add `NOT` to negate: `s NOT CONTAINS substring`. | + + +```gql +match () limit 1 return upper("abc") as result +``` + +|result| +|---| +|ABC| + + +```gql +match () limit 1 return trim(leading 'a' from 'aabc') as result +``` + +|result| +|---| +|bc| + + +```gql +match () limit 1 return string_join(["a", "bc"], "") as result +``` +|result| +|---| +|abc| + + + +```gql +MATCH (p :Person) +WHERE p.Name starts with 'Tom' or (p.Name ends with 's' and p.Name contains 'Han') +RETURN p.Name as actorName +``` + +|actorName| +|---| +|Actor1| + +## Numeric functions + +| Function | Description | +|---|---| +| `ABS(x)` | Absolute value of `x`. | +| `SQRT(x)` | Square root. | +| `EXP(x)` | e raised to `x`. | +| `LN(x)` | Natural logarithm. | +| `LOG10(x)` | Base-10 logarithm. | +| `FLOOR(x)` | Largest integer ≤ `x`. | +| `CEIL(x)`, `CEILING(x)` | Smallest integer ≥ `x`. | +| `MOD(x, y)` | Remainder of `x` divided by `y`. | +| `SIN(x)`, `COS(x)`, `TAN(x)`, `COT(x)` | Trigonometric functions (radians). | +| `ASIN(x)`, `ACOS(x)`, `ATAN(x)` | Inverse trigonometric functions. | +| `DEGREES(x)` | Convert radians to degrees. | +| `RADIANS(x)` | Convert degrees to radians. | + +Numbers combine with the arithmetic operators `+`, `-`, `*`, and `/`: + +| Operator | Description | +|---|---| +| `x + y` | Addition. | +| `x - y` | Subtraction. | +| `x * y` | Multiplication. | +| `x / y` | Division. | +| `-x` | Negation. | + + +```gql +match () limit 1 return 2 + 3 * 4 as a, 10 - 6 as b, mod(10, 4) as c, abs(-7) as d +``` + +|a|b|c|d| +|---|---|---|---| +|14|4|2|7| + + +```gql +match () limit 1 return sqrt(9) as `sqrt`, 9 / 3 as divideResult +``` + +|sqrt|divideResult| +|---|---| +|3|3| + +## Conditional expressions + +### CASE + +`CASE` returns a value based on conditions, in searched form (evaluate independent predicates) or simple form (compare one expression to a series of values). + +Searched form evaluates each `WHEN` predicate in turn: + + +```gql +match (m:Movie) +return m.Title as Title, case when m.Year < 2000 then 'C' else 'M' end as Era +``` + +|Title|Era| +|---|---| +|Movie1|C| +|Movie2|M| +|Movie3|M| + +Simple form compares one expression against each `WHEN` value: + + +```gql +match (m:Movie) +return m.Title as Title, + case m.Title + when 'Movie1' then 'A' + when 'Movie3' then 'G' + else 'O' + end as Name +``` + +|Title|Name| +|---|---| +|Movie1|A| +|Movie2|O| +|Movie3|G| + +> [!NOTE] +> Every `CASE` expression must include an `ELSE` clause. A `CASE` without `ELSE` isn't supported. + +## Type conversion + +### CAST + +`CAST(value AS type)` converts a value to another type, such as `string`, `int64`. It's required in two common situations. + +**Nested property access.** When a property is reached through a nested or indexed expression, such as an element of a path or a member of a map, the result is a `dynamic` value whose type isn't known in advance. Cast it to the expected type before you compare or compute with it. + +See [here](graph-query-language-guide.md#supported-types) the supported types. + + +```gql +match p = (a:Person)-[:ACTED_IN]->(m:Movie) +return m.Title as Title, cast(nodes(p)[0].Born as int) as ActorBorn +``` + +|Title|ActorBorn| +|---|---| +|Movie3|1956| +|Movie2|1956| +|Movie1|1956| +|Movie1|1958| + + +```gql +MATCH (p:Person) +RETURN p.Name || ', ' || CAST(p.Born as string) as nameAndYear +``` + +|nameAndYear| +|---| +|Actor1, 1956| +|Actor2, 1958| +|Actor3, 1954| +|Actor4, 1967| + +**Aggregating on object keys.** An aggregation key can't be an object (`dynamic`) value such as a node, edge, or property map. Convert the object value to a string first, using `to_json_string(...)` or `CAST(... AS string)`. + + +```gql +match (p:Person)->(m:Movie) +return to_json_string(p), count(m) as participatedInMoviesCount +``` + +|to_json_string(p)|participatedInMoviesCount| +|---|---| +|{"Name":"Actor1", ... }|4| +|{"Name":"Actor2", ... }|1| +|{"Name":"Actor3", ... }|1| + +## JSON functions + +| Function | Description | +|---|---| +| `PARSE_JSON_STRING(s)` | Parse a JSON string into an object or array. Access members with `.` or `[index]`. | +| `TO_JSON_STRING(x)` | Serialize a value (including a node or edge) to a JSON string. | + + +```gql +MATCH (n:Person {Name: 'Actor4'}) +RETURN TO_JSON_STRING(n) AS `json` +``` + +|json| +|---| +|{"Name":"Actor4","Born":1967,"Label2":["Person ","Female ","BestActressAward "],"Label":"Person"}| + +> [!TIP] +> The result alias `json` is escaped because json is a reserved keyword + + +```gql +MATCH (p:Person) +limit 1 +RETURN parse_json_string('{"a":"Michael", "b":"John"}'.a as string) AS name +``` + +|name| +|---| +|Michael| + + +```gql +MATCH (p:Person) +limit 1 +RETURN cast(parse_json_string('{"a":1, "b":2}').a as integer) AS a +``` + +|a| +|---| +|1| + + +```gql +MATCH () limit 1 RETURN PARSE_JSON_STRING('[1,2,3]')[1] as second +``` + +|second| +|---| +|2| + +> [!TIP] +> When using `parse_json_string()` it might be needed to cast the retrieved value from json if a specific type is needed + +## List and Map functions and predicates + +| Function | Description | +|---|---| +| `SIZE(list)` | Number of elements in a list. | +| `KEYS(element)` | Property names of a node, edge, or map, as a string list. | +| `l1 \|\| l2` | Concatenate lists `l1` and `l2`. | +| `value IN [list]` | True when `value` is a member of the list. | +| `x[i]` | Access a list or path element by zero-based position, or a map member by key. | + + +```gql +match () limit 1 return ["a", "b", "c"][1] as second +``` + +|second| +|---| +|b| + + +```gql +match () limit 1 return size([1, 2, 3]) as `size` +``` + +|size| +|---| +|3| + +> [!TIP] +> The result alias `size` is escaped because size is a reserved keyword + + +```gql +match () limit 1 return keys({"a":1, "b":2}) as my_keys +``` + +|my_keys| +|---| +|["a", "b" ]| + + +```gql +MATCH (p:Person) +WHERE p.Name IN ['Actor1', 'Actor2'] +RETURN p.Name as actor +``` + +|actor| +|---| +|Actor1| +|Actor2| + + +```gql +match () limit 1 return ["a"] || ["b"] as lst +``` + +|lst| +|---| +|["a", "b"]| + +## Date and Time functions + +| Function | Description | +|---|---| +| `ZONED_DATETIME()`, `CURRENT_TIMESTAMP` | Current UTC timestamp. | +| `ZONED_DATETIME("...")` | Parse a timestamp from a string. | +| `DURATION({...})` | Duration from a map of units: `days`, `hours`, `minutes`, `seconds`, `milliseconds`, `microseconds`, `nanoseconds`. Accepts RECORD type value. `years` and `months` aren't supported. | +| `DURATION_BETWEEN(start, end)` | Duration between two timestamps. | + + +```gql +match () limit 1 return duration ({minutes: 5}) as "5min" +``` +|5min| +|---| +|00:05:00| + + +```gql +match () limit 1 return duration ({minutes: 5, seconds:25}) as "5min25sec" +``` +|5min25sec| +|---| +|00:05:25| + +If nodes in the graph have a property such as `event_timestamp` of [KQL type datetime](scalar-data-types/datetime.md), you can find all graph nodes with an event that occurred in the last 10 minutes, measured from now or from a specific timestamp: + + +```gql +MATCH (n) where n.event_timestamp < CURRENT_TIMESTAMP - duration({minutes:10}) return n +``` + + +```gql +MATCH (n) where n.event_timestamp < zoned_datetime('2025-09-15 09:00:00.0') - duration({minutes:10}) return n +``` + + +```gql +MATCH () limit 1 +RETURN DURATION_BETWEEN(zoned_datetime('2025-09-15 09:00:00.0'), zoned_datetime()) AS Elapsed +``` + +> [!NOTE] +> The engine operates in UTC. + +## Graph and path functions + +| Function | Description | +|---|---| +| `LABELS(element)` | Labels of a node or edge, as a list. | +| `ELEMENT_ID(node)` | Identifier of a node. Edges aren't supported. | +| `NODES(path)` | Nodes of a path, as a list. | +| `EDGES(path)`, `RELATIONSHIPS(path)` | Edges of a path, as a list. | +| `PATH_LENGTH(path)` | Number of edges (hops) in a path. | + + +```gql +match (m: Movie) limit 1 return labels(m) as lbls +``` + +|lbls| +|---| +|["Movie"]| + + +```gql +match p = (n:Person)-[e: ACTED_IN]->(m: Movie) return p, path_length(p), nodes(p), edges(p) +``` + +## Aggregations + +Aggregate functions summarize the matched rows. When an aggregate appears next to non-aggregated expressions, those expressions become the grouping keys. + +| Function | Description | +|---|---| +| `COUNT(x)`, `COUNT(*)` | Number of values, or number of rows with `COUNT(*)`. | +| `SUM(x)` | Sum of numeric values. | +| `AVG(x)` | Average of numeric values. | +| `MIN(x)`, `MAX(x)` | Smallest and largest value. | +| `COLLECT_LIST(x)` | Gather values into a list. | + + +```gql +MATCH (p:Person) +RETURN COUNT(*) AS people, MIN(p.Born) AS earliest, MAX(p.Born) AS latest, SUM(p.Born) AS bornSum, AVG(p.Born) AS bornAvg +``` + +|people|earliest|latest|bornSum|bornAvg| +|---|---|---|---|---| +|4|1954|1967|7835|1958.75| + +Count the actors in each movie: + + +```gql +MATCH (p:Person)-[:ACTED_IN]->(m:Movie) +RETURN m.Title as Movie, COUNT(*) AS ActorsCount +``` + +|Movie|ActorsCount| +|---|---| +|Movie1|2| +|Movie2|1| +|Movie3|1| + + +```gql +MATCH (n:Person)-[:ACTED_IN]->(m:Movie) +RETURN collect_list(distinct n.Name) as actors +``` +|actors| +|---| +|["Actor1", "Actor2"]| + +Find how many movies each actor acted in: + + +```gql +MATCH (n:Person)-[:ACTED_IN]->(m:Movie) +RETURN TO_JSON_STRING(n) as actor, count(m) as moviesCount +``` + +|actor|moviesCount| +|---|---| +|{"Name":"Actor1", ... ,"Label":"Person"}|3| +|{"Name":"Actor2", ... ,"Label":"Person"}|1| + +> [!NOTE] +> To group by or aggregate a node, edge, or path, first convert it to a string with `TO_JSON_STRING()` or `CAST(object AS string)`, because objects can't be used as grouping keys directly. + +For more, see [Aggregations](graph-query-language-guide.md#aggregations) in the GQL guide. + + +## Predicates + +Comparison operators compare two scalar values and return a Boolean. They apply to scalar values, not to nodes or edges. + +| Operator | Description | +|---|---| +| `x = y` | Equal. | +| `x <> y` | Not equal. | +| `x < y`, `x <= y` | Less than, less than or equal. | +| `x > y`, `x >= y` | Greater than, greater than or equal. | + +Other operators: + +| Predicate | Description | +|---|---| +| `value IN [list]` | True when `value` is a member of the list. | + + +```gql +match (p:Person) +where p.Name in ['Actor1', 'Actor4'] +return p.Name as name +``` + +|name| +|---| +|Actor1| +|Actor4| + +Negate the test with `NOT` to keep only values that are absent from the list: + + +```gql +match (p:Person) +where not p.Name in ['Actor1', 'Actor2', 'Actor3'] +return p.Name as name +``` + +|name| +|---| +|Actor4| + +Other predicates: + +| Predicate | Description | +|---|---| +| `expr IS NULL`, `expr IS NOT NULL` | Test for null. [string type](scalar-data-types/string.md) can't be null| +| `expr IS [NOT] TRUE`, `expr IS [NOT] FALSE` | Test a Boolean expression. | +| `NOT expr` | Negate a Boolean condition. | + + +```gql +MATCH (p:Person) +WHERE p.Name IN ['Actor1', 'Actor2'] AND p.Born IS NOT NULL +RETURN p.Name as name +``` + +|name| +|---| +|Actor1| +|Actor2| + +It's possible to use `IS TRUE` or `IS FALSE` to test the result of a Boolean expression: + + +```gql +MATCH (p:Person) +WHERE (p.Born > 1960) IS TRUE +RETURN p.Name +``` + +|name| +|---| +|Actor4| + +## Related content + +- [GQL (Graph Query Language)](graph-query-language.md) +- [GQL guide](graph-query-language-guide.md) +- [GQL clauses](graph-query-language-clauses.md) +- [Create a graph and set a graph reference](run-graph-query-with-graph-reference.md) diff --git a/data-explorer/kusto/query/graph-query-language-guide.md b/data-explorer/kusto/query/graph-query-language-guide.md new file mode 100644 index 0000000000..9a2a8f5cfa --- /dev/null +++ b/data-explorer/kusto/query/graph-query-language-guide.md @@ -0,0 +1,796 @@ +--- +title: GQL Guide +description: This article describes GQL guide +ms.reviewer: mbrichko +ms.topic: reference +ms.date: 07/09/2026 +--- + +# GQL guide + +> [!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)] + +GQL (Graph Query Language) is an ISO standard ([ISO/IEC 39075:2024](https://www.iso.org/standard/76120.html)) for querying graphs by matching patterns of paths, nodes, and edges. + +## Prerequisites + +* Create a graph and set a graph reference before running queries. See [Create a graph and set a graph reference](run-graph-query-with-graph-reference.md). Every query begins with `MATCH`. + +* Use the sample movies graph G() referenced in the prerequisites. The graph contains Person nodes (Name, Born), Movie nodes (Title, Description, Year), and ACTED_IN and DIRECTED edges (Role), with labels stored in the Label2 column. + +* For the complete list of supported clauses, see [GQL clauses](graph-query-language-clauses.md). + +* For the complete list of supported functions, see [GQL scalar functions](graph-query-language-functions.md). + +> [!NOTE] +> * Some examples use illustrative labels that aren't included in the sample movies graph. +> * All graph queries begin with the MATCH clause. + +## Query basic structure + +A GQL query starts with a `MATCH` clause and follows a pipeline structure, where each clause operates on the results of the previous clause: + +1. `MATCH` finds patterns in the graph. +2. `WHERE` filters the matched rows. +3. `RETURN` projects the output columns, optionally with `DISTINCT` and aggregations. +4. `ORDER BY`, then `OFFSET` and `LIMIT`, sort and paginate the result. + +Combine several statements with a composite `MATCH` sequence, a `NEXT` pipeline (the rows returned by one statement feed the next), or `UNION` to concatenate results. + +## MATCH + +`MATCH` describes a pattern to find in the graph. A pattern is a chain of node patterns connected by edge patterns. Bind variables to entities to filter or return them. + +Each element pattern (node or edge) can be filtered by **label** (`:Person`), by **inline properties** (`{Year: 1995}`), and by **predicate** (`WHERE` inside the element, such as `(n:Person WHERE n.Born > 1950)`). + +### Node patterns + +A node pattern includes parentheses, optionally with a variable, a label, and inline properties. + + +| Pattern | Matches | +|---|---| +| `()` | any node | +| `(m)` | any node, bound to variable `m` | +| `(:Movie)` | nodes labeled `Movie` | +| `(m:Movie)` | `Movie` nodes, bound to variable `m` | +| `(m:Movie {Year: 1995})` | `Movie` nodes where `Year` equals 1995 | +| `(p:Adventure & !Horror)` | nodes with the `Adventure` label but not `Horror`. Combine labels with `&`, `\|` and `!` (see [Labels](#labels)) | + + +```gql +match (m:Movie) return m.Title as MovieTitle +``` + +| MovieTitle | +|---| +| m.Title | +| Movie1 | +| Movie2 | +| Movie3 | + +### Edge patterns + +An edge pattern includes square brackets between two nodes, with dashes and arrows that set direction. + +| Pattern | Direction | +|---|---| +| `(a)-[e]->(b)` | from `a` to `b` | +| `(a)<-[e]-(b)` | from `b` to `a` | +| `(a)-[e]-(b)` | either direction | +| `(a)->(b)`, `(a)<-(b)`, `(a)-(b)` | direction without a bound edge | +| `(a)-[e:ACTED_IN \| DIRECTED]->(b)` | from `a` to `b`, matching `ACTED_IN` or `DIRECTED` edges. Combine labels with `&`, `\|`, `!`, and `()` (see [Labels](#labels)) | + +The shorthand `->`, `<-`, and `-` may be used in place of `-[]->`, `<-[]-`, and `-[]-`. Edge variables and labels work like nodes: `[e]`, `[:ACTED_IN]`, `[e:ACTED_IN]`, `[e {Role: 'Director'}]`. + + +```gql +match (p:Person)-[:ACTED_IN]->(m:Movie) return p.Name as actor, m.Title as movie +``` + +|actor|movie| +|---|---| +|Actor1|Movie1| +|Actor1|Movie2| +|Actor1|Movie3| +|Actor2|Movie1| + +Count the matched patterns: + + +```gql +match (p:Person)-[:ACTED_IN]->(m:Movie) return count(*) as patternsCount +``` + +|patternsCount| +|---| +|4| + +### Chained patterns + +Connect multiple patterns to follow relationships across several hops. Use the same variable to refer to the same node more than once. + + +```gql +match (p1:Person)-[:ACTED_IN]->(m:Movie)<-[:ACTED_IN]-(p2:Person) +return p1.Name as firstActor, m.Title as movie, p2.Name as secondActor +``` + +|firstActor|movie|secondActor| +|---|---|---| +|Actor1|Movie1|Actor2| +|Actor2|Movie1|Actor1| + +### Multiple sequences (Multi-path or "star" pattern) + +GQL supports multiple comma-separated patterns in a single MATCH clause. The patterns must share at least one node variable, so they form a single connected pattern. For example, the following query finds people who acted in one movie and directed another. + + +```gql +match (p:Person)-[:ACTED_IN]->(m:Movie), (p)-[:DIRECTED]->(m2:Movie) +return p.Name as person, m.Title as firstTitle, m2.Title as secondTitle +``` + +|person|firstTitle|secondTitle| +|---|---|---| +|Actor1|Movie1|Movie2| +|Actor1|Movie2|Movie2| +|Actor1|Movie3|Movie2| + +### Variable-length edges + +Use a quantifier to follow a relationship across multiple hops. Assign the path to a variable (p = ...) to return the full path, or reference specific edges or nodes at index i with expressions such as p[i]. + + +```gql +match p = (n1)-[e]->{2,2}(n2) return p +``` + +| Quantifier | Hops | +|---|---| +| `{m,n}` | between `m` and `n` hops | +| `{m,}` | `m` or more hops | +| `{,n}` | up to `n` hops | +| `{,}` | any number of hops | +| `{n}` | exactly `n` | +| `*` | zero or more (same as `{,}`) | +| `+` | one or more (same as `{1,}`) | + +Example: + + +```gql +match (e:Employee)->*(m:Manager) return m.Name as managerName +``` + +## Labels + +Labels select nodes and edges by category. Filter with `:` followed by a label expression. Any logical combination of AND (`&`), OR (`|`), and NOT (`!`), nested with parentheses to any depth, is supported for both node and edge patterns. Label names are case-sensitive; quote names that collide with keywords using backticks. + +For the following examples, set the graph label name to `Label2` as described in [Create a graph and set a graph reference](run-graph-query-with-graph-reference.md). + +| Operator | Meaning | +|---|---| +| `:A` | has label `A` | +| `A & B` | has both | +| `A \| B` | has either | +| `!A` | does not have `A` | + +Next query finds all entities with label `Female`: + + +```gql +match (p :Female) return p.Name as name +``` + +|name| +|---| +|Actor4| + +Next query showcases a group label expression: + + +```gql +match ()-[e: !ACTED_IN & (DIRECTED | A) | E]->(m:Movie) return m.Title as title +``` + +|title| +|---| +|Movie1| +|Movie2| + +Reference the same variable with two labels to require both: + + +```gql +match (n:Male), (n:BestActorAward) return n.Name as name +``` + +|name| +|---| +|Actor1| + +The [`labels()`](graph-query-language-functions.md#graph-and-path-functions) function returns an element's labels as a list. It's useful in `WHERE` and `RETURN` clauses. For example, `'DIRECTED' IN labels(e)`. + + +```gql +match (p:Person)-[e:ACTED_IN]->(m:Movie) +return p.Name as name, labels(e) as EdgeLabels +``` + +|name|EdgeLabels| +|---|---| +|Actor1|["ACTED_IN"]| +|Actor1|["ACTED_IN"]| +|Actor1|["ACTED_IN"]| +|Actor2|["ACTED_IN"]| + +## Access node properties + +Access a property of a node or an edge by name using dot notation: the bound variable, a `.`, and the property name. + + +```gql +match (m:Movie) +return m.Title as title, m.Year as year +``` + +|title|year| +|---|---| +|Movie1|1995| +|Movie2|2011| +|Movie3|2020| + +The `[]` indexer accesses an element of a list or path by its integer position (zero-based), not a property by name. Combine it with dot notation to read a property of a path element. For example, `p[0]` is the first node of the path `p`, so `p[0].Name` returns that node's `Name`: + + +```gql +match p = (:Person)-[:ACTED_IN]->(:Movie) +return p[0].Name as name +``` + +|name| +|---| +|Actor1| +|Actor1| +|Actor1| +|Actor2| + +## Comparisons + +GQL supports the standard comparison operators on scalar values: `=`, `<>` (not equal), `<`, `<=`, `>`, and `>=`. + +Next query filters out movies with title 'Movie1' from matched patterns: + + +```gql +match (p:Person)-[:ACTED_IN]->(m:Movie) +where m.Title <> 'Movie1' +return p.Name as name, m.Title as title +``` + +|name|title| +|---|---| +|Actor1|Movie2| +|Actor1|Movie3| + +You can't compare two nodes or edges directly with `=` or `<>`. To test whether two entities are different, compare their identities with [`element_id()`](graph-query-language-functions.md#graph-and-path-functions) instead. For example, the following query finds pairs of distinct actors who appeared in the same movie: + + +```gql +match (p1:Person)-[:ACTED_IN]->(m:Movie)<-[:ACTED_IN]-(p2:Person) +where element_id(p1) <> element_id(p2) +return p1.Name as firstName, p2.Name as secondName, m.Title as title +``` + +|firstName|secondName|title| +|---|---|---| +|Actor1|Actor2|Movie1| +|Actor2|Actor1|Movie1| + +#### Filtering edges in a variable-length path + +Inside the edge pattern, label and `WHERE` filters apply to *every* edge along the path; only paths whose hops all match are kept. + + +```gql +match ()-[e:DIRECTED]->+() return e[0].Name as personName, e[0].Role as personRole +``` + +|personName|personRole| +|---|---| +|Actor3|Director| +|Actor1|Director| + + +```gql +match ()-[e where 'DIRECTED' in labels(e)]->+() return e[0].Role as role +``` + +|role| +|---| +|Director| +|Director| + +Outside the pattern, the edge variable `e` is an array of the traversed edges, so index it (`e[0]`, `e[1]`) to access individual hops. + +The following example finds the distinct roles. The `CAST` to string is required to deduplicate the values, because the type of `Role` is unknown in this context. + + +```kusto +match ()-[e where 'DIRECTED' in labels(e)]->+() return distinct cast(e[0].Role as string) as role +``` + +|role| +|---| +|Director| + +The following example finds all patterns with one or two hops and filters every edge by `Role`. The query returns all paths of length 1, and each path is returned as a JSON string. + + +```gql +match p = (n1)-[e {Role: "Role2"}]->{1,2}(n2) +where path_length(p) = 1 +return to_json_string(p) as myPath +``` + +|myPath| +|---| +|[{"Name":"Actor2", ... "Description":"A movie about space","Year":1995}]| + +## Operators + +Beyond comparison operators, GQL supports arithmetic, concatenation, and boolean logic: + +- **Arithmetic (`+`, `-`)**: Operate on numeric values, and add or subtract a `timespan` (`duration(...)`) to or from a `datetime` to shift a point in time. +- **Concatenation (`||`)**: Joins two strings, or two lists, into a single value. +- **Boolean (`AND`, `OR`, `NOT`, `IN`)**: Combine predicates in `WHERE`, `CASE`, and other conditions; group them with parentheses. + + +```gql +match (p:Person) +return p.Name || ' born ' || cast(p.Born as string) as `Label`, p.Born + 1 as NextYear +``` + +|Label|NextYear| +|---|---| +|Actor1 born 1956|1957| +|Actor2 born 1958|1959| +|Actor3 born 1954|1955| +|Actor4 born 1967|1968| + +> [!NOTE] +> The `Label` variable is escaped because label is a reserved keyword. To differentiate between GQL syntax and user variables, escape user variables. Alternatively, add a prefix or suffix `_`. + +## Paths + +A path is the full route a pattern traverses. Bind it with a path variable using `name = pattern`; the variable holds an alternating sequence of nodes and edges, which you can return, index (`p[0]`), or pass to a path function. + + +```gql +match `path` = (n1)-[e]->{1,2}(n2) +where `path`[0].Born = 1958 +return `path` +``` + +|path| +|---| +|[{"Name": "Actor2" ,..., "Drama", "History"]}]| + +> [!TIP] +> The path alias `path` is escaped because path is a reserved keyword. To differentiate between GQL syntax and user variables, you can escape user variables. Alternatively, add a prefix or suffix `_`. + + +```gql +match p1 = (n1)-[e]->{1,2}(n2), p2 = (:Movie & War) return p1, p2 +``` + +|p1|p2| +|---|---| +|[{ "Name": "Actor1" ,..., "War" ]}]|[{ ,..., "War" ]}]| + +The available path functions are `path_length`, `nodes`, and `edges` (also spelled `relationships`), which operate on a path variable. For details, see [Graph and path functions](graph-query-language-functions.md#graph-and-path-functions). + + +```gql +match p = (n1)-[]->{1,3}(n2) +where p[0].Name starts with 'K' +return nodes(p)[0] as firstNode, edges(p) as `edges`, path_length(p) as pathLength +``` + +|firstNode|edges|pathLength| +|---|---|---| +|{ "Name": "Actor2", ...|[ ..."ACTED_IN"... }]|1| + +> [!NOTE] +> The `edges` variable is escaped because edges is a reserved keyword. To differentiate between GQL syntax and user variables, you can escape user variables. Alternatively, add a prefix or suffix `_`. + +You can combine these functions to return the nodes, edges, and length of a matched path: + + +```gql +match p = (n0:Person)-[:DIRECTED]->(m:Movie {Title: 'Movie1'}) +return nodes(p), edges(p), path_length(p) +``` + +|nodes(p)|edges(p)|path_length(p)| +|---|---|---| +|[{"Name": "Actor3",...},{"Title": "Movie1", ...}]|[{..."DIRECTED"...}]|1| + +### Shortest paths + +`ANY SHORTEST` returns a single shortest path for each source and target pair. + + +```gql +match P = any shortest (n {station:"South-West"})->{1,3}(a {station: "North"}) +return P +``` + +### All shortest paths + +`ALL SHORTEST` returns all shortest paths of equal minimum length. + + +```gql +match P = all shortest (n {station:"South-West"})->{1,3}(a {station: "North"}) +return P +``` + +#### Limitations + +* A shortest path `MATCH` clause can't contain multiple pattern sequences. + +## MATCH and PATH modes + +A `MATCH` clause can specify how to handle cycles and repeated elements. The structure is: + +``` +MATCH [DIFFERENT EDGES | REPEATABLE ELEMENTS] [WALK | TRAIL | ACYCLIC | SIMPLE] PATTERN_1, ..., [WALK | TRAIL | ACYCLIC | SIMPLE] PATTERN_N WHERE ... +``` + +- **Match mode** — one setting per `MATCH` clause, either `DIFFERENT EDGES` (No edge matched twice) or `REPEATABLE ELEMENTS` (As many results as possible). The default is `DIFFERENT EDGES`. +- **Path mode** — each path in the clause can be prefixed with `WALK` (No restrictions), `TRAIL` (No repeated edges), `ACYCLIC` (No repeated nodes), or `SIMPLE` (No repeated interior nodes). The GQL default is `WALK`. + +The following tables show which combinations are supported. Multi-path is a "star" pattern with several paths. + +**`DIFFERENT EDGES` (default):** + +| Path mode | Single path | Multi-path ("star") | +|---|---|---| +| WALK | Supported | Supported (only if all paths are WALK/TRAIL) | +| TRAIL | Supported | Supported (only if all paths are WALK/TRAIL) | +| ACYCLIC | Supported | Not supported | +| SIMPLE | Not supported | Not supported | + +**`REPEATABLE ELEMENTS`:** + +| Path mode | Single path | Multi-path ("star") | +|---|---|---| +| WALK | Supported | Supported (only if all paths are WALK) | +| TRAIL | Supported | Not supported | +| ACYCLIC | Supported | Not supported | +| SIMPLE | Not supported | Not supported | + + +```gql +match different edges trail (p:Person)-[]->{1,3}(m:Movie) return p.Name as name, m.Title as title +``` + +With `REPEATABLE ELEMENTS`, the same node or edge can be revisited within a match: + + +```gql +match repeatable elements walk (p:Person)-[:ACTED_IN]->(m:Movie) return p.Name as name, m.Title as title +``` + +## Aggregations + +Aggregate expressions in `RETURN` compute over the matched rows. The non-aggregated columns form the grouping key. Supported [aggregations](graph-query-language-functions.md#aggregations) are `count`, `sum`, `avg`, `min`, `max`, and `collect_list`; each accepts `DISTINCT`. + +For example, find all actors who acted in a movie: + + +```gql +match (p:Person)-[:ACTED_IN]->(m:Movie) +return m.Title as Title, count(*) as Actors, collect_list(p.Name) as ActorNames +``` + +|Title|Actors|ActorNames| +|---|---|---| +|Movie3|1|[ "Actor1"]| +|Movie2|1|[ "Actor1"]| +|Movie1|2|[ "Actor1", "Actor2"]| + +Next query calculates count of patterns: + + +```gql +match (p:Person)-[:ACTED_IN]->(m:Movie) +return count(*) as countOfPatterns +``` + +|countOfPatterns| +|---| +|4| + +Combine several aggregations in one `RETURN`; the non-aggregated columns form the grouping key: + + +```gql +match (p:Person)-[:ACTED_IN]->(m:Movie) +return m.Title as Title, min(p.Born) as Earliest, max(p.Born) as Latest, avg(p.Born) as AvgBorn +``` + +|Title|Earliest|Latest|AvgBorn| +|---|---|---|---| +|Movie3|1956|1956|1956| +|Movie2|1956|1956|1956| +|Movie1|1956|1958|1957| + +`sum` adds up a numeric expression across each group: + + +```gql +match (p:Person)-[:ACTED_IN]->(m:Movie) +return m.Title as Title, sum(p.Born) as BornSum +``` + +|Title|BornSum| +|---|---| +|Movie3|1956| +|Movie2|1956| +|Movie1|3914| + +### Aggregate by entity + +To group by an entire node, edge, or path instead of a scalar property, use `to_json_string(entity)`. This function serializes the entity and its properties to a JSON string and uses that string as the grouping key. + + +```gql +match (p:Person)-[:ACTED_IN]->(m:Movie) +return to_json_string(m) as Movie, count(*) as Actors +``` + +|Movie|Actors| +|---|---| +|{"Title":"Movie3", ... }|1| +|{"Title":"Movie2", ... }|1| +|{"Title":"Movie1", ... }|2| + +Alternatively, convert the entity to a string with `CAST(entity AS string)`. This conversion also works as a grouping key: + + +```gql +match (p:Person)-[:ACTED_IN]->(m:Movie) +return cast(m as string) as Movie, count(*) as Actors +``` + +|Movie|Actors| +|---|---| +|{"Title":"Movie3", ... }|1| +|{"Title":"Movie2", ... }|1| +|{"Title":"Movie1", ... }|2| + +## Composite queries + +A query can contain more than one `MATCH` statement. How the statements relate depends on whether they share variables. + +**Cross join.** When two `MATCH` statements don't share variables, the result is the cartesian product of their rows (every combination). + + +```gql +match (p:Person where p.Name starts with 'J') +match (m:Movie where m.Title starts with 'G') +return p.Name as name, m.Title as title +``` + +|name|title| +|---|---| +|Actor4|Movie3| + +**Match sequence.** When a later `MATCH` reuses a variable bound by an earlier one, it continues from those bindings, joining the patterns on the shared variable. + + +```gql +match (p:Person {Name: 'Actor1'}) +match (p)-[:ACTED_IN]->(m:Movie) +return m.Title as title +``` + +|title| +|---| +|Movie3| +|Movie2| +|Movie1| + +> [!TIP] +> Each additional `MATCH` may introduce another join. Whenever a relationship can be expressed within a single pattern, prefer fewer `MATCH` statements for better performance. For example, the query above is equivalent to, and faster when written as, a single statement: + + + ```gql + match (p:Person {Name: 'Actor1'}), (p)-[:ACTED_IN]->(m:Movie) + return m.Title as title + ``` + +|title| +|---| +|Movie3| +|Movie2| +|Movie1| + +## Optional match + +`OPTIONAL MATCH` works like a left outer join. It keeps the rows from the preceding statement even when the optional pattern has no match. Unmatched variables are null or empty. The first `MATCH` in a query can't be `OPTIONAL`; an `OPTIONAL MATCH` must follow another statement. + + +```gql +match (p:Person) +optional match (p)-[:DIRECTED]->(m:Movie) +return p.Name as name, m.Title as title +``` + +|name|title| +|---|---| +|Actor3|Movie1| +|Actor1|Movie2| +|Actor4| | +|Actor2| | + +## Supported types + +GQL values map to the underlying [Kusto scalar types](scalar-data-types/index.md). The following types are supported: + +| Kusto type | Description | GQL literal example | +|---|---|---| +| `string` | Unicode text. | `'Actor1'` | +| `bool` | Boolean value. | `true`, `false` | +| `int` | 32-bit signed integer. | `42` | +| `long` | 64-bit signed integer. | `9000000000` | +| `real` | 64-bit floating-point number. | `3.14` | +| `decimal` | 128-bit decimal number. | `CAST('19.99' AS decimal)` | +| `datetime` | Point in time (UTC). | `zoned_datetime('2020-01-01 00:00:00.0')` | +| `timespan` | Duration. | `duration({days: 1, hours: 6})` | +| `dynamic` | `List` or `record` (the GQL key-value pair type, a JSON value). | `[1, 2, 3]`, `{a: 1}` | +| `null` | Absence of a value. | `null` | + +`CAST(value AS type)` accepts these GQL type names (case-insensitive), each mapping to a Kusto type: + +| Kusto type | Accepted GQL type names | +|---|---| +| `string` | `string`, `char`, `varchar` | +| `bool` | `bool`, `boolean` | +| `int` | `int`, `int32`, `integer` | +| `long` | `int64`, `integer64`, `uint`, `uint32`, `uint64` | +| `real` | `float`, `float32`, `real`, `double`, `float64` | +| `decimal` | `decimal` | +| `dynamic` | `list`, `record`, `any record` | + + +```gql +match (p:Person) +return p.Name || ' (' || cast(p.Born as string) || ')' as `Label` +``` + +|Label| +|---| +|Actor1 (1956)| +|Actor2 (1958)| +|Actor3 (1954)| +|Actor4 (1967)| + +> [!NOTE] +> The `Label` variable is escaped because label is a reserved keyword. To differentiate between GQL syntax and user variables, you can escape user variables. Alternatively, add a prefix or suffix `_`. + +## Temporal data analysis + +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. For the full list of temporal functions such as `duration()`, `duration_between()`, and `zoned_datetime()`, see [GQL date and time functions](graph-query-language-functions.md#date-and-time-functions). + +### 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` | `duration({hours: 24})` | `1.00:00:00` | +| Minutes | `minutes`, `minute` | `duration({minutes: 30})` | `00:30:00` | +| Seconds | `seconds`, `second` | `duration({seconds: 45})` | `00:00:45` | +| Milliseconds | `milliseconds`, `millisecond` | `duration({milliseconds: 500})` | `00:00:00.5000000` | +| Microseconds | `microseconds`, `microsecond` | `duration({microseconds: 1000})` | `00:00:00.0010000` | +| Nanoseconds | `nanoseconds`, `nanosecond` | `duration({nanoseconds: 1000000})` | `00:00:00.0010000` | + +You can combine multiple units in a single duration object: `duration({days: 1, minutes: 8, seconds: 7})` returns `1.00:08:07`. + +> [!NOTE] +> `duration()` accepts only a `record` of units (for example, `duration({days: 14, hours: 16})`); ISO 8601 duration strings such as `duration('P14DT16H12M')` aren't supported. The `timestamp()` function isn't supported either; use `zoned_datetime()` to construct a `datetime`. + +Example: + +Combine duration functions with timestamp arithmetic for precise temporal filtering. For example, the following query finds alerts generated within a three-minute time window. + + +```gql +match (system:System)-[event:generated]->(alert:Alert) +where event.event_timestamp > zoned_datetime("2012-01-01 08:00:00.0") and event.event_timestamp <= zoned_datetime("2012-01-01 08:00:00.0") + duration({minutes: 3}) +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 +``` + +## Work with JSON + +In GQL, a JSON object is a `record` value (the key-value pair type, written with `{...}`), or a `list` object value (written with `[...]`) which maps to the Kusto `dynamic` type. Two [JSON functions](graph-query-language-functions.md#json-functions) convert between JSON text and `record` values: + +- `parse_json_string(s)` parses a JSON string into a `record` or a `list`. +- `to_json_string(x)` serializes a value, including a node, edge, or path, into a JSON string. + +**Read JSON.** After parsing, access `record` members with `.` and `list` elements with `[index]`. Because the result is a [dynamic type](scalar-data-types/dynamic.md), cast it before you compare or compute with it as needed. + + +```gql +match () +return + parse_json_string('{"a":{"b":3}}').a.b as Nested, + cast(parse_json_string('[10,20,30]')[1] as int) as Second +limit 1 +``` + +|Nested|Second| +|---|---| +|3|20| + +**Write JSON.** Use `to_json_string` to serialize a node, edge, path, or value. + + +```gql +match (n:Person {Name: 'Actor4'}) +return to_json_string(n) as `Json` +``` + +|Json| +|---| +|{"Name":"Actor4","Born":1967,"Label2":["Female","BestActressAward"]}| + +> [!NOTE] +> The `Json` variable is escaped because json is a reserved keyword. To differentiate between GQL syntax and user variables, you can escape user variables. Alternatively, add a prefix or suffix `_`. + +## Work with nulls + +Test for null by using `IS NULL` and `IS NOT NULL`. + +Find entities where a property is not null: + + +```gql +match (p:Person) +where p.Born is not null +return count(*) as `count` +``` + +Find entities where a property is null: + + +```gql +match (p:Person) +where p.Born is null +return p.Name as name +``` + +> [!NOTE] +> * In Kusto, `string` values can't be null. You can instead test the number of characters. +> * The `count` variable is escaped because count is a reserved keyword. To differentiate between GQL syntax and user variables, escape user variables. Alternatively, add a prefix or suffix `_`. + +## Limitations + +For the full list of unsupported clauses, functions, aggregates, and predicates, see the limitations [here](graph-query-language.md#limitations). + +## Related content + +- [GQL (Graph Query Language)](graph-query-language.md) +- [GQL clauses](graph-query-language-clauses.md) +- [GQL scalar functions](graph-query-language-functions.md) +- [Create a graph and set a graph reference](run-graph-query-with-graph-reference.md) diff --git a/data-explorer/kusto/query/graph-query-language-reference.md b/data-explorer/kusto/query/graph-query-language-reference.md deleted file mode 100644 index 5505db9b42..0000000000 --- a/data-explorer/kusto/query/graph-query-language-reference.md +++ /dev/null @@ -1,462 +0,0 @@ ---- -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 -``` - -### Shortest path - -Finds the shortest paths between a set of source nodes and a set of target nodes. - -### Single shortest path - -Calculates single shortest path per source/target pair. - -```gql -MATCH P = ANY SHORTEST (n {station:"South-West"})-[]->{1,3}(a {station: "North"}) -RETURN P -``` - -### All shortest paths - -Calculates all shortest paths of equal minimum length. - -```gql -MATCH P = ALL SHORTEST (n {station:"South-West"})-[]->{1,3}(a {station: "North"}) -RETURN P -``` - -#### Limitations: - -* Shortest path match clause can't contain multiple pattern sequences - -## 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 - -- **Query structure**: All GQL queries must start with a `MATCH` statement. - -- **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 deleted file mode 100644 index 16c18ba172..0000000000 --- a/data-explorer/kusto/query/graph-query-language-use-cases.md +++ /dev/null @@ -1,764 +0,0 @@ ---- -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 index 939474f55e..e2c3dd3abf 100644 --- a/data-explorer/kusto/query/graph-query-language.md +++ b/data-explorer/kusto/query/graph-query-language.md @@ -1,171 +1,104 @@ --- -title: GQL Graph Query Language (preview) -description: This article describes Graph Query Language (GQL) -ms.reviewer: herauch +title: GQL +description: This article describes GQL (Graph Query Language) +ms.reviewer: mbrichko ms.topic: reference -ms.date: 08/24/2025 +ms.date: 07/09/2026 --- -# 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 +# GQL (Graph Query Language) -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. +> [!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)] -GQL provides standardized graph pattern matching capabilities for analyzing relationships in your data using the ISO standard syntax. +GQL (Graph Query Language) is an emerging ISO standard for querying graph databases that uses standardized graph pattern matching. It follows the [ISO GQL standard](https://www.iso.org/obp/ui/en/#iso:std:iso-iec:39075:ed-1:v1:en). ## Getting started -To use GQL, you need: +To use GQL, you need a graph data source that is either a [persistent graph](..\management\graph\graph-persistent-overview.md) (recommended for production scenarios) or a function that returns a transient graph (ending with a [make-graph](make-graph-operator.md) operator). + +## Prerequisites -- 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). +Create a graph and set a graph reference for querying. Follow the steps described [here](run-graph-query-with-graph-reference.md). -### Step 1: Create a graph reference +## Query example -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. +A typical GQL query starts with a `MATCH` pattern, optionally filters and aggregates the matched rows, and can chain more processing stages with `NEXT`. The following query uses the movies graph to find actors whose name starts with `T` or with `K` who appeared in more than one movie released in 1990 or later, ordered from the most movies to the fewest: ```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 -} +MATCH (p:Person)-[:ACTED_IN]->(m:Movie) +WHERE m.Year >= 1990 AND (p.Name STARTS WITH 'T' OR p.Name STARTS WITH 'K') +RETURN p.Name AS actor, COUNT(m) AS movieCount +NEXT +FILTER movieCount > 1 +RETURN actor, movieCount +ORDER BY movieCount DESC +LIMIT 1 ``` -### 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 +| actor | movieCount | +|---|---| +| Actor1 | 3 | -#### Set client request properties +Reading the query clause by clause: -> [!IMPORTANT] -> Run each directive separately before you run your GQL query. The directives set up the query environment for GQL execution. +- `MATCH (p:Person)-[:ACTED_IN]->(m:Movie)` describes the pattern to find: a node with the `Person` label bound to the variable `p`, connected by an edge with the `ACTED_IN` label, to a node with the `Movie` label bound to the variable `m`. Labels (`:Person`, `:ACTED_IN`, `:Movie`) restrict which entities match, and the variables (`p`, `m`) let you refer to the matched entities in later clauses. The arrow `->` sets the direction, from actor to movie. Every query must begin with `MATCH`. +- `WHERE` m.Year >= 1990 AND (p.Name STARTS WITH 'T' OR p.Name STARTS WITH 'K') filters the matched rows by a predicate before they're returned, here keeping only movies released in 1990 or later whose actor's name starts with `T` or `K`. `STARTS WITH` is a string predicate; you can also match the end of a value with `ENDS WITH`, or find a substring anywhere in the value with `CONTAINS`. +- `RETURN p.Name AS actor, COUNT(m) AS movieCount` projects the output columns and renames them with `AS`. It includes an aggregation, `COUNT(m)`, which groups by the other returned column (`p.Name`) and produces one row per actor with the number of matching movies. +- `NEXT` pipes the result of the preceding segment into a new query segment, so you can keep processing the rows you just produced, including the `movieCount` aggregate. +- `FILTER movieCount > 1` keeps only the rows that satisfy a predicate. `FILTER` is the same as `WHERE` but acts as a standalone statement while `WHERE` comes with `MATCH`. `WHERE` is more performant as it allows to eliminate redundant data earlier in the query. +- `RETURN actor, movieCount` projects the final columns, and `ORDER BY movieCount DESC` sorts the result from the highest movie count to the lowest. +- `LIMIT 1` caps the result at the first row after sorting. - -```kql -#crp query_language=gql -``` +Each clause is optional except `MATCH`. Omit the aggregation, `NEXT`, `FILTER`, and `LIMIT` for simple lookups, and combine them when you need to aggregate and then filter on the aggregated values. - -```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] +> For better performance, use as few `MATCH` clauses as possible, ideally one per query. A single `MATCH` clause can still be as complex as needed: the pattern can be nontrivial and can include connected components, variable-length edges, and more. > [!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. +> Whenever a condition can be expressed against the matched pattern, prefer `WHERE` over a standalone `FILTER`. `WHERE` is coupled to `MATCH` and is evaluated as part of pattern matching, so the whole query runs more efficiently. Use `FILTER` only when you need to filter on values produced by a later stage, such as after `NEXT` or an aggregation. -#### Set client request properties in programmatically +> [!TIP] +> Use `RETURN COUNT(*)` for existence checks. If you only need to check whether a pattern exists, return `COUNT(*)` instead of returning the full results. -For programmatic access, set these client request properties: +For the official International Standard for GQL, see [ISO/IEC 39075 Information Technology - Database Languages - GQL](https://www.iso.org/standard/76120.html). -- `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"`). +## GQL guide +For the GQL guide and supported features, see the [GQL guide](graph-query-language-guide.md). For all supported clauses see [GQL clauses](graph-query-language-clauses.md) and for functions, operators and predicates see [GQL scalar functions](graph-query-language-functions.md). -### Step 3: Run GQL queries +## Limitations -After you finish setup, run GQL queries using standard GQL syntax. Use the examples below to explore basic and advanced GQL features. +- **Query structure**: All queries must start with a `MATCH` statement. -## Examples +- **No graph modification operations**: Operations to change graph structures (such as `CREATE`/`DROP`/`ALTER` GRAPH, `INSERT`/`SET`/`REMOVE`/`DELETE`) and transactions (START `TRANSACTION`/`COMMIT`/`ROLLBACK`) are not supported. Use KQL for all graph creation, modification, and management tasks. -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). +- **Unsupported clauses**: `EXISTS`, `VALUE`, `CALL`, `EXCEPT`, `INTERSECT`. -### Example GQL query for basic pattern matching - -```gql -MATCH (n)-[e]->(n2) -RETURN COUNT(*) as CNT -``` +- **Unsupported functions, aggregates and predicates**: `SINH`, `COSH`, `TANH`, `DATE`, `TIME`, `LOCAL_TIME`, `LOCAL_DATETIME`, `ZONED_TIME`, `IS TYPED`, `DIRECTED`, `IS LABELED`, `SOURCE OF`, `DESTINATION OF`, `IS NORMALIZED`, `ALL_DIFFERENT`, `SAME`, `REGEXP_CONTAINS`, `POWER`, `LOG(base, x)`, `BYTE_LENGTH`, `OCTET_LENGTH`, `ROW_ID`, `BINARY_SEARCH`, `NORMALIZE`, `SOURCE`, `DESTINATION`, `ELEMENTS`, `RANGE`, `INNER_NODES`, `NULLIF`, `COALESCE`, `STDDEV_POP`,`STDDEV_SAMP`, `PERCENTILE_CONT`, `DISC`, `COLLECT_ELEMENTS`, `COLLECT_ONE` -**Output** +- **Unsupported types**: `BYTES`, `BINARY`, `VARBINARY`, `DATE`, `TIME` -The following table shows the result of the query. +- **Pattern matching**: `MATCH` and `OPTIONAL MATCH` are supported only for node entities, not for edge patterns. For multiple sequences per single `MATCH` clause, only single connected component patterns are supported. Undirected edges (`~`, `<~`, `~>`) are not supported. Quantified paths are not supported. -| CNT | -|-----| -| 20 | +- **Entity equivalence checks**: Checking entity equivalence using operators like `=` or `<>` between nodes or relationships (for example, `MATCH (n)-[]-(m) WHERE n <> m`) is not supported. Use the `element_id()` function or explicit field comparisons instead, such as `n.id <> m.id`. -### Example GQL query with labels +- **Distinct**: Applying `DISTINCT` directly to entities is not supported. As an alternative, convert the entity to a string, for example: `MATCH p = (n :Person) RETURN DISTINCT tostring(p)`. - -```gql -MATCH (p:Person)-[e]->(target) -RETURN p.name, target.name, e.lbl -ORDER BY p.name, target.name -LIMIT 2 -``` - -**Output** +- **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. -The following table shows the result of the query. - -| p.name | target.name | e.lbl | -|--------|-------------|-------| -| Alice | Bob | knows | -| Alice | David | likes | +> [!Note] +> * Reserved keywords: Some GQL keywords cannot be used as identifiers in queries, and some reserved keywords are not immediately obvious. 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. A good practice is to escape these names with `backticks` or add a prefix or suffix such as `_`. +> +> * Time and timezone: The engine operates in UTC. Datetime literals must use appropriate formats; only the UTC time zone is supported via ZONED_DATETIME("2011-12-31 23:59:59.9"). ## 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) +- [GQL Guide](graph-query-language-guide.md) +- [GQL Clauses](graph-query-language-clauses.md) +- [GQL Functions](graph-query-language-functions.md) +- [Create a graph and set a graph reference for querying](run-graph-query-with-graph-reference.md) +- [ISO/IEC 39075 Information Technology - Database Languages - GQL](https://www.iso.org/standard/76120.html) +- [ISO GQL standard](https://www.iso.org/obp/ui/en/#iso:std:iso-iec:39075:ed-1:v1:en) +- [make-graph operator](make-graph-operator.md) +- [Create persistent graph](..\management\graph\graph-persistent-overview.md) \ No newline at end of file diff --git a/data-explorer/kusto/query/media/graphs/graph-client-request-properties.png b/data-explorer/kusto/query/media/graphs/graph-client-request-properties.png new file mode 100644 index 0000000000..ee7d388d97 Binary files /dev/null and b/data-explorer/kusto/query/media/graphs/graph-client-request-properties.png differ diff --git a/data-explorer/kusto/query/media/graphs/graph-query-language.png b/data-explorer/kusto/query/media/graphs/graph-query-language.png new file mode 100644 index 0000000000..a99812b84a Binary files /dev/null and b/data-explorer/kusto/query/media/graphs/graph-query-language.png differ diff --git a/data-explorer/kusto/query/opencypher-graph-query-language.md b/data-explorer/kusto/query/opencypher-graph-query-language.md index f6e40cd5f3..a43c2efff1 100644 --- a/data-explorer/kusto/query/opencypher-graph-query-language.md +++ b/data-explorer/kusto/query/opencypher-graph-query-language.md @@ -1,278 +1,80 @@ --- -title: openCypher (preview) +title: openCypher description: This article describes openCypher, an open-source graph query language -ms.reviewer: herauch +ms.reviewer: mbrichko ms.topic: reference -ms.date: 10/29/2025 +ms.date: 06/22/2026 --- -# openCypher (preview) +# openCypher > [!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)] -openCypher is an [open-source specification](https://s3.amazonaws.com/artifacts.opencypher.org/openCypher9.pdf) for querying property graph databases. You can now run openCypher queries on KQL graph semantics, using a declarative syntax with ASCII-art style pattern matching that makes it intuitive to express complex graph patterns and relationships. - -> [!NOTE] -> openCypher support is in preview. Features and syntax can change based on feedback and ongoing development. +openCypher is an [open-source specification](https://s3.amazonaws.com/artifacts.opencypher.org/openCypher9.pdf) for querying property graph databases. You can run openCypher queries over KQL graph semantics by using a declarative, pattern-matching syntax that makes it intuitive to express complex graph patterns and relationships. ## Getting started -To use openCypher, 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 openCypher, create a graph data source. This article uses an in-memory make-graph operator, but we recommend using a graph snapshot for production scenarios. - - -```openCypher -.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 openCypher 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 openCypher queries, set three client request properties. Set these properties through the SDK, API, or directly in the [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 openCypher query. The directives set up the query environment for openCypher execution. - - -```kql -#crp query_language=opencypher -``` - - -```kql -#crp query_graph_reference=G_doc() -``` - -To use labels in openCypher, set the label column name: - - -```kql -#crp query_graph_label_name=lbl -``` - -> [!TIP] -> Labels are optional in openCypher, but they're often used to filter nodes and edges by type. Set the label column name to use labels in your openCypher queries. - -#### Set client request properties programmatically - -For programmatic access, set these client request properties: - -- `query_language`: Set to `"opencypher"`. -- `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 openCypher queries - -After you finish setup, run openCypher queries using the standard syntax. Use the examples below to explore basic openCypher features. - -## Examples - -The following examples demonstrate core openCypher query patterns. openCypher uses ASCII-art style syntax where nodes are represented in parentheses `()` and relationships in square brackets `[]` with arrows `-->` or `<--` to indicate direction. - -### Example 1: Basic pattern matching - -Find all relationships in the graph and count them. - - -```openCypher -MATCH (n)-[e]->(n2) -RETURN COUNT(*) as CNT -``` - -**Output** - -The following table shows the result of the query. +To use openCypher, you need a graph data source that is either a [persistent graph](..\management\graph\graph-persistent-overview.md) (recommended for production scenarios) or a function that returns a transient graph (ending with a [make-graph](make-graph-operator.md) operator). -| CNT | -|-----| -| 20 | +## Prerequisites -This query matches any node `n` connected to another node `n2` through a relationship `e` and returns the total count of such patterns. +Create a graph and set a graph reference for querying. Follow the steps described [here](run-graph-query-with-graph-reference.md). -### Example 2: Pattern matching with labels +## Example -Find people and their connections, filtering by node labels. +Find all actors who acted in a movie. ```openCypher -MATCH (p:Person)-[e]->(target) -RETURN p.name, target.name, e.lbl -ORDER BY p.name, target.name +MATCH (n :Person)-[e :ACTED_IN]->(m: Movie) +WHERE m.Title starts with 'A' +RETURN n.Name as actorName, m.Title as movieTitle +ORDER BY actorName desc LIMIT 2 ``` **Output** -The following table shows the result of the query. - -| p.name | target.name | e.lbl | -|--------|-------------|-------| -| Alice | Bob | knows | -| Alice | David | likes | - -This query finds all `Person` nodes and returns their names, the names of connected nodes, and the relationship type. The `:Person` label filters the source node to only match Person nodes. - -### Example 3: Filtering with WHERE clause - -Find people over 25 years old and return their details. - - -```openCypher -MATCH (person:Person) -WHERE person.properties.age > 25 -RETURN person.name, person.properties.age -ORDER BY person.name -``` - -**Output** - -| person.name | person.properties.age | -|-------------|------------------------| -| Bob | 30 | -| Carol | 28 | -| David | 35 | -| Emma | 26 | - -This query demonstrates property filtering using the `WHERE` clause to find people whose age property is greater than 25. - -### Example 4: Variable length paths - -Find all persons reachable from Alice through 1 to 3 hops. - - -```openCypher -MATCH (center)-[*1..3]->(connected:Person) -WHERE center.name = 'Alice' -RETURN DISTINCT connected.name -ORDER BY connected.name -``` - -**Output** - -|connected.name| -|---| -|Alice| -|Bob| -|Carol| -|David| -|Emma| +| actorName | movieTitle | +|-------------| --------- | +| Actor1 | Movie1 | +| Actor2 | Movie1 | -This query uses variable length paths `[*1..3]` to find all persons reachable from Alice through one to three hops. The `DISTINCT` keyword ensures each connected node appears only once. - -### Example 5: Aggregation and grouping - -Count how many people work at each company. - - -```openCypher -MATCH (person:Person)-[:works_at]->(company:Company) -RETURN company.name AS Company, COUNT(person) AS EmployeeCount -ORDER BY EmployeeCount DESC -``` - -**Output** - -| Company | EmployeeCount | -|-----------|---------------| -| TechCorp | 3 | -| CloudInc | 1 | -| DataSoft | 1 | - -This query demonstrates aggregation by grouping people by their company and counting employees at each company. The `[:works_at]` syntax filters relationships by type. +For more openCypher examples, see the [openCypher specification](https://s3.amazonaws.com/artifacts.opencypher.org/openCypher9.pdf). ## Limitations -openCypher support has the following limitations: +- **Query structure**: All queries must start with a `MATCH` statement. -- **Query structure**: All openCypher queries must start with a `MATCH` statement. +- **No graph modification operations**: Operations to change graph structures (such as `CREATE`, `MERGE`, `DELETE`, `SET`, `REMOVE`, `DETACH`) are not supported. Use KQL for all graph creation, modification, and management tasks. -- **Unsupported language constructs**: The following openCypher language constructs are not currently supported: - - **WITH clause**: Not supported for query composition - - **Subqueries**: Including `EXISTS` subqueries and pattern predicates +- **Unsupported clauses**: `EXISTS`, `SET`, `CALL`. -- **Unsupported keywords**: The following openCypher keywords are not supported: - - `MANDATORY MATCH` - - `UNWIND` - - `UNION` - - `SKIP` +- **Unsupported functions and aggregates**: `percentileDisc()`, `percentileCont()`, `stDev()`, `stDevP()`, `properties()`, `type()`, `range()`, `exists()`, `startNode()`, `endNode()`, `tail()`, `replace()`, `split()`, `substring()`, `rand()`, `round()`, `sign()`, `atan2()`, `e()`, `pi()`. -- **Unsupported scalar functions**: The following openCypher scalar functions are not yet implemented: - - Mathematical functions: `sin()`, `cos()`, `tan()`, `log()`, `round()`, `sign()`, `rand()` - - Statistical functions: `percentileDisc()`, `percentileCont()`, `stDev()`, `stDevP()` - - Utility functions: `coalesce()`, `id()`, `properties()`, `type()`, `range()` - - Case expressions: `CASE` statements +- **Pattern matching**: `MATCH` and `OPTIONAL MATCH` are supported only for node entities, not for edge patterns. For multiple sequences per single `MATCH` clause, only single connected component patterns are supported. -- **Partial support**: `CALL` procedures have only partial support. +- **Entity equivalence checks**: Checking entity equivalence using operators like `=` or `<>` between nodes or relationships (for example, `MATCH (n)-[]-(m) WHERE n <> m`) is not supported. Use the `id()` function or explicit field comparisons instead, such as `n.id <> m.id`. -- **Reserved keywords**: Some openCypher keywords can't be used as identifiers in queries. Some reserved keywords aren't immediately obvious. If your graph data has property names that conflict with openCypher reserved keywords, use different property names in your graph schema or rename them to avoid parsing conflicts. +- **Distinct**: Applying `DISTINCT` directly to entities is not supported. As an alternative, convert the entity to a string, for example: `MATCH p = (n :Person) RETURN DISTINCT tostring(p)`. - > [!IMPORTANT] - > When you design your graph schema, some common property names might conflict with openCypher reserved keywords. Avoid or rename these property names. +## Notes -- **No graph modification operations**: Operations to change graph structures (such as `CREATE`, `MERGE`, `DELETE`, `SET`, `REMOVE`) are not supported. Use KQL for all graph creation, change, and management tasks. +> [!NOTE] +> Performance improvement suggestion: +> +> For better performance, use as few `MATCH` clauses as possible, ideally one per query. A single `MATCH` clause can still be as complex as needed: the pattern can be nontrivial and can include connected components, variable-length edges, and more. -- **Optional pattern matching**: `OPTIONAL MATCH` is supported only for node patterns, not for edge patterns. +- **Reserved keywords**: Some openCypher keywords cannot be used as identifiers in queries, and some reserved keywords are not immediately obvious. If your graph data has property names that conflict with openCypher reserved keywords, use different property names in your graph schema or rename them to avoid parsing conflicts. -- **Entity equivalence checks**: Entity equivalence checks using operators like `=` or `<>` between nodes or relationships (for example, `MATCH (n)-[]-(m) WHERE n <> m`) are not supported. Use explicit field comparisons instead, such as `n.id <> m.id`. +> [!IMPORTANT] +> When you design your graph schema, some common property names might conflict with openCypher reserved keywords. Avoid or rename these property names. A good practice is to escape these names with `backticks` or add a prefix or suffix such as `_`. -- **Time and timezone**: The engine operates in UTC. Datetime literals must use appropriate formats; only the UTC timezone is supported. +- **Time and timezone**: The engine operates in UTC. Datetime literals must use appropriate formats; only the UTC time zone is supported. ## Related content -- [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) +- [openCypher specification](https://s3.amazonaws.com/artifacts.opencypher.org/openCypher9.pdf) - [openCypher project website](https://opencypher.org/) +- [make-graph operator](make-graph-operator.md) +- [Create persistent graph](..\management\graph\graph-persistent-overview.md) diff --git a/data-explorer/kusto/query/run-graph-query-with-graph-reference.md b/data-explorer/kusto/query/run-graph-query-with-graph-reference.md new file mode 100644 index 0000000000..fa1ad39a03 --- /dev/null +++ b/data-explorer/kusto/query/run-graph-query-with-graph-reference.md @@ -0,0 +1,197 @@ +--- +title: How to run GQL or openCypher queries +description: This article describes how to create a graph reference for openCypher and GQL queries +ms.reviewer: mbrichko +ms.topic: reference +ms.date: 07/09/2026 +--- + +# How to run GQL or openCypher queries + +> [!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)] + +To run GQL or openCypher queries, you need to create a graph, which can be either persistent or transient. + +## Create graph + +- For a transient graph, create a function that ends with a [make-graph](make-graph-operator.md) operator. Set labels if needed. Label data can be either a string column or an array column. + +The following example creates a movies graph of relationships between movies and people. + + +``` +.create-or-alter function G() + { + let Movies = datatable (Title: string, Description: string, Year: int, Label2: dynamic) + [ + 'Movie1', 'A movie about space', 1995, dynamic(["Movie","Survival","Adventure","Drama","History"]), + 'Movie2', 'Romantic comedy', 2011, dynamic(["Movie","Comedy","Drama","Romance"]), + 'Movie3', 'War film', 2020, dynamic(["Movie","Drama","History","War"]), + ] + | extend Label = 'Movie'; + let People = datatable (Name: string, Born: int, Label2: dynamic) + [ + 'Actor1', 1956, dynamic(["Person","Male","BestActorAward"]), + 'Actor2', 1958, dynamic(["Person","Male"]), + 'Actor3', 1954, dynamic(["Person","Male","BestDirectorAward"]), + 'Actor4', 1967, dynamic(["Person","Female","BestActressAward"]), + ] + | extend Label = 'Person'; + let Actions = datatable(Name: string, Movie: string, Label: string, Label2: dynamic, Role: string) + [ + 'Actor1', 'Movie1', 'ACTED_IN', dynamic(["ACTED_IN"]), 'Role1', + 'Actor2', 'Movie1', 'ACTED_IN', dynamic(["ACTED_IN"]), 'Role2', + 'Actor3', 'Movie1', 'DIRECTED', dynamic(["DIRECTED"]), 'Director', + 'Actor1', 'Movie2', 'ACTED_IN', dynamic(["ACTED_IN"]), 'Movie2', + 'Actor1', 'Movie2', 'DIRECTED', dynamic(["DIRECTED"]), 'Director', + 'Actor1', 'Movie3', 'ACTED_IN', dynamic(["ACTED_IN"]), 'Role3', + ]; + Actions + | make-graph Name --> Movie with People on Name, Movies on Title + } +``` +In the above example, entity label data can be either the `Label` column (single string) or the `Label2` column (string array). + + +- For a [persistent graph](..\management\graph\graph-persistent-overview.md), create a graph model and a snapshot. + +> [!TIP] +> For production scenarios, it is recommended to use graph model snapshots. + +Example: + + +``` +.create-or-alter graph_model MyGraphModel1 ``` + { + "Schema": { + "Nodes": { + "Person": { "Name":"string", "Born":"int" }, + "Movie": { "Title":"string", "Description":"string", "Year":"int" } + }, + "Edges": { + "ACTED_IN": { "Name":"string", "Movie":"string", "Role":"string" }, + "DIRECTED": { "Name":"string", "Movie":"string", "Role":"string" } + } + }, + "Definition": { + "Steps": [ + { + "Query": "datatable(Name:string, Born:int, Label2:dynamic) + [ + 'Actor1', 1956, dynamic(['Male', 'BestActorAward']), + 'Actor2', 1958, dynamic(['Male']), + 'Actor3', 1954, dynamic(['Male', 'BestDirectorAward']), + 'Actor4', 1967, dynamic(['Female', 'BestActressAward']), + ]", + "NodeIdColumn": "Name", + "Kind": "AddNodes", + "Labels": ["Person"], + "LabelsColumn" :"Label2", + }, + { + "Query": "datatable(Title:string, Description:string, Year:int, Label2:dynamic) + [ + 'Movie1', 'A movie about space', 1995, dynamic(['Survival', 'Adventure', 'Drama', 'History']), + 'Movie2', 'Romantic comedy', 2011, dynamic(['Comedy', 'Drama', 'Romance']), + 'Movie3', 'War film', 2020, dynamic(['Drama', 'History', 'War']), + ]", + "NodeIdColumn": "Title", + "Kind": "AddNodes", + "Labels": ["Movie"], + "LabelsColumn" :"Label2", + }, + { + "Query": "datatable(Name:string, Movie:string, Role:string, Label2:string) + [ + 'Actor1', 'Movie1', 'Role1', 'ACTED_IN', + 'Actor2', 'Movie1', 'Role2', 'ACTED_IN', + 'Actor1', 'Movie2', 'Movie2', 'ACTED_IN', + 'Actor1', 'Movie3', 'Role3', 'ACTED_IN', + 'Actor3', 'Movie1', 'Director', 'DIRECTED', + 'Actor1', 'Movie2', 'Director', 'DIRECTED', + ]", + "SourceColumn": "Name", + "TargetColumn": "Movie", + "Kind": "AddEdges", + "LabelsColumn" :"Label2", + } + ] + } +}``` +``` + +Create a snapshot for the model: + +``` +.make graph_snapshot MySnapshot1 from MyGraphModel1 +``` + +## Query graph + +::: moniker range="azure-data-explorer" +You can query the graph through the SDK, API, or directly in [Kusto Explorer](../tools/kusto-explorer.md) or the [Azure Data Explorer web UI](/azure/data-explorer/web-ui-query-overview). +::: moniker-end +::: moniker range="microsoft-fabric" +You can query the graph through the SDK, API, or directly in the [KQL queryset](/fabric/real-time-intelligence/kusto-query-set). +::: moniker-end +Set the directives described below as needed, then run either GQL or openCypher queries. + +* Set query language (run the following directive): + + +``` +#crp query_language=gql // or #crp query_language=opencypher +``` + +* Set the graph reference (run the following directive): + + +``` +#crp query_graph_reference=G +``` + +If the graph is a model and the client is the web UI (run the following directive): + + +``` +#crp query_graph_reference = graph("MyGraphModel1","MySnapshot1") +``` + +If the graph is a model and the client is [Kusto Explorer desktop application](../tools/kusto-explorer.md) (run the following directive): + + +``` + #crp query_graph_reference = 'graph("MyGraphModel1", "MySnapshot1")' +``` + +* If the graph is transient, set the label column name (default: `Label`). Run the following directive: + + +``` + #crp query_graph_label_name = Label2 +``` + +> [!IMPORTANT] +> Run each directive separately before you run your query. + +The following screenshot shows an example of three client request properties that have been set: + +:::image type="content" source="media/graphs/graph-client-request-properties.png" alt-text="Screenshot that shows client request properties."::: + +#### Querying a graph via Desktop UI + +Instead of using directive, it's possible to set query language via UI (Default: GQL): + In [Kusto Explorer](../tools/kusto-explorer.md), you can select the language via **Tools** > **Options** > **Query editor** > **Graph query language**: + +:::image type="content" source="media/graphs/graph-query-language.png" alt-text="Screenshot that shows how to choose query language."::: + +#### Querying a graph via SDK or API + +Set the following client request properties: + +- `query_language`: Set to `"opencypher"` or `"gql"`. +- `query_graph_reference`: Set to your graph function name (for example, `"G"`). +- `query_graph_label_name`: Set to your label column name (for example, `"Label2"`). + +For more information, see client request properties [here](..\api\get-started\app-basic-query.md?tabs=csharp#customize-query-behavior-with-client-request-properties). \ No newline at end of file diff --git a/data-explorer/kusto/query/toc.yml b/data-explorer/kusto/query/toc.yml index 160b6f08ce..cc92acb1c4 100644 --- a/data-explorer/kusto/query/toc.yml +++ b/data-explorer/kusto/query/toc.yml @@ -1346,16 +1346,20 @@ items: href: graph-best-practices.md - name: Additional query languages items: - - name: Graph query language (GQL) + - name: GQL (Graph query language) items: - - name: Query data using GQL + - name: 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: GQL guide + href: graph-query-language-guide.md + - name: GQL clauses + href: graph-query-language-clauses.md + - name: GQL functions + href: graph-query-language-functions.md - name: openCypher href: opencypher-graph-query-language.md + - name: How to run GQL or openCypher queries + href: run-graph-query-with-graph-reference.md - name: Geospatial items: - name: Geospatial clustering diff --git a/data-explorer/toc.yml b/data-explorer/toc.yml index fc343fc797..b9e4c2e241 100644 --- a/data-explorer/toc.yml +++ b/data-explorer/toc.yml @@ -333,7 +333,9 @@ items: - 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 + href: /kusto/query/graph-query-language.md + - name: Query data using openCypher + href: /kusto/query/opencypher-graph-query-language.md - name: Query data in MATLAB href: query-matlab.md - name: Query data using Power Apps