join ↗
Use the join stage to combine the results of two queries into a single result set based on a specified condition.
Syntax
join conflict_strategy = both|left|right type = inner|left|right ((<xql query>) as <execution_name> <boolean_expr>)
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
conflict_strategy |
string | No | Identifies how column name conflicts are resolved if a field name exists in both the parent (left) query's result set and the joined (right) query's result set. Valid values are both, left, or right. The default is right. |
type |
string | No | Defines the type of join, dictating which records are included in the final result set based on the join condition. Valid values are inner, left, or right. The default is inner. |
xql query |
query block | Yes | The XQL query whose results you want to combine with the parent query. This sub-query must be enclosed in parentheses. |
execution_name |
string | Yes | Provides an alias for the joined query's result set using the as clause. This alias is used to refer to fields from the joined query (for example, alias.field_name). |
boolean_expr |
expression | Yes | Identifies the conditions (join keys) that must be met to place a record in the join result set. |
Returns
The join stage returns a unified result set containing rows from the parent query combined with rows from the sub-query, based on the specified join type and condition.
Usage notes
- The
joinstage can combine results, but it does not preserve sort order. If sorting is needed, specify thesortstage after thejoinstage. joinoperations, especially on large datasets likexdr_data, can be resource-intensive. It is a Best practice to pare down datasets before joining them.- Always use the
fieldsstage early in your queries, including within the sub-queries of ajoin, to select only necessary columns. This minimizes the data processed and improves performance. - The
config case_sensitivestage can be used at the beginning of the query or when adding ajoinstage to control case sensitivity for field value evaluation. - By default, forensic datasets are not included in XQL query results unless explicitly defined. Queries for forensic data require specific enabling steps.
Examples
Example 1: Inner join
Goal: Perform an inner join to return only the records where there is a match in event_id from both the left and right datasets.
XQL code:
config timeframe = 1d | dataset = sample_xql_raw | filter event_id in (101, 102, 103, 104) // Simulates left side | fields event_id, event_description | join type = inner ( dataset = sample_xql_raw | filter event_id in (103, 104, 105, 106) // Simulates right side | fields event_id as joined_event_id, is_successful as success_status ) as right_data right_data.joined_event_id = event_id // Join condition
Explanation: This query joins records from sample_xql_raw where event_id is 101, 102, 103, or 104, with records where event_id is 103, 104, 105, or 106. Only event_ids present in both sets will be returned. The joined_event_id from right_data is matched with event_id from the main query.
Output:
| event_id | event_description | joined_event_id | success_status |
|---|---|---|---|
| 103 | Network connection established | 103 | true |
| 104 | System heartbeat | 104 | true |
Example 2: Left join
Goal: Perform a left join to return all records from the left dataset, and the matching records from the right.
XQL code:
config timeframe = 1d | dataset = sample_xql_raw | filter event_id in (101, 102, 103, 104) // Simulates left side | fields event_id, event_description | join type = left ( dataset = sample_xql_raw | filter event_id in (103, 104, 105, 106) // Simulates right side | fields event_id as joined_event_id, is_successful as success_status ) as right_data right_data.joined_event_id = event_id // Join condition
Explanation: This query returns all event_ids 101, 102, 103, 104 from the left side. For event_ids 101 and 102, since there are no matching records on the right side, NULL is populated for joined_event_id and success_status.
Output:
| event_id | event_description | joined_event_id | success_status |
|---|---|---|---|
| 101 | User login successful | NULL | NULL |
| 102 | File access attempt | NULL | NULL |
| 103 | Network connection established | 103 | true |
| 104 | System heartbeat | 104 | true |
Example 3: Right join
Goal: Perform a right join to return all records from the right dataset, and the matching records from the left.
XQL code:
config timeframe = 1d | dataset = sample_xql_raw | filter event_id in (101, 102, 103, 104) // Simulates left side | fields event_id, event_description | join type = right ( dataset = sample_xql_raw | filter event_id in (103, 104, 105, 106) // Simulates right side | fields event_id as joined_event_id, is_successful as success_status ) as right_data right_data.joined_event_id = event_id // Join condition
Explanation: This query returns all event_ids 103, 104, 105, 106 from the right side. For event_ids 105 and 106, since there are no matching records on the left side, NULL is populated for event_description.
Output:
| event_id | event_description | joined_event_id | success_status |
|---|---|---|---|
| 103 | Network connection established | 103 | true |
| 104 | System heartbeat | 104 | true |
| NULL | NULL | 105 | true |
| NULL | NULL | 106 | false |
Example 4: Conflict strategy left
Goal: Perform an inner join while prioritizing the column from the original (parent) query's result set when a conflict exists.
XQL code:
config timeframe = 1d | dataset = sample_xql_raw | filter event_id in (101, 102, 103, 104) // Simulates left side with original event_description | fields event_id, event_description | join conflict_strategy = left // Prioritize left side event_description type = inner ( dataset = sample_xql_raw | filter event_id in (103, 104, 105, 106) // Simulates right side data for join | fields event_id as joined_event_id, is_successful as event_description // This event_description will be discarded due to conflict_strategy = left ) as right_data right_data.joined_event_id = event_id // Join condition | fields event_id, event_description, joined_event_id // Select fields to show output
Explanation: The sub-query renames is_successful to event_description to create a conflict. Since conflict_strategy = left is specified, the event_description from the primary (left) query, which contains string values (for example, "Network connection established"), is preserved.
Output:
| event_id | event_description | joined_event_id |
|---|---|---|
| 103 | Network connection established | 103 |
| 104 | System heartbeat | 104 |
Example 5: Conflict strategy right
Goal: Perform an inner join while prioritizing the column from the inner (joined) query's result set when a conflict exists.
XQL code:
config timeframe = 1d | dataset = sample_xql_raw | filter event_id in (101, 102, 103, 104) // Simulates left side with original event_description | fields event_id, event_description | join conflict_strategy = right // Prioritize right side event_description type = inner ( dataset = sample_xql_raw | filter event_id in (103, 104, 105, 106) // Simulates right side data for join | fields event_id as joined_event_id, is_successful as event_description // This event_description will be kept ) as right_data right_data.joined_event_id = event_id // Join condition | fields event_id, event_description, joined_event_id // Select fields to show output
Explanation: With conflict_strategy = right, the event_description column from the inner (right) query is kept. This results in the event_description column containing the boolean values from is_successful (for example, true), while the original string event_description from the left query is discarded for the joined records.
Output:
| event_id | event_description | joined_event_id |
|---|---|---|
| 103 | true | 103 |
| 104 | true | 104 |