Table joins in Manticore Search enable you to combine documents from two tables by matching related columns. This functionality allows for more complex queries and enhanced data retrieval across multiple tables.
SELECT
select_expr [, select_expr] ...
FROM tbl_name
{INNER | LEFT} JOIN tbl2_name
ON join_condition
[...other select options]
join_condition: {
left_table.attr = right_table.attr
| left_table.json_attr.string_id = string(right_table.json_attr.string_id)
| left_table.json_attr.int_id = int(right_table.json_attr.int_id)
}
For more information on select options, refer to the SELECT section.
When joining by a value from a JSON attribute, you need to explicitly specify the value's type using the int()
or string()
function.
- String JSON attribute
- Int JSON attribute
SELECT ... ON left_table.json_attr.string_id = string(right_table.json_attr.string_id)
POST /search
{
"table": "table_name",
"query": {
<optional full-text query against the left table>
},
"join": [
{
"type": "inner" | "left",
"table": "joined_table_name",
"query": {
<optional full-text query against the right table>
},
"on": [
{
"left": {
"table": "left_table_name",
"field": "field_name",
"type": "<common field's type when joining using json attributes>"
},
"operator": "eq",
"right": {
"table": "right_table_name",
"field": "field_name"
}
}
]
}
],
"options": {
...
}
}
on.type: {
int
| string
}
Note, there is the type
field in the left
operand section which you should use when joining two tables using json attributes. The allowed values are string
and int
.
Manticore Search supports two types of joins:
- INNER JOIN: Returns only the rows where there is a match in both tables. For example, the query performs an INNER JOIN between the
orders
andcustomers
tables, including only the orders that have matching customers.
- SQL
- JSON
SELECT product, customers.email, customers.name, customers.address
FROM orders
INNER JOIN customers
ON customers.id = orders.customer_id
WHERE MATCH('maple', customers)
ORDER BY customers.email ASC;
+---------+-------------------+----------------+-------------------+
| product | customers.email | customers.name | customers.address |
+---------+-------------------+----------------+-------------------+
| Laptop | alice@example.com | Alice Johnson | 123 Maple St |
| Tablet | alice@example.com | Alice Johnson | 123 Maple St |
+---------+-------------------+----------------+-------------------+
2 rows in set (0.00 sec)
- LEFT JOIN: Returns all rows from the left table and the matched rows from the right table. If there is no match, NULL values are returned for the right table's columns. For example, this query retrieves all customers along with their corresponding orders using a LEFT JOIN. If no corresponding order exists, NULL values will appear. The results are sorted by the customer's email, and only the customer's name and the order quantity are selected.
- SQL
- JSON
SELECT
name, orders.quantity
FROM customers
LEFT JOIN orders
ON orders.customer_id = customers.id
ORDER BY email ASC;
+---------------+-----------------+-------------------+
| name | orders.quantity | @int_attr_email |
+---------------+-----------------+-------------------+
| Alice Johnson | 1 | alice@example.com |
| Alice Johnson | 1 | alice@example.com |
| Bob Smith | 2 | bob@example.com |
| Carol White | 1 | carol@example.com |
| John Smith | NULL | john@example.com |
+---------------+-----------------+-------------------+
5 rows in set (0.00 sec)
Building on the previous examples, let's explore a more advanced scenario where we combine table joins with faceting. This allows us to not only retrieve joined data but also aggregate and analyze it in meaningful ways.
This query retrieves products, customer names, product prices, and product tags from the orders
and customers
tables. It performs a LEFT JOIN
, ensuring all customers are included even if they have not made an order. The query filters the results to include only orders with a price greater than 500
and matches the products to the terms 'laptop', 'phone', or 'monitor'. The results are ordered by the id
of the orders in ascending order. Additionally, the query facets the results based on the warranty details from the JSON attributes of the joined orders
table.
- SQL
- JSON
SELECT orders.product, name, orders.details.price, orders.tags
FROM customers
LEFT JOIN orders
ON customers.id = orders.customer_id
WHERE orders.details.price > 500
AND MATCH('laptop|phone|monitor', orders)
ORDER BY orders.id ASC
FACET orders.details.warranty;
+----------------+---------------+----------------------+-------------+
| orders.product | name | orders.details.price | orders.tags |
+----------------+---------------+----------------------+-------------+
| Laptop | Alice Johnson | 1200 | 101,102 |
| Phone | Bob Smith | 800 | 103 |
+----------------+---------------+----------------------+-------------+
2 rows in set (0.01 sec)
--- 2 out of 2 results in 0ms ---
+-------------------------+----------+
| orders.details.warranty | count(*) |
+-------------------------+----------+
| 2 years | 1 |
| 1 year | 1 |
+-------------------------+----------+
2 rows in set (0.01 sec)
--- 2 out of 2 results in 0ms ---
Separate options can be specified for queries in a join: for the left table and the right table. The syntax is OPTION(<table_name>)
for SQL queries and one or more subobjects under "options"
for JSON queries.
Here's an example of how to specify different field weights for a full-text query on the right table. To retrieve match weights via SQL, use the <table_name>.weight()
expression.
In JSON queries, this weight is represented as <table_name>._score
.
- SQL
- JSON
SELECT product, customers.email, customers.name, customers.address, customers.weight()
FROM orders
INNER JOIN customers
ON customers.id = orders.customer_id
WHERE MATCH('maple', customers)
OPTION(customers) field_weights=(address=1500);
+---------+-------------------+----------------+-------------------+--------------------+
| product | customers.email | customers.name | customers.address | customers.weight() |
+---------+-------------------+----------------+-------------------+--------------------+
| Laptop | alice@example.com | Alice Johnson | 123 Maple St | 1500680 |
| Tablet | alice@example.com | Alice Johnson | 123 Maple St | 1500680 |
+---------+-------------------+----------------+-------------------+--------------------+
2 rows in set (0.00 sec)
When performing table joins, Manticore Search processes the results in batches to optimize performance and resource usage. Here's how it works:
-
How Batching Works:
- The query on the left table is executed first, and the results are accumulated into a batch.
- This batch is then used as input for the query on the right table, which is executed as a single operation.
- This approach minimizes the number of queries sent to the right table, improving efficiency.
-
Configuring Batch Size:
- The size of the batch can be adjusted using the
join_batch_size
search option. - It is also configurable in the
searchd
section of the configuration file. - The default batch size is
1000
, but you can increase or decrease it depending on your use case. - Setting
join_batch_size=0
disables batching entirely, which may be useful for debugging or specific scenarios.
- The size of the batch can be adjusted using the
-
Performance Considerations:
- A larger batch size can improve performance by reducing the number of queries executed on the right table.
- However, larger batches may consume more memory, especially for complex queries or large datasets.
- Experiment with different batch sizes to find the optimal balance between performance and resource usage.
To further optimize join operations, Manticore Search employs a caching mechanism for queries executed on the right table. Here's what you need to know:
-
How Caching Works:
- Each query on the right table is defined by the
JOIN ON
conditions. - If the same
JOIN ON
conditions are repeated across multiple queries, the results are cached and reused. - This avoids redundant queries and speeds up subsequent join operations.
- Each query on the right table is defined by the
-
Configuring Cache Size:
- The size of the join cache can be configured using the join_cache_size option in the
searchd
section of the configuration file. - The default cache size is
20MB
, but you can adjust it based on your workload and available memory. - Setting
join_cache_size=0
disables caching entirely.
- The size of the join cache can be configured using the join_cache_size option in the
-
Memory Considerations:
- Each thread maintains its own cache, so the total memory usage depends on the number of threads and the cache size.
- Ensure your server has sufficient memory to accommodate the cache, especially for high-concurrency environments.
When using JOINs in Manticore Search, keep the following points in mind:
-
Field selection: When selecting fields from two tables in a JOIN, do not prefix fields from the left table, but do prefix fields from the right table. For example:
SELECT field_name, right_table.field_name FROM ...
-
JOIN conditions: Always explicitly specify the table names in your JOIN conditions:
JOIN ON table_name.some_field = another_table_name.some_field
-
Expressions with JOINs: When using expressions that combine fields from both joined tables, alias the result of the expression:
SELECT *, (nums2.n + 3) AS x, x * n FROM nums LEFT JOIN nums2 ON nums2.id = nums.num2_id
-
Filtering on aliased expressions: You cannot use aliases for expressions involving fields from both tables in the WHERE clause.
-
JSON attributes: When joining on JSON attributes, you must explicitly cast the values to the appropriate type:
-- Correct: SELECT * FROM t1 LEFT JOIN t2 ON int(t1.json_attr.id) = t2.json_attr.id -- Incorrect: SELECT * FROM t1 LEFT JOIN t2 ON t1.json_attr.id = t2.json_attr.id
-
NULL handling: You can use IS NULL and IS NOT NULL conditions on joined fields:
SELECT * FROM t1 LEFT JOIN t2 ON t1.id = t2.id WHERE t2.name IS NULL SELECT * FROM t1 LEFT JOIN t2 ON t1.id = t2.id WHERE t2.name IS NOT NULL
-
Using ANY with MVA: When using the
ANY()
function with multi-valued attributes in JOINs, alias the multi-valued attribute from the joined table:SELECT *, t2.m AS alias FROM t LEFT JOIN t2 ON t.id = t2.t_id WHERE ANY(alias) IN (3, 5)
By following these guidelines, you can effectively use JOINs in Manticore Search to combine data from multiple indexes and perform complex queries.