diff --git a/.translation-cache/Searching/Joining.md.json b/.translation-cache/Searching/Joining.md.json index dbc8956235..ef1e44e8e0 100644 --- a/.translation-cache/Searching/Joining.md.json +++ b/.translation-cache/Searching/Joining.md.json @@ -40,8 +40,20 @@ "updated_at": 1766374263 }, "__meta": { - "source_text": "# Joining tables\n\nTable 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.\n\n## General syntax\n\n### SQL\n\n```sql\nSELECT\n\tselect_expr [, select_expr] ...\n\tFROM tbl_name\n\t{INNER | LEFT} JOIN tbl2_name\n\tON join_condition\n\t[...other select options]\n\njoin_condition: {\n\tleft_table.attr = right_table.attr\n\t| left_table.json_attr.string_id = string(right_table.json_attr.string_id)\n\t| left_table.json_attr.int_id = int(right_table.json_attr.int_id)\n\t| [..filters on right table attributes]\n}\n```\n\nFor more information on select options, refer to the [SELECT](../Searching/Intro.md#General-syntax) section.\n\n\n\nWhen joining by a value from a JSON attribute, you need to explicitly specify the value's type using the `int()` or `string()` function.\n\n\n```sql\nSELECT ... ON left_table.json_attr.string_id = string(right_table.json_attr.string_id)\n```\n\n\n```sql\nSELECT ... ON left_table.json_attr.int_id = int(right_table.json_attr.int_id)\n```\n\n\n\n### JSON\n\n```json\nPOST /search\n{\n \"table\": \"table_name\",\n \"query\": {\n \n },\n \"join\": [\n {\n \"type\": \"inner\" | \"left\",\n \"table\": \"joined_table_name\",\n \"query\": {\n \n },\n \"on\": [\n {\n \"left\": {\n \"table\": \"left_table_name\",\n \"field\": \"field_name\",\n \"type\": \"\"\n },\n \"operator\": \"eq\",\n \"right\": {\n \"table\": \"right_table_name\",\n \"field\": \"field_name\"\n }\n }\n ]\n }\n ],\n \"options\": {\n ...\n }\n}\n\non.type: {\n\tint\n\t| string\n}\n```\nNote, 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`.\n\n## Types of joins\n\nManticore Search supports two types of joins:\n\n\n\n1. **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` and `customers` tables, including only the orders that have matching customers.\n\n\n```sql\nSELECT product, customers.email, customers.name, customers.address\nFROM orders\nINNER JOIN customers\nON customers.id = orders.customer_id\nWHERE MATCH('maple', customers)\nORDER BY customers.email ASC;\n```\n\n\n```json\nPOST /search\n{\n \"table\": \"orders\",\n \"join\": [\n {\n \"type\": \"inner\",\n \"table\": \"customers\",\n \"query\": {\n \"query_string\": \"maple\"\n },\n \"on\": [\n {\n \"left\": {\n \"table\": \"orders\",\n \"field\": \"customer_id\"\n },\n \"operator\": \"eq\",\n \"right\": {\n \"table\": \"customers\",\n \"field\": \"id\"\n }\n }\n ]\n }\n ],\n \"_source\": [\"product\", \"customers.email\", \"customers.name\", \"customers.address\"],\n \"sort\": [{\"customers.email\": \"asc\"}]\n}\n```\n\n\n\n```sql\n+---------+-------------------+----------------+-------------------+\n| product | customers.email | customers.name | customers.address |\n+---------+-------------------+----------------+-------------------+\n| Laptop | alice@example.com | Alice Johnson | 123 Maple St |\n| Tablet | alice@example.com | Alice Johnson | 123 Maple St |\n+---------+-------------------+----------------+-------------------+\n2 rows in set (0.00 sec)\n```\n\n\n\n```json\n{\n \"took\": 0,\n \"timed_out\": false,\n \"hits\": {\n \"total\": 2,\n \"total_relation\": \"eq\",\n \"hits\": [\n {\n \"_id\": 1,\n \"_score\": 1,\n \"_source\": {\n \"product\": \"Laptop\",\n \"customers.email\": \"alice@example.com\",\n \"customers.name\": \"Alice Johnson\",\n \"customers.address\": \"123 Maple St\"\n }\n },\n {\n \"_id\": 3,\n \"_score\": 1,\n \"_source\": {\n \"product\": \"Tablet\",\n \"customers.email\": \"alice@example.com\",\n \"customers.name\": \"Alice Johnson\",\n \"customers.address\": \"123 Maple St\"\n }\n }\n ]\n }\n}\n```\n\n\n\n\n2. **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.\n\n\n```sql\nSELECT\nname, orders.quantity\nFROM customers\nLEFT JOIN orders\nON orders.customer_id = customers.id\nORDER BY email ASC;\n```\n\n\n```json\nPOST /search\n{\n\t\"table\": \"customers\",\n\t\"_source\": [\"name\", \"orders.quantity\"],\n\t\"join\": [\n {\n \"type\": \"left\",\n \"table\": \"orders\",\n \"on\": [\n {\n \"left\": {\n \"table\": \"orders\",\n \"field\": \"customer_id\"\n },\n \"operator\": \"eq\",\n \"right\": {\n \"table\": \"customers\",\n \"field\": \"id\"\n }\n }\n ]\n }\n ],\n \"sort\": [{\"email\": \"asc\"}]\n}\n```\n\n\n```\n+---------------+-----------------+-------------------+\n| name | orders.quantity | @int_attr_email |\n+---------------+-----------------+-------------------+\n| Alice Johnson | 1 | alice@example.com |\n| Alice Johnson | 1 | alice@example.com |\n| Bob Smith | 2 | bob@example.com |\n| Carol White | 1 | carol@example.com |\n| John Smith | NULL | john@example.com |\n+---------------+-----------------+-------------------+\n5 rows in set (0.00 sec)\n```\n\n\n\n```\n{\n \"took\": 0,\n \"timed_out\": false,\n \"hits\": {\n \"total\": 5,\n \"total_relation\": \"eq\",\n \"hits\": [\n {\n \"_id\": 1,\n \"_score\": 1,\n \"_source\": {\n \"name\": \"Alice Johnson\",\n \"address\": \"123 Maple St\",\n \"email\": \"alice@example.com\",\n \"orders.id\": 3,\n \"orders.customer_id\": 1,\n \"orders.quantity\": 1,\n \"orders.order_date\": \"2023-01-03\",\n \"orders.tags\": [\n 101,\n 104\n ],\n \"orders.details\": {\n \"price\": 450,\n \"warranty\": \"1 year\"\n },\n \"orders.product\": \"Tablet\"\n }\n },\n {\n \"_id\": 1,\n \"_score\": 1,\n \"_source\": {\n \"name\": \"Alice Johnson\",\n \"address\": \"123 Maple St\",\n \"email\": \"alice@example.com\",\n \"orders.id\": 1,\n \"orders.customer_id\": 1,\n \"orders.quantity\": 1,\n \"orders.order_date\": \"2023-01-01\",\n \"orders.tags\": [\n 101,\n 102\n ],\n \"orders.details\": {\n \"price\": 1200,\n \"warranty\": \"2 years\"\n },\n \"orders.product\": \"Laptop\"\n }\n },\n {\n \"_id\": 2,\n \"_score\": 1,\n \"_source\": {\n \"name\": \"Bob Smith\",\n \"address\": \"456 Oak St\",\n \"email\": \"bob@example.com\",\n \"orders.id\": 2,\n \"orders.customer_id\": 2,\n \"orders.quantity\": 2,\n \"orders.order_date\": \"2023-01-02\",\n \"orders.tags\": [\n 103\n ],\n \"orders.details\": {\n \"price\": 800,\n \"warranty\": \"1 year\"\n },\n \"orders.product\": \"Phone\"\n }\n },\n {\n \"_id\": 3,\n \"_score\": 1,\n \"_source\": {\n \"name\": \"Carol White\",\n \"address\": \"789 Pine St\",\n \"email\": \"carol@example.com\",\n \"orders.id\": 4,\n \"orders.customer_id\": 3,\n \"orders.quantity\": 1,\n \"orders.order_date\": \"2023-01-04\",\n \"orders.tags\": [\n 105\n ],\n \"orders.details\": {\n \"price\": 300,\n \"warranty\": \"1 year\"\n },\n \"orders.product\": \"Monitor\"\n }\n },\n {\n \"_id\": 4,\n \"_score\": 1,\n \"_source\": {\n \"name\": \"John Smith\",\n \"address\": \"15 Barclays St\",\n \"email\": \"john@example.com\",\n \"orders.id\": 0,\n \"orders.customer_id\": 0,\n \"orders.quantity\": 0,\n \"orders.order_date\": \"\",\n \"orders.tags\": [],\n \"orders.details\": null,\n \"orders.product\": \"\"\n }\n }\n ]\n }\n}\n```\n\n\n\n## Full-text matching across joined tables\n\nOne of the powerful features of table joins in Manticore Search is the ability to perform full-text searches on both the left and right tables simultaneously. This allows you to create complex queries that filter based on text content in multiple tables.\n\n\n\nYou can use separate `MATCH()` functions for each table in your JOIN query. The query filters results based on text content in both tables.\n\n\n```sql\nSELECT t1.f, t2.f \nFROM t1 \nLEFT JOIN t2 ON t1.id = t2.id \nWHERE MATCH('hello', t1) AND MATCH('goodbye', t2);\n```\n\n\n```json\nPOST /search\n{\n \"table\": \"t1\",\n \"query\": {\n \"query_string\": \"hello\"\n },\n \"join\": [\n {\n \"type\": \"left\",\n \"table\": \"t2\",\n \"query\": {\n \"query_string\": \"goodbye\"\n },\n \"on\": [\n {\n \"left\": {\n \"table\": \"t1\",\n \"field\": \"id\"\n },\n \"operator\": \"eq\",\n \"right\": {\n \"table\": \"t2\",\n \"field\": \"id\"\n }\n }\n ]\n }\n ],\n \"_source\": [\"f\", \"t2.f\"]\n}\n```\n\n\n\n```sql\n+-------------+---------------+\n| f | t2.f |\n+-------------+---------------+\n| hello world | goodbye world |\n+-------------+---------------+\n1 row in set (0.00 sec)\n```\n\n\n\n```json\n{\n \"took\": 1,\n \"timed_out\": false,\n \"hits\": {\n \"total\": 1,\n \"total_relation\": \"eq\",\n \"hits\": [\n {\n \"_id\": 2,\n \"_score\": 1680,\n \"t2._score\": 1680,\n \"_source\": {\n \"f\": \"hello world\",\n \"t2.f\": \"goodbye world\"\n }\n }\n ]\n }\n}\n```\n\n\n\n### JSON query structure for joins\n\nIn JSON API queries, table-specific full-text matching is structured differently than SQL:\n\n\n\n**Main table query**: The `\"query\"` field at the root level applies to the main table (specified in `\"table\"`).\n\n**Joined table query**: Each join definition can include its own `\"query\"` field that applies specifically to that joined table.\n\n\n```json\nPOST /search\n{\n \"table\": \"t1\",\n \"query\": {\n \"query_string\": \"hello\"\n },\n \"join\": [\n {\n \"type\": \"left\",\n \"table\": \"t2\",\n \"query\": {\n \"match\": {\n \"*\": \"goodbye\"\n }\n },\n \"on\": [\n {\n \"left\": {\n \"table\": \"t1\",\n \"field\": \"id\"\n },\n \"operator\": \"eq\",\n \"right\": {\n \"table\": \"t2\",\n \"field\": \"id\"\n }\n }\n ]\n }\n ]\n}\n```\n\n\n\n```json\n{\n \"took\": 1,\n \"timed_out\": false,\n \"hits\": {\n \"total\": 1,\n \"total_relation\": \"eq\",\n \"hits\": [\n {\n \"_id\": 1,\n \"_score\": 1680,\n \"t2._score\": 1680,\n \"_source\": {\n \"f\": \"hello world\",\n \"t2.id\": 1,\n \"t2.f\": \"goodbye world\"\n }\n }\n ]\n }\n}\n```\n\n\n\n### Understanding query behavior in JOIN operations\n\n\n\n**1. Query on main table only**: Returns all matching rows from the main table. For unmatched joined records (LEFT JOIN), SQL returns NULL values while JSON API returns default values (0 for numbers, empty strings for text).\n\n\n```sql\nSELECT * FROM t1 \nLEFT JOIN t2 ON t1.id = t2.id \nWHERE MATCH('database', t1);\n```\n\n\n\n```sql\n+------+-----------------+-------+------+\n| id | f | t2.id | t2.f |\n+------+-----------------+-------+------+\n| 3 | database search | NULL | NULL |\n+------+-----------------+-------+------+\n1 row in set (0.00 sec)\n```\n\n\n```json\nPOST /search\n{\n \"table\": \"t1\",\n \"query\": {\n \"query_string\": \"database\"\n },\n \"join\": [\n {\n \"type\": \"left\",\n \"table\": \"t2\",\n \"on\": [\n {\n \"left\": {\n \"table\": \"t1\",\n \"field\": \"id\"\n },\n \"operator\": \"eq\",\n \"right\": {\n \"table\": \"t2\",\n \"field\": \"id\"\n }\n }\n ]\n }\n ]\n}\n```\n\n\n\n```json\n{\n \"took\": 0,\n \"timed_out\": false,\n \"hits\": {\n \"total\": 1,\n \"total_relation\": \"eq\",\n \"hits\": [\n {\n \"_id\": 3,\n \"_score\": 1680,\n \"t2._score\": 0,\n \"_source\": {\n \"f\": \"database search\",\n \"t2.id\": 0,\n \"t2.f\": \"\"\n }\n }\n ]\n }\n}\n```\n\n\n\n**2. Query on joined table acts as filter**: When a joined table has a query, only records matching both the join condition AND the query condition are returned.\n\n\n```json\nPOST /search\n{\n \"table\": \"t1\",\n \"query\": {\n \"query_string\": \"database\"\n },\n \"join\": [\n {\n \"type\": \"left\",\n \"table\": \"t2\",\n \"query\": {\n \"query_string\": \"nonexistent\"\n },\n \"on\": [\n {\n \"left\": {\n \"table\": \"t1\",\n \"field\": \"id\"\n },\n \"operator\": \"eq\",\n \"right\": {\n \"table\": \"t2\",\n \"field\": \"id\"\n }\n }\n ]\n }\n ]\n}\n```\n\n\n\n```json\n{\n \"took\": 0,\n \"timed_out\": false,\n \"hits\": {\n \"total\": 0,\n \"total_relation\": \"eq\",\n \"hits\": []\n }\n}\n```\n\n\n**3. JOIN type affects filtering**: INNER JOIN requires both join and query conditions to be satisfied, while LEFT JOIN returns matching left table rows even when right table conditions fail.\n\n\n### Important considerations for full-text matching in JOINs\n\nWhen using full-text matching with joins, keep these points in mind:\n\n1. **Table-specific matching**: \n - **SQL**: Each `MATCH()` function should specify which table to search in: `MATCH('term', table_name)`\n - **JSON**: Use the root-level `\"query\"` for the main table and `\"query\"` within each join definition for joined tables\n\n2. **Query syntax flexibility**: JSON API supports both `\"query_string\"` and `\"match\"` syntaxes for full-text queries\n\n3. **Performance implications**: Full-text matching on both tables may impact query performance, especially with large datasets. Consider using appropriate indexes and batch sizes.\n\n4. **NULL/default value handling**: With LEFT JOIN, if there's no matching record in the right table, the query optimizer decides whether to evaluate full-text conditions or filtering conditions first based on performance. SQL returns NULL values while JSON API returns default values (0 for numbers, empty strings for text).\n\n5. **Filtering behavior**: Queries on joined tables act as filters - they restrict results to records that satisfy both join and query conditions.\n\n6. **Full-text operator support**: All [full-text operators](../Searching/Full_text_matching/Operators.md) are supported in JOIN queries, including phrase, proximity, field search, NEAR, quorum matching, and advanced operators.\n\n7. **Score calculation**: Each table maintains its own relevance score, accessible via `table_name.weight()` in SQL or `table_name._score` in JSON responses.\n\n## Example: Complex JOIN with faceting\n\nBuilding on the previous examples, let's explore a more advanced scenario where we combine table joins with faceting and full-text matching across multiple tables. This demonstrates the full power of Manticore's JOIN capabilities with complex filtering and aggregation.\n\n
\n\nInit queries for the following example:\n\n```\ndrop table if exists customers; drop table if exists orders; create table customers(name text, email text, address text); create table orders(product text, customer_id int, quantity int, order_date string, tags multi, details json); insert into customers values (1, 'Alice Johnson', 'alice@example.com', '123 Maple St'), (2, 'Bob Smith', 'bob@example.com', '456 Oak St'), (3, 'Carol White', 'carol@example.com', '789 Pine St'), (4, 'John Smith', 'john@example.com', '15 Barclays St'); insert into orders values (1, 'Laptop Computer', 1, 1, '2023-01-01', (101,102), '{\"price\":1200,\"warranty\":\"2 years\"}'), (2, 'Smart Phone', 2, 2, '2023-01-02', (103), '{\"price\":800,\"warranty\":\"1 year\"}'), (3, 'Tablet Device', 1, 1, '2023-01-03', (101,104), '{\"price\":450,\"warranty\":\"1 year\"}'), (4, 'Monitor Display', 3, 1, '2023-01-04', (105), '{\"price\":300,\"warranty\":\"1 year\"}');\n```\n\n
\n\n\n\nThis query demonstrates full-text matching across both the `customers` and `orders` tables, combined with range filtering and faceting. It searches for customers named \"Alice\" or \"Bob\" and their orders containing \"laptop\", \"phone\", or \"tablet\" with prices above $500. The results are ordered by order ID and faceted by warranty terms.\n\n\n```sql\nSELECT orders.product, name, orders.details.price, orders.tags\nFROM customers\nLEFT JOIN orders ON customers.id = orders.customer_id\nWHERE orders.details.price > 500\nAND MATCH('laptop | phone | tablet', orders)\nAND MATCH('alice | bob', customers)\nORDER BY orders.id ASC\nFACET orders.details.warranty;\n```\n\n\n```json\nPOST /search\n{\n \"table\": \"customers\",\n \"query\": {\n \"bool\": {\n \"must\": [\n {\n \"range\": {\n \"orders.details.price\": {\n \"gt\": 500\n }\n },\n \"query_string\": \"alice | bob\"\n ]\n }\n },\n \"join\": [\n {\n \"type\": \"left\",\n \"table\": \"orders\",\n \"query\": {\n \"query_string\": \"laptop | phone | tablet\"\n },\n \"on\": [\n {\n \"left\": {\n \"table\": \"customers\",\n \"field\": \"id\"\n },\n \"operator\": \"eq\",\n \"right\": {\n \"table\": \"orders\",\n \"field\": \"customer_id\"\n }\n }\n ]\n }\n ],\n \"_source\": [\"orders.product\", \"name\", \"orders.details.price\", \"orders.tags\"],\n \"sort\": [{\"orders.id\": \"asc\"}],\n \"aggs\": {\n \"warranty_facet\": {\n \"terms\": {\n \"field\": \"orders.details.warranty\"\n }\n }\n }\n}\n```\n\n\n```sql\n+-----------------+---------------+----------------------+-------------+\n| orders.product | name | orders.details.price | orders.tags |\n+-----------------+---------------+----------------------+-------------+\n| Laptop Computer | Alice Johnson | 1200 | 101,102 |\n| Smart Phone | Bob Smith | 800 | 103 |\n+-----------------+---------------+----------------------+-------------+\n2 rows in set (0.00 sec)\n\n+-------------------------+----------+\n| orders.details.warranty | count(*) |\n+-------------------------+----------+\n| 2 years | 1 |\n| 1 year | 1 |\n+-------------------------+----------+\n2 rows in set (0.00 sec)\n```\n\n\n\n```json\n{\n \"took\": 0,\n \"timed_out\": false,\n \"hits\": {\n \"total\": 3,\n \"total_relation\": \"eq\",\n \"hits\": [\n {\n \"_id\": 1,\n \"_score\": 1,\n \"orders._score\": 1565,\n \"_source\": {\n \"name\": \"Alice Johnson\",\n \"orders.tags\": [\n 101,\n 102\n ],\n \"orders.product\": \"Laptop Computer\"\n }\n },\n {\n \"_id\": 2,\n \"_score\": 1,\n \"orders._score\": 1565,\n \"_source\": {\n \"name\": \"Bob Smith\",\n \"orders.tags\": [\n 103\n ],\n \"orders.product\": \"Smart Phone\"\n }\n },\n {\n \"_id\": 1,\n \"_score\": 1,\n \"orders._score\": 1565,\n \"_source\": {\n \"name\": \"Alice Johnson\",\n \"orders.tags\": [\n 101,\n 104\n ],\n \"orders.product\": \"Tablet Device\"\n }\n }\n ]\n },\n \"aggregations\": {\n \"warranty_facet\": {\n \"buckets\": [\n {\n \"key\": \"2 years\",\n \"doc_count\": 1\n },\n {\n \"key\": \"1 year\",\n \"doc_count\": 2\n }\n ]\n }\n }\n}\n```\n\n\n\n\n## Search options and match weights\n\nSeparate options can be specified for queries in a join: for the left table and the right table. The syntax is `OPTION()` for SQL queries and one or more subobjects under `\"options\"` for JSON queries.\n\n\n\n\nHere'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 `.weight()` expression.\nIn JSON queries, this weight is represented as `._score`.\n\n\n```sql\nSELECT product, customers.email, customers.name, customers.address, customers.weight()\nFROM orders\nINNER JOIN customers\nON customers.id = orders.customer_id\nWHERE MATCH('maple', customers)\nOPTION(customers) field_weights=(address=1500);\n```\n\n\n```json\nPOST /search\n{\n \"table\": \"orders\",\n \"options\": {\n \"customers\": {\n \"field_weights\": {\n \"address\": 1500\n }\n }\n },\n \"join\": [\n {\n \"type\": \"inner\",\n \"table\": \"customers\",\n \"query\": {\n \"query_string\": \"maple\"\n },\n \"on\": [\n {\n \"left\": {\n \"table\": \"orders\",\n \"field\": \"customer_id\"\n },\n \"operator\": \"eq\",\n \"right\": {\n \"table\": \"customers\",\n \"field\": \"id\"\n }\n }\n ]\n }\n ],\n \"_source\": [\"product\", \"customers.email\", \"customers.name\", \"customers.address\"]\n}\n```\n\n\n\n```sql\n+---------+-------------------+----------------+-------------------+--------------------+\n| product | customers.email | customers.name | customers.address | customers.weight() |\n+---------+-------------------+----------------+-------------------+--------------------+\n| Laptop | alice@example.com | Alice Johnson | 123 Maple St | 1500680 |\n| Tablet | alice@example.com | Alice Johnson | 123 Maple St | 1500680 |\n+---------+-------------------+----------------+-------------------+--------------------+\n2 rows in set (0.00 sec)\n```\n\n\n\n```json\n{\n \"took\": 0,\n \"timed_out\": false,\n \"hits\": {\n \"total\": 2,\n \"total_relation\": \"eq\",\n \"hits\": [\n {\n \"_id\": 1,\n \"_score\": 1,\n \"customers._score\": 15000680,\n \"_source\": {\n \"product\": \"Laptop\",\n \"customers.email\": \"alice@example.com\",\n \"customers.name\": \"Alice Johnson\",\n \"customers.address\": \"123 Maple St\"\n }\n },\n {\n \"_id\": 3,\n \"_score\": 1,\n \"customers._score\": 15000680,\n \"_source\": {\n \"product\": \"Tablet\",\n \"customers.email\": \"alice@example.com\",\n \"customers.name\": \"Alice Johnson\",\n \"customers.address\": \"123 Maple St\"\n }\n }\n ]\n }\n}\n```\n\n\n## Join batching\n\nWhen performing table joins, Manticore Search processes the results in batches to optimize performance and resource usage. Here's how it works:\n\n- **How Batching Works**:\n - The query on the left table is executed first, and the results are accumulated into a batch.\n - This batch is then used as input for the query on the right table, which is executed as a single operation.\n - This approach minimizes the number of queries sent to the right table, improving efficiency.\n\n- **Configuring Batch Size**:\n - The size of the batch can be adjusted using the `join_batch_size` search option.\n - It is also [configurable](../Server_settings/Searchd.md#join_batch_size) in the `searchd` section of the configuration file.\n - The default batch size is `1000`, but you can increase or decrease it depending on your use case.\n - Setting `join_batch_size=0` disables batching entirely, which may be useful for debugging or specific scenarios.\n\n- **Performance Considerations**:\n - A larger batch size can improve performance by reducing the number of queries executed on the right table.\n - However, larger batches may consume more memory, especially for complex queries or large datasets.\n - Experiment with different batch sizes to find the optimal balance between performance and resource usage.\n\n## Join caching\n\nTo further optimize join operations, Manticore Search employs a caching mechanism for queries executed on the right table. Here's what you need to know:\n\n- **How Caching Works**:\n - Each query on the right table is defined by the `JOIN ON` conditions.\n - If the same `JOIN ON` conditions are repeated across multiple queries, the results are cached and reused.\n - This avoids redundant queries and speeds up subsequent join operations.\n\n- **Configuring Cache Size**:\n - The size of the join cache can be configured using the [join_cache_size](../Server_settings/Searchd.md#join_cache_size) option in the `searchd` section of the configuration file.\n - The default cache size is `20MB`, but you can adjust it based on your workload and available memory.\n - Setting `join_cache_size=0` disables caching entirely.\n\n- **Memory Considerations**:\n - Each thread maintains its own cache, so the total memory usage depends on the number of threads and the cache size.\n - Ensure your server has sufficient memory to accommodate the cache, especially for high-concurrency environments.\n\n## Joining distributed tables\n\nDistributed tables consisting only of local tables are supported on both the left and right sides of a join query. However, distributed tables that include remote tables are not supported.\n\n## Caveats and best practices\n\nWhen using JOINs in Manticore Search, keep the following points in mind:\n\n1. **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:\n ```sql\n SELECT field_name, right_table.field_name FROM ...\n ```\n\n2. **JOIN conditions**: Always explicitly specify the table names in your JOIN conditions:\n ```sql\n JOIN ON table_name.some_field = another_table_name.some_field\n ```\n\n3. **Expressions with JOINs**: When using expressions that combine fields from both joined tables, alias the result of the expression:\n ```sql\n SELECT *, (nums2.n + 3) AS x, x * n FROM nums LEFT JOIN nums2 ON nums2.id = nums.num2_id\n ```\n\n4. **Filtering on aliased expressions**: You cannot use aliases for expressions involving fields from both tables in the WHERE clause.\n\n5. **JSON attributes**: When joining on JSON attributes, you must explicitly cast the values to the appropriate type:\n ```sql\n -- Correct:\n SELECT * FROM t1 LEFT JOIN t2 ON int(t1.json_attr.id) = t2.json_attr.id\n\n -- Incorrect:\n SELECT * FROM t1 LEFT JOIN t2 ON t1.json_attr.id = t2.json_attr.id\n ```\n\n6. **NULL handling**: You can use IS NULL and IS NOT NULL conditions on joined fields:\n ```sql\n SELECT * FROM t1 LEFT JOIN t2 ON t1.id = t2.id WHERE t2.name IS NULL\n SELECT * FROM t1 LEFT JOIN t2 ON t1.id = t2.id WHERE t2.name IS NOT NULL\n ```\n\n7. **Using ANY with MVA**: When using the `ANY()` function with multi-valued attributes in JOINs, alias the multi-valued attribute from the joined table:\n ```sql\n SELECT *, t2.m AS alias\n FROM t\n LEFT JOIN t2 ON t.id = t2.t_id\n WHERE ANY(alias) IN (3, 5)\n ```\n\nBy following these guidelines, you can effectively use JOINs in Manticore Search to combine data from multiple indexes and perform complex queries.\n\n\n\n", - "updated_at": 1768530797, - "source_md5": "c7124fb4b24049b32e3aa8eca338b945" + "source_text": "# Joining tables\n\nTable 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.\n\n## General syntax\n\n### SQL\n\n```sql\nSELECT\n\tselect_expr [, select_expr] ...\n\tFROM tbl_name\n\t{INNER | LEFT} JOIN tbl2_name\n\tON join_condition\n\t[...other select options]\n\njoin_condition: {\n\tleft_table.attr = right_table.attr\n\t| left_table.json_attr.string_id = string(right_table.json_attr.string_id)\n\t| left_table.json_attr.int_id = int(right_table.json_attr.int_id)\n\t| [..filters on right table attributes]\n}\n```\n\nFor more information on select options, refer to the [SELECT](../Searching/Intro.md#General-syntax) section.\n\n\n\nWhen joining by a value from a JSON attribute, you need to explicitly specify the value's type using the `int()` or `string()` function.\n\n\n```sql\nSELECT ... ON left_table.json_attr.string_id = string(right_table.json_attr.string_id)\n```\n\n\n```sql\nSELECT ... ON left_table.json_attr.int_id = int(right_table.json_attr.int_id)\n```\n\n\n\n### JSON\n\n```json\nPOST /search\n{\n \"table\": \"table_name\",\n \"query\": {\n \n },\n \"join\": [\n {\n \"type\": \"inner\" | \"left\",\n \"table\": \"joined_table_name\",\n \"query\": {\n \n },\n \"on\": [\n {\n \"left\": {\n \"table\": \"left_table_name\",\n \"field\": \"field_name\",\n \"type\": \"\"\n },\n \"operator\": \"eq\",\n \"right\": {\n \"table\": \"right_table_name\",\n \"field\": \"field_name\"\n }\n }\n ]\n }\n ],\n \"options\": {\n ...\n }\n}\n\non.type: {\n\tint\n\t| string\n}\n```\nNote, 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`.\n\n## Types of joins\n\nManticore Search supports two types of joins:\n\n\n\n1. **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` and `customers` tables, including only the orders that have matching customers.\n\n\n```sql\nSELECT product, customers.email, customers.name, customers.address\nFROM orders\nINNER JOIN customers\nON customers.id = orders.customer_id\nWHERE MATCH('maple', customers)\nORDER BY customers.email ASC;\n```\n\n\n```json\nPOST /search\n{\n \"table\": \"orders\",\n \"join\": [\n {\n \"type\": \"inner\",\n \"table\": \"customers\",\n \"query\": {\n \"query_string\": \"maple\"\n },\n \"on\": [\n {\n \"left\": {\n \"table\": \"orders\",\n \"field\": \"customer_id\"\n },\n \"operator\": \"eq\",\n \"right\": {\n \"table\": \"customers\",\n \"field\": \"id\"\n }\n }\n ]\n }\n ],\n \"_source\": [\"product\", \"customers.email\", \"customers.name\", \"customers.address\"],\n \"sort\": [{\"customers.email\": \"asc\"}]\n}\n```\n\n\n\n```sql\n+---------+-------------------+----------------+-------------------+\n| product | customers.email | customers.name | customers.address |\n+---------+-------------------+----------------+-------------------+\n| Laptop | alice@example.com | Alice Johnson | 123 Maple St |\n| Tablet | alice@example.com | Alice Johnson | 123 Maple St |\n+---------+-------------------+----------------+-------------------+\n2 rows in set (0.00 sec)\n```\n\n\n\n```json\n{\n \"took\": 0,\n \"timed_out\": false,\n \"hits\": {\n \"total\": 2,\n \"total_relation\": \"eq\",\n \"hits\": [\n {\n \"_id\": 1,\n \"_score\": 1,\n \"_source\": {\n \"product\": \"Laptop\",\n \"customers.email\": \"alice@example.com\",\n \"customers.name\": \"Alice Johnson\",\n \"customers.address\": \"123 Maple St\"\n }\n },\n {\n \"_id\": 3,\n \"_score\": 1,\n \"_source\": {\n \"product\": \"Tablet\",\n \"customers.email\": \"alice@example.com\",\n \"customers.name\": \"Alice Johnson\",\n \"customers.address\": \"123 Maple St\"\n }\n }\n ]\n }\n}\n```\n\n\n\n\n2. **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.\n\n\n```sql\nSELECT\nname, orders.quantity\nFROM customers\nLEFT JOIN orders\nON orders.customer_id = customers.id\nORDER BY email ASC;\n```\n\n\n```json\nPOST /search\n{\n\t\"table\": \"customers\",\n\t\"_source\": [\"name\", \"orders.quantity\"],\n\t\"join\": [\n {\n \"type\": \"left\",\n \"table\": \"orders\",\n \"on\": [\n {\n \"left\": {\n \"table\": \"orders\",\n \"field\": \"customer_id\"\n },\n \"operator\": \"eq\",\n \"right\": {\n \"table\": \"customers\",\n \"field\": \"id\"\n }\n }\n ]\n }\n ],\n \"sort\": [{\"email\": \"asc\"}]\n}\n```\n\n\n```\n+---------------+-----------------+-------------------+\n| name | orders.quantity | @int_attr_email |\n+---------------+-----------------+-------------------+\n| Alice Johnson | 1 | alice@example.com |\n| Alice Johnson | 1 | alice@example.com |\n| Bob Smith | 2 | bob@example.com |\n| Carol White | 1 | carol@example.com |\n| John Smith | NULL | john@example.com |\n+---------------+-----------------+-------------------+\n5 rows in set (0.00 sec)\n```\n\n\n\n```\n{\n \"took\": 0,\n \"timed_out\": false,\n \"hits\": {\n \"total\": 5,\n \"total_relation\": \"eq\",\n \"hits\": [\n {\n \"_id\": 1,\n \"_score\": 1,\n \"_source\": {\n \"name\": \"Alice Johnson\",\n \"address\": \"123 Maple St\",\n \"email\": \"alice@example.com\",\n \"orders.id\": 3,\n \"orders.customer_id\": 1,\n \"orders.quantity\": 1,\n \"orders.order_date\": \"2023-01-03\",\n \"orders.tags\": [\n 101,\n 104\n ],\n \"orders.details\": {\n \"price\": 450,\n \"warranty\": \"1 year\"\n },\n \"orders.product\": \"Tablet\"\n }\n },\n {\n \"_id\": 1,\n \"_score\": 1,\n \"_source\": {\n \"name\": \"Alice Johnson\",\n \"address\": \"123 Maple St\",\n \"email\": \"alice@example.com\",\n \"orders.id\": 1,\n \"orders.customer_id\": 1,\n \"orders.quantity\": 1,\n \"orders.order_date\": \"2023-01-01\",\n \"orders.tags\": [\n 101,\n 102\n ],\n \"orders.details\": {\n \"price\": 1200,\n \"warranty\": \"2 years\"\n },\n \"orders.product\": \"Laptop\"\n }\n },\n {\n \"_id\": 2,\n \"_score\": 1,\n \"_source\": {\n \"name\": \"Bob Smith\",\n \"address\": \"456 Oak St\",\n \"email\": \"bob@example.com\",\n \"orders.id\": 2,\n \"orders.customer_id\": 2,\n \"orders.quantity\": 2,\n \"orders.order_date\": \"2023-01-02\",\n \"orders.tags\": [\n 103\n ],\n \"orders.details\": {\n \"price\": 800,\n \"warranty\": \"1 year\"\n },\n \"orders.product\": \"Phone\"\n }\n },\n {\n \"_id\": 3,\n \"_score\": 1,\n \"_source\": {\n \"name\": \"Carol White\",\n \"address\": \"789 Pine St\",\n \"email\": \"carol@example.com\",\n \"orders.id\": 4,\n \"orders.customer_id\": 3,\n \"orders.quantity\": 1,\n \"orders.order_date\": \"2023-01-04\",\n \"orders.tags\": [\n 105\n ],\n \"orders.details\": {\n \"price\": 300,\n \"warranty\": \"1 year\"\n },\n \"orders.product\": \"Monitor\"\n }\n },\n {\n \"_id\": 4,\n \"_score\": 1,\n \"_source\": {\n \"name\": \"John Smith\",\n \"address\": \"15 Barclays St\",\n \"email\": \"john@example.com\",\n \"orders.id\": 0,\n \"orders.customer_id\": 0,\n \"orders.quantity\": 0,\n \"orders.order_date\": \"\",\n \"orders.tags\": [],\n \"orders.details\": null,\n \"orders.product\": \"\"\n }\n }\n ]\n }\n}\n```\n\n\n\n## Full-text matching across joined tables\n\nOne of the powerful features of table joins in Manticore Search is the ability to perform full-text searches on both the left and right tables simultaneously. This allows you to create complex queries that filter based on text content in multiple tables.\n\n\n\nYou can use separate `MATCH()` functions for each table in your JOIN query. The query filters results based on text content in both tables.\n\n\n```sql\nSELECT t1.f, t2.f \nFROM t1 \nLEFT JOIN t2 ON t1.id = t2.id \nWHERE MATCH('hello', t1) AND MATCH('goodbye', t2);\n```\n\n\n```json\nPOST /search\n{\n \"table\": \"t1\",\n \"query\": {\n \"query_string\": \"hello\"\n },\n \"join\": [\n {\n \"type\": \"left\",\n \"table\": \"t2\",\n \"query\": {\n \"query_string\": \"goodbye\"\n },\n \"on\": [\n {\n \"left\": {\n \"table\": \"t1\",\n \"field\": \"id\"\n },\n \"operator\": \"eq\",\n \"right\": {\n \"table\": \"t2\",\n \"field\": \"id\"\n }\n }\n ]\n }\n ],\n \"_source\": [\"f\", \"t2.f\"]\n}\n```\n\n\n\n```sql\n+-------------+---------------+\n| f | t2.f |\n+-------------+---------------+\n| hello world | goodbye world |\n+-------------+---------------+\n1 row in set (0.00 sec)\n```\n\n\n\n```json\n{\n \"took\": 1,\n \"timed_out\": false,\n \"hits\": {\n \"total\": 1,\n \"total_relation\": \"eq\",\n \"hits\": [\n {\n \"_id\": 2,\n \"_score\": 1680,\n \"t2._score\": 1680,\n \"_source\": {\n \"f\": \"hello world\",\n \"t2.f\": \"goodbye world\"\n }\n }\n ]\n }\n}\n```\n\n\n\n### JSON query structure for joins\n\nIn JSON API queries, table-specific full-text matching is structured differently than SQL:\n\n\n\n**Main table query**: The `\"query\"` field at the root level applies to the main table (specified in `\"table\"`).\n\n**Joined table query**: Each join definition can include its own `\"query\"` field that applies specifically to that joined table.\n\n\n```json\nPOST /search\n{\n \"table\": \"t1\",\n \"query\": {\n \"query_string\": \"hello\"\n },\n \"join\": [\n {\n \"type\": \"left\",\n \"table\": \"t2\",\n \"query\": {\n \"match\": {\n \"*\": \"goodbye\"\n }\n },\n \"on\": [\n {\n \"left\": {\n \"table\": \"t1\",\n \"field\": \"id\"\n },\n \"operator\": \"eq\",\n \"right\": {\n \"table\": \"t2\",\n \"field\": \"id\"\n }\n }\n ]\n }\n ]\n}\n```\n\n\n\n```json\n{\n \"took\": 1,\n \"timed_out\": false,\n \"hits\": {\n \"total\": 1,\n \"total_relation\": \"eq\",\n \"hits\": [\n {\n \"_id\": 1,\n \"_score\": 1680,\n \"t2._score\": 1680,\n \"_source\": {\n \"f\": \"hello world\",\n \"t2.id\": 1,\n \"t2.f\": \"goodbye world\"\n }\n }\n ]\n }\n}\n```\n\n\n\n### Understanding query behavior in JOIN operations\n\n\n\n**1. Query on main table only**: Returns all matching rows from the main table. For unmatched joined records (LEFT JOIN), SQL returns NULL values while JSON API returns default values (0 for numbers, empty strings for text).\n\n\n```sql\nSELECT * FROM t1 \nLEFT JOIN t2 ON t1.id = t2.id \nWHERE MATCH('database', t1);\n```\n\n\n\n```sql\n+------+-----------------+-------+------+\n| id | f | t2.id | t2.f |\n+------+-----------------+-------+------+\n| 3 | database search | NULL | NULL |\n+------+-----------------+-------+------+\n1 row in set (0.00 sec)\n```\n\n\n```json\nPOST /search\n{\n \"table\": \"t1\",\n \"query\": {\n \"query_string\": \"database\"\n },\n \"join\": [\n {\n \"type\": \"left\",\n \"table\": \"t2\",\n \"on\": [\n {\n \"left\": {\n \"table\": \"t1\",\n \"field\": \"id\"\n },\n \"operator\": \"eq\",\n \"right\": {\n \"table\": \"t2\",\n \"field\": \"id\"\n }\n }\n ]\n }\n ]\n}\n```\n\n\n\n```json\n{\n \"took\": 0,\n \"timed_out\": false,\n \"hits\": {\n \"total\": 1,\n \"total_relation\": \"eq\",\n \"hits\": [\n {\n \"_id\": 3,\n \"_score\": 1680,\n \"t2._score\": 0,\n \"_source\": {\n \"f\": \"database search\",\n \"t2.id\": 0,\n \"t2.f\": \"\"\n }\n }\n ]\n }\n}\n```\n\n\n\n**2. Query on joined table acts as filter**: When a joined table has a query, only records matching both the join condition AND the query condition are returned.\n\n\n```json\nPOST /search\n{\n \"table\": \"t1\",\n \"query\": {\n \"query_string\": \"database\"\n },\n \"join\": [\n {\n \"type\": \"left\",\n \"table\": \"t2\",\n \"query\": {\n \"query_string\": \"nonexistent\"\n },\n \"on\": [\n {\n \"left\": {\n \"table\": \"t1\",\n \"field\": \"id\"\n },\n \"operator\": \"eq\",\n \"right\": {\n \"table\": \"t2\",\n \"field\": \"id\"\n }\n }\n ]\n }\n ]\n}\n```\n\n\n\n```json\n{\n \"took\": 0,\n \"timed_out\": false,\n \"hits\": {\n \"total\": 0,\n \"total_relation\": \"eq\",\n \"hits\": []\n }\n}\n```\n\n\n**3. JOIN type affects filtering**: INNER JOIN requires both join and query conditions to be satisfied, while LEFT JOIN returns matching left table rows even when right table conditions fail.\n\n\n### Important considerations for full-text matching in JOINs\n\nWhen using full-text matching with joins, keep these points in mind:\n\n1. **Table-specific matching**: \n - **SQL**: Each `MATCH()` function should specify which table to search in: `MATCH('term', table_name)`\n - **JSON**: Use the root-level `\"query\"` for the main table and `\"query\"` within each join definition for joined tables\n\n2. **Query syntax flexibility**: JSON API supports both `\"query_string\"` and `\"match\"` syntaxes for full-text queries\n\n3. **Performance implications**: Full-text matching on both tables may impact query performance, especially with large datasets. Consider using appropriate indexes and batch sizes.\n\n4. **NULL/default value handling**: With LEFT JOIN, if there's no matching record in the right table, the query optimizer decides whether to evaluate full-text conditions or filtering conditions first based on performance. SQL returns NULL values while JSON API returns default values (0 for numbers, empty strings for text).\n\n5. **Filtering behavior**: Queries on joined tables act as filters - they restrict results to records that satisfy both join and query conditions.\n\n6. **Full-text operator support**: All [full-text operators](../Searching/Full_text_matching/Operators.md) are supported in JOIN queries, including phrase, proximity, field search, NEAR, quorum matching, and advanced operators.\n\n7. **Score calculation**: Each table maintains its own relevance score, accessible via `table_name.weight()` in SQL or `table_name._score` in JSON responses.\n\n## Example: Complex JOIN with faceting\n\nBuilding on the previous examples, let's explore a more advanced scenario where we combine table joins with faceting and full-text matching across multiple tables. This demonstrates the full power of Manticore's JOIN capabilities with complex filtering and aggregation.\n\n
\n\nInit queries for the following example:\n\n```\ndrop table if exists customers; drop table if exists orders; create table customers(name text, email text, address text); create table orders(product text, customer_id int, quantity int, order_date string, tags multi, details json); insert into customers values (1, 'Alice Johnson', 'alice@example.com', '123 Maple St'), (2, 'Bob Smith', 'bob@example.com', '456 Oak St'), (3, 'Carol White', 'carol@example.com', '789 Pine St'), (4, 'John Smith', 'john@example.com', '15 Barclays St'); insert into orders values (1, 'Laptop Computer', 1, 1, '2023-01-01', (101,102), '{\"price\":1200,\"warranty\":\"2 years\"}'), (2, 'Smart Phone', 2, 2, '2023-01-02', (103), '{\"price\":800,\"warranty\":\"1 year\"}'), (3, 'Tablet Device', 1, 1, '2023-01-03', (101,104), '{\"price\":450,\"warranty\":\"1 year\"}'), (4, 'Monitor Display', 3, 1, '2023-01-04', (105), '{\"price\":300,\"warranty\":\"1 year\"}');\n```\n\n
\n\n\n\nThis query demonstrates full-text matching across both the `customers` and `orders` tables, combined with range filtering and faceting. It searches for customers named \"Alice\" or \"Bob\" and their orders containing \"laptop\", \"phone\", or \"tablet\" with prices above $500. The results are ordered by order ID and faceted by warranty terms.\n\n\n```sql\nSELECT orders.product, name, orders.details.price, orders.tags\nFROM customers\nLEFT JOIN orders ON customers.id = orders.customer_id\nWHERE orders.details.price > 500\nAND MATCH('laptop | phone | tablet', orders)\nAND MATCH('alice | bob', customers)\nORDER BY orders.id ASC\nFACET orders.details.warranty;\n```\n\n\n```json\nPOST /search\n{\n \"table\": \"customers\",\n \"query\": {\n \"bool\": {\n \"must\": [\n {\n \"range\": {\n \"orders.details.price\": {\n \"gt\": 500\n }\n },\n \"query_string\": \"alice | bob\"\n ]\n }\n },\n \"join\": [\n {\n \"type\": \"left\",\n \"table\": \"orders\",\n \"query\": {\n \"query_string\": \"laptop | phone | tablet\"\n },\n \"on\": [\n {\n \"left\": {\n \"table\": \"customers\",\n \"field\": \"id\"\n },\n \"operator\": \"eq\",\n \"right\": {\n \"table\": \"orders\",\n \"field\": \"customer_id\"\n }\n }\n ]\n }\n ],\n \"_source\": [\"orders.product\", \"name\", \"orders.details.price\", \"orders.tags\"],\n \"sort\": [{\"orders.id\": \"asc\"}],\n \"aggs\": {\n \"warranty_facet\": {\n \"terms\": {\n \"field\": \"orders.details.warranty\"\n }\n }\n }\n}\n```\n\n\n```sql\n+-----------------+---------------+----------------------+-------------+\n| orders.product | name | orders.details.price | orders.tags |\n+-----------------+---------------+----------------------+-------------+\n| Laptop Computer | Alice Johnson | 1200 | 101,102 |\n| Smart Phone | Bob Smith | 800 | 103 |\n+-----------------+---------------+----------------------+-------------+\n2 rows in set (0.00 sec)\n\n+-------------------------+----------+\n| orders.details.warranty | count(*) |\n+-------------------------+----------+\n| 2 years | 1 |\n| 1 year | 1 |\n+-------------------------+----------+\n2 rows in set (0.00 sec)\n```\n\n\n\n```json\n{\n \"took\": 0,\n \"timed_out\": false,\n \"hits\": {\n \"total\": 3,\n \"total_relation\": \"eq\",\n \"hits\": [\n {\n \"_id\": 1,\n \"_score\": 1,\n \"orders._score\": 1565,\n \"_source\": {\n \"name\": \"Alice Johnson\",\n \"orders.tags\": [\n 101,\n 102\n ],\n \"orders.product\": \"Laptop Computer\"\n }\n },\n {\n \"_id\": 2,\n \"_score\": 1,\n \"orders._score\": 1565,\n \"_source\": {\n \"name\": \"Bob Smith\",\n \"orders.tags\": [\n 103\n ],\n \"orders.product\": \"Smart Phone\"\n }\n },\n {\n \"_id\": 1,\n \"_score\": 1,\n \"orders._score\": 1565,\n \"_source\": {\n \"name\": \"Alice Johnson\",\n \"orders.tags\": [\n 101,\n 104\n ],\n \"orders.product\": \"Tablet Device\"\n }\n }\n ]\n },\n \"aggregations\": {\n \"warranty_facet\": {\n \"buckets\": [\n {\n \"key\": \"2 years\",\n \"doc_count\": 1\n },\n {\n \"key\": \"1 year\",\n \"doc_count\": 2\n }\n ]\n }\n }\n}\n```\n\n\n\n\n## Search options and match weights\n\nSeparate options can be specified for queries in a join: for the left table and the right table. The syntax is `OPTION()` for SQL queries and one or more subobjects under `\"options\"` for JSON queries.\n\n\n\n\nHere'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 `.weight()` expression.\nIn JSON queries, this weight is represented as `._score`.\n\n\n```sql\nSELECT product, customers.email, customers.name, customers.address, customers.weight()\nFROM orders\nINNER JOIN customers\nON customers.id = orders.customer_id\nWHERE MATCH('maple', customers)\nOPTION(customers) field_weights=(address=1500);\n```\n\n\n```json\nPOST /search\n{\n \"table\": \"orders\",\n \"options\": {\n \"customers\": {\n \"field_weights\": {\n \"address\": 1500\n }\n }\n },\n \"join\": [\n {\n \"type\": \"inner\",\n \"table\": \"customers\",\n \"query\": {\n \"query_string\": \"maple\"\n },\n \"on\": [\n {\n \"left\": {\n \"table\": \"orders\",\n \"field\": \"customer_id\"\n },\n \"operator\": \"eq\",\n \"right\": {\n \"table\": \"customers\",\n \"field\": \"id\"\n }\n }\n ]\n }\n ],\n \"_source\": [\"product\", \"customers.email\", \"customers.name\", \"customers.address\"]\n}\n```\n\n\n\n```sql\n+---------+-------------------+----------------+-------------------+--------------------+\n| product | customers.email | customers.name | customers.address | customers.weight() |\n+---------+-------------------+----------------+-------------------+--------------------+\n| Laptop | alice@example.com | Alice Johnson | 123 Maple St | 1500680 |\n| Tablet | alice@example.com | Alice Johnson | 123 Maple St | 1500680 |\n+---------+-------------------+----------------+-------------------+--------------------+\n2 rows in set (0.00 sec)\n```\n\n\n\n```json\n{\n \"took\": 0,\n \"timed_out\": false,\n \"hits\": {\n \"total\": 2,\n \"total_relation\": \"eq\",\n \"hits\": [\n {\n \"_id\": 1,\n \"_score\": 1,\n \"customers._score\": 15000680,\n \"_source\": {\n \"product\": \"Laptop\",\n \"customers.email\": \"alice@example.com\",\n \"customers.name\": \"Alice Johnson\",\n \"customers.address\": \"123 Maple St\"\n }\n },\n {\n \"_id\": 3,\n \"_score\": 1,\n \"customers._score\": 15000680,\n \"_source\": {\n \"product\": \"Tablet\",\n \"customers.email\": \"alice@example.com\",\n \"customers.name\": \"Alice Johnson\",\n \"customers.address\": \"123 Maple St\"\n }\n }\n ]\n }\n}\n```\n\n\n## Join batching\n\nWhen performing table joins, Manticore Search processes the results in batches to optimize performance and resource usage. Here's how it works:\n\n- **How Batching Works**:\n - The query on the left table is executed first, and the results are accumulated into a batch.\n - This batch is then used as input for the query on the right table, which is executed as a single operation.\n - This approach minimizes the number of queries sent to the right table, improving efficiency.\n\n- **Configuring Batch Size**:\n - The size of the batch can be adjusted using the `join_batch_size` search option.\n - It is also [configurable](../Server_settings/Searchd.md#join_batch_size) in the `searchd` section of the configuration file.\n - The default batch size is `1000`, but you can increase or decrease it depending on your use case.\n - Setting `join_batch_size=0` disables batching entirely, which may be useful for debugging or specific scenarios.\n\n- **Performance Considerations**:\n - A larger batch size can improve performance by reducing the number of queries executed on the right table.\n - However, larger batches may consume more memory, especially for complex queries or large datasets.\n - Experiment with different batch sizes to find the optimal balance between performance and resource usage.\n\n## Join caching\n\nTo further optimize join operations, Manticore Search employs a caching mechanism for queries executed on the right table. Here's what you need to know:\n\n- **How Caching Works**:\n - Each query on the right table is defined by the `JOIN ON` conditions.\n - If the same `JOIN ON` conditions are repeated across multiple queries, the results are cached and reused.\n - This avoids redundant queries and speeds up subsequent join operations.\n\n- **Configuring Cache Size**:\n - The size of the join cache can be configured using the [join_cache_size](../Server_settings/Searchd.md#join_cache_size) option in the `searchd` section of the configuration file.\n - The default cache size is `20MB`, but you can adjust it based on your workload and available memory.\n - Setting `join_cache_size=0` disables caching entirely.\n\n- **Memory Considerations**:\n - Each thread maintains its own cache, so the total memory usage depends on the number of threads and the cache size.\n - Ensure your server has sufficient memory to accommodate the cache, especially for high-concurrency environments.\n\n## Joining distributed tables\n\nDistributed tables consisting only of local tables are supported on both the left and right sides of a join query. However, distributed tables that include remote tables are not supported.\n\n## Caveats and best practices\n\nWhen using JOINs in Manticore Search, keep the following points in mind:\n\n1. **Field selection**: When selecting fields from two tables in a JOIN, prefix fields from the right table with the table name. Left table fields can be used with or without the table prefix. For example:\n ```sql\n SELECT field_name, right_table.field_name FROM ...\n -- or with left table prefix:\n SELECT left_table.field_name, right_table.field_name FROM ...\n ```\n\n2. **JOIN conditions**: Always explicitly specify the table names in your JOIN conditions:\n ```sql\n JOIN ON table_name.some_field = another_table_name.some_field\n ```\n\n3. **Expressions with JOINs**: When using expressions that combine fields from both joined tables, alias the result of the expression:\n ```sql\n SELECT *, (nums2.n + 3) AS x, x * n FROM nums LEFT JOIN nums2 ON nums2.id = nums.num2_id\n ```\n\n4. **Filtering on aliased expressions**: You cannot use aliases for expressions involving fields from both tables in the WHERE clause.\n\n5. **JSON attributes**: When joining on JSON attributes, you must explicitly cast the values to the appropriate type:\n ```sql\n -- Correct:\n SELECT * FROM t1 LEFT JOIN t2 ON int(t1.json_attr.id) = t2.json_attr.id\n\n -- Incorrect:\n SELECT * FROM t1 LEFT JOIN t2 ON t1.json_attr.id = t2.json_attr.id\n ```\n\n6. **NULL handling**: You can use IS NULL and IS NOT NULL conditions on joined fields:\n ```sql\n SELECT * FROM t1 LEFT JOIN t2 ON t1.id = t2.id WHERE t2.name IS NULL\n SELECT * FROM t1 LEFT JOIN t2 ON t1.id = t2.id WHERE t2.name IS NOT NULL\n ```\n\n7. **Using ANY with MVA**: When using the `ANY()` function with multi-valued attributes in JOINs, alias the multi-valued attribute from the joined table:\n ```sql\n SELECT *, t2.m AS alias\n FROM t\n LEFT JOIN t2 ON t.id = t2.t_id\n WHERE ANY(alias) IN (3, 5)\n ```\n\nBy following these guidelines, you can effectively use JOINs in Manticore Search to combine data from multiple indexes and perform complex queries.\n\n\n\n", + "updated_at": 1772599148, + "source_md5": "a4ae13416c3166f34b80b1326bfdde85", + "source_snapshot": "/private/var/folders/19/fcmvcvj57qg026vpv2z93rg80000gn/T/translator-source-d7k0blfjj709d9YBSFH", + "target_snapshot": "/private/var/folders/19/fcmvcvj57qg026vpv2z93rg80000gn/T/translator-target-6s741g60rc3m6H9E9ip" + }, + "e21bcc66d170bd2182f239590ae531cd4ee5963e1aa8481ee6b475f95182774c": { + "original": "This query demonstrates full-text matching across both the `customers` and `orders` tables, combined with range filtering and faceting. It searches for customers named \"Alice\" or \"Bob\" and their orders containing \"laptop\", \"phone\", or \"tablet\" with prices above $500. The results are ordered by order ID and faceted by warranty terms.\n\n\nCODE_BLOCK_25\n\n\nCODE_BLOCK_26\n\n\nCODE_BLOCK_27\n\n\n\nCODE_BLOCK_28\n\n\n\n## Search options and match weights\n\nSeparate options can be specified for queries in a join: for the left table and the right table. The syntax is `OPTION()` for SQL queries and one or more subobjects under `\"options\"` for JSON queries.\n\n\n\nHere'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 `.weight()` expression.\nIn JSON queries, this weight is represented as `._score`.\n\n\nCODE_BLOCK_29\n\n\nCODE_BLOCK_30\n\n\n\nCODE_BLOCK_31\n\n\n\nCODE_BLOCK_32\n\n\n## Join batching\n\nWhen performing table joins, Manticore Search processes the results in batches to optimize performance and resource usage. Here's how it works:\n\n- **How Batching Works**:\n - The query on the left table is executed first, and the results are accumulated into a batch.\n - This batch is then used as input for the query on the right table, which is executed as a single operation.\n - This approach minimizes the number of queries sent to the right table, improving efficiency.\n\n- **Configuring Batch Size**:\n - The size of the batch can be adjusted using the `join_batch_size` search option.\n - It is also [configurable](../Server_settings/Searchd.md#join_batch_size) in the `searchd` section of the configuration file.\n - The default batch size is `1000`, but you can increase or decrease it depending on your use case.\n - Setting `join_batch_size=0` disables batching entirely, which may be useful for debugging or specific scenarios.\n\n- **Performance Considerations**:\n - A larger batch size can improve performance by reducing the number of queries executed on the right table.\n - However, larger batches may consume more memory, especially for complex queries or large datasets.\n - Experiment with different batch sizes to find the optimal balance between performance and resource usage.\n\n## Join caching\n\nTo further optimize join operations, Manticore Search employs a caching mechanism for queries executed on the right table. Here's what you need to know:\n\n- **How Caching Works**:\n - Each query on the right table is defined by the `JOIN ON` conditions.\n - If the same `JOIN ON` conditions are repeated across multiple queries, the results are cached and reused.\n - This avoids redundant queries and speeds up subsequent join operations.\n\n- **Configuring Cache Size**:\n - The size of the join cache can be configured using the [join_cache_size](../Server_settings/Searchd.md#join_cache_size) option in the `searchd` section of the configuration file.\n - The default cache size is `20MB`, but you can adjust it based on your workload and available memory.\n - Setting `join_cache_size=0` disables caching entirely.\n\n- **Memory Considerations**:\n - Each thread maintains its own cache, so the total memory usage depends on the number of threads and the cache size.\n - Ensure your server has sufficient memory to accommodate the cache, especially for high-concurrency environments.\n\n## Joining distributed tables\n\nDistributed tables consisting only of local tables are supported on both the left and right sides of a join query. However, distributed tables that include remote tables are not supported.\n\n## Caveats and best practices\n\nWhen using JOINs in Manticore Search, keep the following points in mind:\n\n1. **Field selection**: When selecting fields from two tables in a JOIN, prefix fields from the right table with the table name. Left table fields can be used with or without the table prefix. For example:\nCODE_BLOCK_33\n\n2. **JOIN conditions**: Always explicitly specify the table names in your JOIN conditions:\nCODE_BLOCK_34\n\n3. **Expressions with JOINs**: When using expressions that combine fields from both joined tables, alias the result of the expression:\nCODE_BLOCK_35\n\n4. **Filtering on aliased expressions**: You cannot use aliases for expressions involving fields from both tables in the WHERE clause.\n\n5. **JSON attributes**: When joining on JSON attributes, you must explicitly cast the values to the appropriate type:\nCODE_BLOCK_36\n\n6. **NULL handling**: You can use IS NULL and IS NOT NULL conditions on joined fields:\nCODE_BLOCK_37\n\n7. **Using ANY with MVA**: When using the `ANY()` function with multi-valued attributes in JOINs, alias the multi-valued attribute from the joined table:\nCODE_BLOCK_38\n\nBy following these guidelines, you can effectively use JOINs in Manticore Search to combine data from multiple indexes and perform complex queries.\n\n\n\n", + "translations": { + "chinese": "\u6b64\u67e5\u8be2\u6f14\u793a\u4e86\u5728 `customers` \u548c `orders` \u8868\u4e2d\u8fdb\u884c\u5168\u6587\u5339\u914d\uff0c\u7ed3\u5408\u8303\u56f4\u8fc7\u6ee4\u548c\u5206\u9762\u3002\u5b83\u641c\u7d22\u540d\u4e3a \"Alice\" \u6216 \"Bob\" \u7684\u5ba2\u6237\u53ca\u5176\u5305\u542b \"laptop\"\u3001\"phone\" \u6216 \"tablet\" \u4e14\u4ef7\u683c\u9ad8\u4e8e 500 \u7f8e\u5143\u7684\u8ba2\u5355\u3002\u7ed3\u679c\u6309\u8ba2\u5355 ID \u6392\u5e8f\uff0c\u5e76\u6309\u4fdd\u4fee\u6761\u6b3e\u8fdb\u884c\u5206\u9762\u3002\n\n\nCODE_BLOCK_25\n\n\nCODE_BLOCK_26\n\n\nCODE_BLOCK_27\n\n\n\nCODE_BLOCK_28\n\n\n\n## \u641c\u7d22\u9009\u9879\u548c\u5339\u914d\u6743\u91cd\n\n\u53ef\u4ee5\u5728\u8fde\u63a5\u67e5\u8be2\u4e2d\u4e3a\u5de6\u53f3\u8868\u5206\u522b\u6307\u5b9a\u9009\u9879\u3002SQL \u67e5\u8be2\u7684\u8bed\u6cd5\u662f `OPTION()`\uff0cJSON \u67e5\u8be2\u5219\u5728 `\"options\"` \u4e0b\u4f7f\u7528\u4e00\u4e2a\u6216\u591a\u4e2a\u5b50\u5bf9\u8c61\u3002\n\n\n\n\u4ee5\u4e0b\u662f\u5982\u4f55\u4e3a\u53f3\u8868\u7684\u5168\u6587\u67e5\u8be2\u6307\u5b9a\u4e0d\u540c\u5b57\u6bb5\u6743\u91cd\u7684\u793a\u4f8b\u3002\u8981\u901a\u8fc7 SQL \u83b7\u53d6\u5339\u914d\u6743\u91cd\uff0c\u8bf7\u4f7f\u7528 `.weight()` \u8868\u8fbe\u5f0f\u3002\n\u5728 JSON \u67e5\u8be2\u4e2d\uff0c\u6b64\u6743\u91cd\u8868\u793a\u4e3a `._score`\u3002\n\n\nCODE_BLOCK_29\n\n\nCODE_BLOCK_30\n\n\n\nCODE_BLOCK_31\n\n\n\nCODE_BLOCK_32\n\n\n## \u8fde\u63a5\u6279\u91cf\u5904\u7406\n\n\u5728\u6267\u884c\u8868\u8fde\u63a5\u65f6\uff0cManticore Search \u4ee5\u6279\u6b21\u65b9\u5f0f\u5904\u7406\u7ed3\u679c\uff0c\u4ee5\u4f18\u5316\u6027\u80fd\u548c\u8d44\u6e90\u4f7f\u7528\u3002\u4ee5\u4e0b\u662f\u5176\u5de5\u4f5c\u539f\u7406\uff1a\n\n- **\u6279\u91cf\u5904\u7406\u673a\u5236**\uff1a\n - \u9996\u5148\u6267\u884c\u5de6\u8868\u7684\u67e5\u8be2\uff0c\u5e76\u5c06\u7ed3\u679c\u7d2f\u79ef\u5230\u4e00\u4e2a\u6279\u6b21\u4e2d\u3002\n - \u7136\u540e\u5c06\u6b64\u6279\u6b21\u4f5c\u4e3a\u53f3\u8868\u67e5\u8be2\u7684\u8f93\u5165\uff0c\u4ee5\u5355\u4e2a\u64cd\u4f5c\u6267\u884c\u3002\n - \u8fd9\u79cd\u65b9\u6cd5\u51cf\u5c11\u4e86\u53d1\u9001\u5230\u53f3\u8868\u7684\u67e5\u8be2\u6b21\u6570\uff0c\u63d0\u9ad8\u4e86\u6548\u7387\u3002\n\n- **\u914d\u7f6e\u6279\u6b21\u5927\u5c0f**\uff1a\n - \u53ef\u4ee5\u4f7f\u7528 `join_batch_size` \u641c\u7d22\u9009\u9879\u8c03\u6574\u6279\u6b21\u5927\u5c0f\u3002\n - \u4e5f\u53ef\u4ee5\u5728\u914d\u7f6e\u6587\u4ef6\u7684 `searchd` \u90e8\u5206\u4e2d [\u914d\u7f6e](../Server_settings/Searchd.md#join_batch_size)\u3002\n - \u9ed8\u8ba4\u6279\u6b21\u5927\u5c0f\u4e3a `1000`\uff0c\u4f46\u53ef\u6839\u636e\u4f7f\u7528\u60c5\u51b5\u589e\u52a0\u6216\u51cf\u5c11\u3002\n - \u8bbe\u7f6e `join_batch_size=0` \u53ef\u5b8c\u5168\u7981\u7528\u6279\u5904\u7406\uff0c\u8fd9\u5728\u8c03\u8bd5\u6216\u7279\u5b9a\u573a\u666f\u4e2d\u53ef\u80fd\u6709\u7528\u3002\n\n- **\u6027\u80fd\u8003\u8651**\uff1a\n - \u66f4\u5927\u7684\u6279\u6b21\u5927\u5c0f\u53ef\u4ee5\u901a\u8fc7\u51cf\u5c11\u5bf9\u53f3\u8868\u6267\u884c\u7684\u67e5\u8be2\u6b21\u6570\u6765\u63d0\u9ad8\u6027\u80fd\u3002\n - \u7136\u800c\uff0c\u66f4\u5927\u7684\u6279\u6b21\u53ef\u80fd\u4f1a\u6d88\u8017\u66f4\u591a\u5185\u5b58\uff0c\u5c24\u5176\u662f\u5728\u590d\u6742\u67e5\u8be2\u6216\u5927\u578b\u6570\u636e\u96c6\u7684\u60c5\u51b5\u4e0b\u3002\n - \u901a\u8fc7\u5c1d\u8bd5\u4e0d\u540c\u7684\u6279\u6b21\u5927\u5c0f\uff0c\u627e\u5230\u6027\u80fd\u548c\u8d44\u6e90\u4f7f\u7528\u4e4b\u95f4\u7684\u6700\u4f73\u5e73\u8861\u3002\n\n## \u8fde\u63a5\u7f13\u5b58\n\n\u4e3a\u4e86\u8fdb\u4e00\u6b65\u4f18\u5316\u8fde\u63a5\u64cd\u4f5c\uff0cManticore Search \u4e3a\u5728\u53f3\u8868\u4e0a\u6267\u884c\u7684\u67e5\u8be2\u91c7\u7528\u7f13\u5b58\u673a\u5236\u3002\u4ee5\u4e0b\u662f\u9700\u8981\u4e86\u89e3\u7684\u5185\u5bb9\uff1a\n\n- **\u7f13\u5b58\u673a\u5236**\uff1a\n - \u6bcf\u4e2a\u53f3\u8868\u67e5\u8be2\u7531 `JOIN ON` \u6761\u4ef6\u5b9a\u4e49\u3002\n - \u5982\u679c\u591a\u4e2a\u67e5\u8be2\u91cd\u590d\u4f7f\u7528\u76f8\u540c\u7684 `JOIN ON` \u6761\u4ef6\uff0c\u7ed3\u679c\u5c06\u88ab\u7f13\u5b58\u5e76\u91cd\u7528\u3002\n - \u8fd9\u907f\u514d\u4e86\u5197\u4f59\u67e5\u8be2\uff0c\u52a0\u5feb\u4e86\u540e\u7eed\u8fde\u63a5\u64cd\u4f5c\u7684\u901f\u5ea6\u3002\n\n- **\u914d\u7f6e\u7f13\u5b58\u5927\u5c0f**\uff1a\n - \u53ef\u4ee5\u5728\u914d\u7f6e\u6587\u4ef6\u7684 `searchd` \u90e8\u5206\u4e2d\u4f7f\u7528 [join_cache_size](../Server_settings/Searchd.md#join_cache_size) \u9009\u9879\u914d\u7f6e\u8fde\u63a5\u7f13\u5b58\u5927\u5c0f\u3002\n - \u9ed8\u8ba4\u7f13\u5b58\u5927\u5c0f\u4e3a `20MB`\uff0c\u4f46\u53ef\u4ee5\u6839\u636e\u5de5\u4f5c\u8d1f\u8f7d\u548c\u53ef\u7528\u5185\u5b58\u8fdb\u884c\u8c03\u6574\u3002\n - \u8bbe\u7f6e `join_cache_size=0` \u53ef\u5b8c\u5168\u7981\u7528\u7f13\u5b58\u3002\n\n- **\u5185\u5b58\u8003\u8651**\uff1a\n - \u6bcf\u4e2a\u7ebf\u7a0b\u7ef4\u62a4\u81ea\u5df1\u7684\u7f13\u5b58\uff0c\u56e0\u6b64\u603b\u5185\u5b58\u4f7f\u7528\u91cf\u53d6\u51b3\u4e8e\u7ebf\u7a0b\u6570\u548c\u7f13\u5b58\u5927\u5c0f\u3002\n - \u786e\u4fdd\u670d\u52a1\u5668\u6709\u8db3\u591f\u7684\u5185\u5b58\u6765\u5bb9\u7eb3\u7f13\u5b58\uff0c\u5c24\u5176\u662f\u5728\u9ad8\u5e76\u53d1\u73af\u5883\u4e2d\u3002\n\n## \u8fde\u63a5\u5206\u5e03\u5f0f\u8868\n\nManticore Search \u652f\u6301\u5728\u8fde\u63a5\u67e5\u8be2\u7684\u5de6\u53f3\u4e24\u4fa7\u4f7f\u7528\u4ec5\u7531\u672c\u5730\u8868\u7ec4\u6210\u7684\u5206\u5e03\u5f0f\u8868\u3002\u4f46\u662f\uff0c\u5305\u542b\u8fdc\u7a0b\u8868\u7684\u5206\u5e03\u5f0f\u8868\u4e0d\u88ab\u652f\u6301\u3002\n\n## \u6ce8\u610f\u4e8b\u9879\u548c\u6700\u4f73\u5b9e\u8df5\n\n\u5728 Manticore Search \u4e2d\u4f7f\u7528 JOIN \u65f6\uff0c\u8bf7\u6ce8\u610f\u4ee5\u4e0b\u51e0\u70b9\uff1a\n\n1. **\u5b57\u6bb5\u9009\u62e9**\uff1a\u5728 JOIN \u4e2d\u4ece\u4e24\u4e2a\u8868\u9009\u62e9\u5b57\u6bb5\u65f6\uff0c\u53f3\u8868\u7684\u5b57\u6bb5\u9700\u4ee5\u8868\u540d\u4f5c\u4e3a\u524d\u7f00\u3002\u5de6\u8868\u7684\u5b57\u6bb5\u53ef\u4ee5\u4f7f\u7528\u6216\u4e0d\u4f7f\u7528\u8868\u540d\u524d\u7f00\u3002\u4f8b\u5982\uff1a\nCODE_BLOCK_33\n\n2. **JOIN \u6761\u4ef6**\uff1a\u59cb\u7ec8\u5728 JOIN \u6761\u4ef6\u4e2d\u663e\u5f0f\u6307\u5b9a\u8868\u540d\uff1a\nCODE_BLOCK_34\n\n3. **\u5305\u542b JOIN \u7684\u8868\u8fbe\u5f0f**\uff1a\u5f53\u4f7f\u7528\u7ed3\u5408\u4e24\u4e2a\u8fde\u63a5\u8868\u5b57\u6bb5\u7684\u8868\u8fbe\u5f0f\u65f6\uff0c\u8bf7\u4e3a\u8868\u8fbe\u5f0f\u7684\u7ed3\u679c\u8bbe\u7f6e\u522b\u540d\uff1a\nCODE_BLOCK_35\n\n4. **\u5728 WHERE \u5b50\u53e5\u4e2d\u4f7f\u7528\u522b\u540d\u8868\u8fbe\u5f0f**\uff1a\u4e0d\u80fd\u5728 WHERE \u5b50\u53e5\u4e2d\u4f7f\u7528\u6d89\u53ca\u4e24\u4e2a\u8868\u5b57\u6bb5\u7684\u8868\u8fbe\u5f0f\u7684\u522b\u540d\u3002\n\n5. **JSON \u5c5e\u6027**\uff1a\u5728\u4f7f\u7528 JSON \u5c5e\u6027\u8fdb\u884c\u8fde\u63a5\u65f6\uff0c\u5fc5\u987b\u663e\u5f0f\u5730\u5c06\u503c\u8f6c\u6362\u4e3a\u9002\u5f53\u7684\u7c7b\u578b\uff1a\nCODE_BLOCK_36\n\n6. **NULL \u5904\u7406**\uff1a\u53ef\u4ee5\u5728\u8fde\u63a5\u5b57\u6bb5\u4e0a\u4f7f\u7528 IS NULL \u548c IS NOT NULL \u6761\u4ef6\uff1a\nCODE_BLOCK_37\n\n7. **\u4f7f\u7528 ANY \u4e0e MVA**\uff1a\u5728\u4f7f\u7528 `ANY()` \u51fd\u6570\u4e0e\u591a\u503c\u5c5e\u6027\u8fdb\u884c\u8fde\u63a5\u65f6\uff0c\u8bf7\u4e3a\u8fde\u63a5\u8868\u7684\u591a\u503c\u5c5e\u6027\u8bbe\u7f6e\u522b\u540d\uff1a\nCODE_BLOCK_38\n\n\u9075\u5faa\u8fd9\u4e9b\u6307\u5357\uff0c\u60a8\u53ef\u4ee5\u6709\u6548\u5730\u5728 Manticore Search \u4e2d\u4f7f\u7528 JOIN\uff0c\u5c06\u591a\u4e2a\u7d22\u5f15\u7684\u6570\u636e\u7ec4\u5408\u8d77\u6765\u5e76\u6267\u884c\u590d\u6742\u67e5\u8be2\u3002\n\n\n\n", + "russian": "\u0414\u0430\u043d\u043d\u044b\u0439 \u0437\u0430\u043f\u0440\u043e\u0441 \u0434\u0435\u043c\u043e\u043d\u0441\u0442\u0440\u0438\u0440\u0443\u0435\u0442 \u043f\u043e\u043b\u043d\u043e\u0442\u0435\u043a\u0441\u0442\u043e\u0432\u044b\u0439 \u043f\u043e\u0438\u0441\u043a \u043f\u043e \u0442\u0430\u0431\u043b\u0438\u0446\u0430\u043c `customers` \u0438 `orders`, \u0441\u043e\u0432\u043c\u0435\u0449\u0451\u043d\u043d\u044b\u0439 \u0441 \u0444\u0438\u043b\u044c\u0442\u0440\u0430\u0446\u0438\u0435\u0439 \u043f\u043e \u0434\u0438\u0430\u043f\u0430\u0437\u043e\u043d\u0443 \u0438 \u0444\u0430\u0441\u0435\u0442\u043d\u043e\u0439 \u0433\u0440\u0443\u043f\u043f\u0438\u0440\u043e\u0432\u043a\u043e\u0439. \u041e\u043d \u0438\u0449\u0435\u0442 \u043a\u043b\u0438\u0435\u043d\u0442\u043e\u0432 \u0441 \u0438\u043c\u0435\u043d\u0430\u043c\u0438 \"Alice\" \u0438\u043b\u0438 \"Bob\" \u0438 \u0438\u0445 \u0437\u0430\u043a\u0430\u0437\u044b, \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u0449\u0438\u0435 \"laptop\", \"phone\" \u0438\u043b\u0438 \"tablet\", \u0441 \u0446\u0435\u043d\u043e\u0439 \u0432\u044b\u0448\u0435 $500. \u0420\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u044b \u0443\u043f\u043e\u0440\u044f\u0434\u043e\u0447\u0435\u043d\u044b \u043f\u043e \u0438\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0440\u0443 \u0437\u0430\u043a\u0430\u0437\u0430 \u0438 \u0441\u0433\u0440\u0443\u043f\u043f\u0438\u0440\u043e\u0432\u0430\u043d\u044b \u043f\u043e \u0443\u0441\u043b\u043e\u0432\u0438\u044f\u043c \u0433\u0430\u0440\u0430\u043d\u0442\u0438\u0438.\n\n\nCODE_BLOCK_25\n\n\nCODE_BLOCK_26\n\n\nCODE_BLOCK_27\n\n\n\nCODE_BLOCK_28\n\n\n\n## \u041f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u043f\u043e\u0438\u0441\u043a\u0430 \u0438 \u0432\u0435\u0441\u0430 \u0441\u043e\u0432\u043f\u0430\u0434\u0435\u043d\u0438\u0439\n\n\u0414\u043b\u044f \u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432 \u0432 \u043e\u0431\u044a\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0438 \u043c\u043e\u0436\u043d\u043e \u0437\u0430\u0434\u0430\u0432\u0430\u0442\u044c \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438: \u0434\u043b\u044f \u043b\u0435\u0432\u043e\u0439 \u0442\u0430\u0431\u043b\u0438\u0446\u044b \u0438 \u0434\u043b\u044f \u043f\u0440\u0430\u0432\u043e\u0439. \u0421\u0438\u043d\u0442\u0430\u043a\u0441\u0438\u0441: `OPTION()` \u0434\u043b\u044f SQL1\u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432 \u0438 \u043e\u0434\u0438\u043d \u0438\u043b\u0438 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0434\u043e\u0447\u0435\u0440\u043d\u0438\u0445 \u043e\u0431\u044a\u0435\u043a\u0442\u043e\u0432 \u0432 `\"options\"` \u0434\u043b\u044f JSON1\u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432.\n\n\n\n\u0412\u043e\u0442 \u043f\u0440\u0438\u043c\u0435\u0440, \u043a\u0430\u043a \u0437\u0430\u0434\u0430\u0442\u044c \u0440\u0430\u0437\u043d\u044b\u0435 \u0432\u0435\u0441\u0430 \u043f\u043e\u043b\u0435\u0439 \u0434\u043b\u044f \u043f\u043e\u043b\u043d\u043e\u0442\u0435\u043a\u0441\u0442\u043e\u0432\u043e\u0433\u043e \u0437\u0430\u043f\u0440\u043e\u0441\u0430 \u043f\u043e \u043f\u0440\u0430\u0432\u043e\u0439 \u0442\u0430\u0431\u043b\u0438\u0446\u0435. \u0427\u0442\u043e\u0431\u044b \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c \u0432\u0435\u0441\u0430 \u0441\u043e\u0432\u043f\u0430\u0434\u0435\u043d\u0438\u0439 \u0447\u0435\u0440\u0435\u0437 SQL, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0439\u0442\u0435 \u0432\u044b\u0440\u0430\u0436\u0435\u043d\u0438\u0435 `.weight()`.\n\u0412 JSON1\u0437\u0430\u043f\u0440\u043e\u0441\u0430\u0445 \u044d\u0442\u043e\u0442 \u0432\u0435\u0441 \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d \u043a\u0430\u043a `._score`.\n\n\nCODE_BLOCK_29\n\n\nCODE_BLOCK_30\n\n\n\nCODE_BLOCK_31\n\n\n\nCODE_BLOCK_32\n\n\n## \u041f\u0430\u043a\u0435\u0442\u043d\u0430\u044f \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0430 \u043e\u0431\u044a\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0439\n\n\u041f\u0440\u0438 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0438 \u043e\u0431\u044a\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u044f \u0442\u0430\u0431\u043b\u0438\u0446 Manticore Search \u043e\u0431\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u0435\u0442 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u044b \u043f\u0430\u043a\u0435\u0442\u0430\u043c\u0438 \u0434\u043b\u044f \u043e\u043f\u0442\u0438\u043c\u0438\u0437\u0430\u0446\u0438\u0438 \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u0438 \u0438 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044f \u0440\u0435\u0441\u0443\u0440\u0441\u043e\u0432. \u0412\u043e\u0442 \u043a\u0430\u043a \u044d\u0442\u043e \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442:\n\n- **\u041f\u0440\u0438\u043d\u0446\u0438\u043f \u043f\u0430\u043a\u0435\u0442\u043d\u043e\u0439 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0438**:\n - \u0421\u043d\u0430\u0447\u0430\u043b\u0430 \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u0442\u0441\u044f \u0437\u0430\u043f\u0440\u043e\u0441 \u043f\u043e \u043b\u0435\u0432\u043e\u0439 \u0442\u0430\u0431\u043b\u0438\u0446\u0435, \u0438 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u044b \u043d\u0430\u043a\u0430\u043f\u043b\u0438\u0432\u0430\u044e\u0442\u0441\u044f \u0432 \u043f\u0430\u043a\u0435\u0442.\n - \u0417\u0430\u0442\u0435\u043c \u044d\u0442\u043e\u0442 \u043f\u0430\u043a\u0435\u0442 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u043a\u0430\u043a \u0432\u0445\u043e\u0434\u043d\u044b\u0435 \u0434\u0430\u043d\u043d\u044b\u0435 \u0434\u043b\u044f \u0437\u0430\u043f\u0440\u043e\u0441\u0430 \u043f\u043e \u043f\u0440\u0430\u0432\u043e\u0439 \u0442\u0430\u0431\u043b\u0438\u0446\u0435, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u0442\u0441\u044f \u043a\u0430\u043a \u0435\u0434\u0438\u043d\u0430\u044f \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u044f.\n - \u0422\u0430\u043a\u043e\u0439 \u043f\u043e\u0434\u0445\u043e\u0434 \u043c\u0438\u043d\u0438\u043c\u0438\u0437\u0438\u0440\u0443\u0435\u0442 \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432 \u043a \u043f\u0440\u0430\u0432\u043e\u0439 \u0442\u0430\u0431\u043b\u0438\u0446\u0435, \u043f\u043e\u0432\u044b\u0448\u0430\u044f \u044d\u0444\u0444\u0435\u043a\u0442\u0438\u0432\u043d\u043e\u0441\u0442\u044c.\n\n- **\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0440\u0430\u0437\u043c\u0435\u0440\u0430 \u043f\u0430\u043a\u0435\u0442\u0430**:\n - \u0420\u0430\u0437\u043c\u0435\u0440 \u043f\u0430\u043a\u0435\u0442\u0430 \u043c\u043e\u0436\u043d\u043e \u0440\u0435\u0433\u0443\u043b\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u043e\u043f\u0446\u0438\u0438 \u043f\u043e\u0438\u0441\u043a\u0430 `join_batch_size`.\n - \u0415\u0433\u043e \u0442\u0430\u043a\u0436\u0435 \u043c\u043e\u0436\u043d\u043e [\u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c](../Server_settings/Searchd.md#join_batch_size) \u0432 \u0440\u0430\u0437\u0434\u0435\u043b\u0435 `searchd` \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u043e\u043d\u043d\u043e\u0433\u043e \u0444\u0430\u0439\u043b\u0430.\n - \u0420\u0430\u0437\u043c\u0435\u0440 \u043f\u0430\u043a\u0435\u0442\u0430 \u043f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e \u2014 `1000`, \u043d\u043e \u0432\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u0443\u0432\u0435\u043b\u0438\u0447\u0438\u0442\u044c \u0438\u043b\u0438 \u0443\u043c\u0435\u043d\u044c\u0448\u0438\u0442\u044c \u0435\u0433\u043e \u0432 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438 \u043e\u0442 \u0432\u0430\u0448\u0435\u0433\u043e \u0441\u0446\u0435\u043d\u0430\u0440\u0438\u044f \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044f.\n - \u0423\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0430 `join_batch_size=0` \u043f\u043e\u043b\u043d\u043e\u0441\u0442\u044c\u044e \u043e\u0442\u043a\u043b\u044e\u0447\u0430\u0435\u0442 \u043f\u0430\u043a\u0435\u0442\u043d\u0443\u044e \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0443, \u0447\u0442\u043e \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u043f\u043e\u043b\u0435\u0437\u043d\u043e \u0434\u043b\u044f \u043e\u0442\u043b\u0430\u0434\u043a\u0438 \u0438\u043b\u0438 \u0441\u043f\u0435\u0446\u0438\u0444\u0438\u0447\u0435\u0441\u043a\u0438\u0445 \u0441\u043b\u0443\u0447\u0430\u0435\u0432.\n\n- **\u0421\u043e\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u0438**:\n - \u0423\u0432\u0435\u043b\u0438\u0447\u0435\u043d\u0438\u0435 \u0440\u0430\u0437\u043c\u0435\u0440\u0430 \u043f\u0430\u043a\u0435\u0442\u0430 \u043c\u043e\u0436\u0435\u0442 \u0443\u043b\u0443\u0447\u0448\u0438\u0442\u044c \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u044c, \u0441\u043e\u043a\u0440\u0430\u0449\u0430\u044f \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u043c\u044b\u0445 \u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432 \u043a \u043f\u0440\u0430\u0432\u043e\u0439 \u0442\u0430\u0431\u043b\u0438\u0446\u0435.\n - \u041e\u0434\u043d\u0430\u043a\u043e \u0431\u043e\u043b\u044c\u0448\u0438\u0435 \u043f\u0430\u043a\u0435\u0442\u044b \u043c\u043e\u0433\u0443\u0442 \u043f\u043e\u0442\u0440\u0435\u0431\u043b\u044f\u0442\u044c \u0431\u043e\u043b\u044c\u0448\u0435 \u043f\u0430\u043c\u044f\u0442\u0438, \u043e\u0441\u043e\u0431\u0435\u043d\u043d\u043e \u0434\u043b\u044f \u0441\u043b\u043e\u0436\u043d\u044b\u0445 \u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432 \u0438\u043b\u0438 \u0431\u043e\u043b\u044c\u0448\u0438\u0445 \u043d\u0430\u0431\u043e\u0440\u043e\u0432 \u0434\u0430\u043d\u043d\u044b\u0445.\n - \u042d\u043a\u0441\u043f\u0435\u0440\u0438\u043c\u0435\u043d\u0442\u0438\u0440\u0443\u0439\u0442\u0435 \u0441 \u0440\u0430\u0437\u043d\u044b\u043c\u0438 \u0440\u0430\u0437\u043c\u0435\u0440\u0430\u043c\u0438 \u043f\u0430\u043a\u0435\u0442\u043e\u0432, \u0447\u0442\u043e\u0431\u044b \u043d\u0430\u0439\u0442\u0438 \u043e\u043f\u0442\u0438\u043c\u0430\u043b\u044c\u043d\u044b\u0439 \u0431\u0430\u043b\u0430\u043d\u0441 \u043c\u0435\u0436\u0434\u0443 \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u044c\u044e \u0438 \u043f\u043e\u0442\u0440\u0435\u0431\u043b\u0435\u043d\u0438\u0435\u043c \u0440\u0435\u0441\u0443\u0440\u0441\u043e\u0432.\n\n## \u041a\u044d\u0448\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u043e\u0431\u044a\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0439\n\n\u0414\u043b\u044f \u0434\u0430\u043b\u044c\u043d\u0435\u0439\u0448\u0435\u0439 \u043e\u043f\u0442\u0438\u043c\u0438\u0437\u0430\u0446\u0438\u0438 \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u0439 \u043e\u0431\u044a\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u044f Manticore Search \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442 \u043c\u0435\u0445\u0430\u043d\u0438\u0437\u043c \u043a\u044d\u0448\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432, \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u043c\u044b\u0445 \u043f\u043e \u043f\u0440\u0430\u0432\u043e\u0439 \u0442\u0430\u0431\u043b\u0438\u0446\u0435. \u0412\u043e\u0442 \u0447\u0442\u043e \u043d\u0443\u0436\u043d\u043e \u0437\u043d\u0430\u0442\u044c:\n\n- **\u041f\u0440\u0438\u043d\u0446\u0438\u043f \u0440\u0430\u0431\u043e\u0442\u044b \u043a\u044d\u0448\u0430**:\n - \u041a\u0430\u0436\u0434\u044b\u0439 \u0437\u0430\u043f\u0440\u043e\u0441 \u043a \u043f\u0440\u0430\u0432\u043e\u0439 \u0442\u0430\u0431\u043b\u0438\u0446\u0435 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u0442\u0441\u044f \u0443\u0441\u043b\u043e\u0432\u0438\u044f\u043c\u0438 `JOIN ON`.\n - \u0415\u0441\u043b\u0438 \u043e\u0434\u0438\u043d\u0430\u043a\u043e\u0432\u044b\u0435 \u0443\u0441\u043b\u043e\u0432\u0438\u044f `JOIN ON` \u043f\u043e\u0432\u0442\u043e\u0440\u044f\u044e\u0442\u0441\u044f \u0432 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u0438\u0445 \u0437\u0430\u043f\u0440\u043e\u0441\u0430\u0445, \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u044b \u043a\u044d\u0448\u0438\u0440\u0443\u044e\u0442\u0441\u044f \u0438 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044e\u0442\u0441\u044f \u043f\u043e\u0432\u0442\u043e\u0440\u043d\u043e.\n - \u042d\u0442\u043e \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u0438\u0437\u0431\u0435\u0436\u0430\u0442\u044c \u0438\u0437\u0431\u044b\u0442\u043e\u0447\u043d\u044b\u0445 \u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432 \u0438 \u0443\u0441\u043a\u043e\u0440\u0438\u0442\u044c \u043f\u043e\u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0435 \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u0438 \u043e\u0431\u044a\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u044f.\n\n- **\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0440\u0430\u0437\u043c\u0435\u0440\u0430 \u043a\u044d\u0448\u0430**:\n - \u0420\u0430\u0437\u043c\u0435\u0440 \u043a\u044d\u0448\u0430 \u043e\u0431\u044a\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0439 \u043c\u043e\u0436\u043d\u043e \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u043e\u043f\u0446\u0438\u0438 [join_cache_size](../Server_settings/Searchd.md#join_cache_size) \u0432 \u0440\u0430\u0437\u0434\u0435\u043b\u0435 `searchd` \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u043e\u043d\u043d\u043e\u0433\u043e \u0444\u0430\u0439\u043b\u0430.\n - \u0420\u0430\u0437\u043c\u0435\u0440 \u043a\u044d\u0448\u0430 \u043f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e \u2014 `20MB`, \u043d\u043e \u0432\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u0438\u0437\u043c\u0435\u043d\u0438\u0442\u044c \u0435\u0433\u043e \u0432 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438 \u043e\u0442 \u043d\u0430\u0433\u0440\u0443\u0437\u043a\u0438 \u0438 \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u043e\u0439 \u043f\u0430\u043c\u044f\u0442\u0438.\n - \u0423\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0430 `join_cache_size=0` \u043f\u043e\u043b\u043d\u043e\u0441\u0442\u044c\u044e \u043e\u0442\u043a\u043b\u044e\u0447\u0430\u0435\u0442 \u043a\u044d\u0448\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435.\n\n- **\u0421\u043e\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f \u043f\u043e \u043f\u0430\u043c\u044f\u0442\u0438**:\n - \u041a\u0430\u0436\u0434\u044b\u0439 \u043f\u043e\u0442\u043e\u043a \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442 \u0441\u0432\u043e\u0439 \u0441\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u043d\u044b\u0439 \u043a\u044d\u0448, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u043e\u0431\u0449\u0435\u0435 \u043f\u043e\u0442\u0440\u0435\u0431\u043b\u0435\u043d\u0438\u0435 \u043f\u0430\u043c\u044f\u0442\u0438 \u0437\u0430\u0432\u0438\u0441\u0438\u0442 \u043e\u0442 \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u0430 \u043f\u043e\u0442\u043e\u043a\u043e\u0432 \u0438 \u0440\u0430\u0437\u043c\u0435\u0440\u0430 \u043a\u044d\u0448\u0430.\n - \u0423\u0431\u0435\u0434\u0438\u0442\u0435\u0441\u044c, \u0447\u0442\u043e \u0432\u0430\u0448 \u0441\u0435\u0440\u0432\u0435\u0440 \u043e\u0431\u043b\u0430\u0434\u0430\u0435\u0442 \u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e\u0439 \u043f\u0430\u043c\u044f\u0442\u044c\u044e \u0434\u043b\u044f \u0440\u0430\u0437\u043c\u0435\u0449\u0435\u043d\u0438\u044f \u043a\u044d\u0448\u0430, \u043e\u0441\u043e\u0431\u0435\u043d\u043d\u043e \u0432 \u0441\u0440\u0435\u0434\u0430\u0445 \u0441 \u0432\u044b\u0441\u043e\u043a\u043e\u0439 \u043f\u0430\u0440\u0430\u043b\u043b\u0435\u043b\u044c\u043d\u043e\u0439 \u043d\u0430\u0433\u0440\u0443\u0437\u043a\u043e\u0439.\n\n## \u041e\u0431\u044a\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0435 \u0440\u0430\u0441\u043f\u0440\u0435\u0434\u0435\u043b\u0451\u043d\u043d\u044b\u0445 \u0442\u0430\u0431\u043b\u0438\u0446\n\n\u0420\u0430\u0441\u043f\u0440\u0435\u0434\u0435\u043b\u0451\u043d\u043d\u044b\u0435 \u0442\u0430\u0431\u043b\u0438\u0446\u044b, \u0441\u043e\u0441\u0442\u043e\u044f\u0449\u0438\u0435 \u0442\u043e\u043b\u044c\u043a\u043e \u0438\u0437 \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u044b\u0445 \u0442\u0430\u0431\u043b\u0438\u0446, \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u044e\u0442\u0441\u044f \u043a\u0430\u043a \u043d\u0430 \u043b\u0435\u0432\u043e\u0439, \u0442\u0430\u043a \u0438 \u043d\u0430 \u043f\u0440\u0430\u0432\u043e\u0439 \u0441\u0442\u043e\u0440\u043e\u043d\u0435 \u043e\u0431\u044a\u0435\u0434\u0438\u043d\u044f\u044e\u0449\u0435\u0433\u043e \u0437\u0430\u043f\u0440\u043e\u0441\u0430. \u041e\u0434\u043d\u0430\u043a\u043e \u0440\u0430\u0441\u043f\u0440\u0435\u0434\u0435\u043b\u0451\u043d\u043d\u044b\u0435 \u0442\u0430\u0431\u043b\u0438\u0446\u044b, \u0432\u043a\u043b\u044e\u0447\u0430\u044e\u0449\u0438\u0435 \u0443\u0434\u0430\u043b\u0451\u043d\u043d\u044b\u0435 \u0442\u0430\u0431\u043b\u0438\u0446\u044b, \u043d\u0435 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u044e\u0442\u0441\u044f.\n\n## \u041e\u0441\u043e\u0431\u0435\u043d\u043d\u043e\u0441\u0442\u0438 \u0438 \u0440\u0435\u043a\u043e\u043c\u0435\u043d\u0434\u0430\u0446\u0438\u0438\n\n\u041f\u0440\u0438 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0438 \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u0439 JOIN \u0432 Manticore Search \u0443\u0447\u0438\u0442\u044b\u0432\u0430\u0439\u0442\u0435 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0435 \u043c\u043e\u043c\u0435\u043d\u0442\u044b:\n\n1. **\u0412\u044b\u0431\u043e\u0440 \u043f\u043e\u043b\u0435\u0439**: \u041f\u0440\u0438 \u0432\u044b\u0431\u043e\u0440\u0435 \u043f\u043e\u043b\u0435\u0439 \u0438\u0437 \u0434\u0432\u0443\u0445 \u0442\u0430\u0431\u043b\u0438\u0446 \u0432 JOIN \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0439\u0442\u0435 \u043f\u0440\u0435\u0444\u0438\u043a\u0441 \u0438\u043c\u0435\u043d\u0438 \u0442\u0430\u0431\u043b\u0438\u0446\u044b \u043a \u043f\u043e\u043b\u044f\u043c \u0438\u0437 \u043f\u0440\u0430\u0432\u043e\u0439 \u0442\u0430\u0431\u043b\u0438\u0446\u044b. \u041f\u043e\u043b\u044f \u043b\u0435\u0432\u043e\u0439 \u0442\u0430\u0431\u043b\u0438\u0446\u044b \u043c\u043e\u0436\u043d\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u043a\u0430\u043a \u0441 \u043f\u0440\u0435\u0444\u0438\u043a\u0441\u043e\u043c \u0442\u0430\u0431\u043b\u0438\u0446\u044b, \u0442\u0430\u043a \u0438 \u0431\u0435\u0437 \u043d\u0435\u0433\u043e. \u041d\u0430\u043f\u0440\u0438\u043c\u0435\u0440:\nCODE_BLOCK_33\n\n2. **\u0423\u0441\u043b\u043e\u0432\u0438\u044f JOIN**: \u0412\u0441\u0435\u0433\u0434\u0430 \u044f\u0432\u043d\u043e \u0443\u043a\u0430\u0437\u044b\u0432\u0430\u0439\u0442\u0435 \u0438\u043c\u0435\u043d\u0430 \u0442\u0430\u0431\u043b\u0438\u0446 \u0432 \u0443\u0441\u043b\u043e\u0432\u0438\u044f\u0445 JOIN:\nCODE_BLOCK_34\n\n3. **\u0412\u044b\u0440\u0430\u0436\u0435\u043d\u0438\u044f \u0441 JOIN**: \u041f\u0440\u0438 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0438 \u0432\u044b\u0440\u0430\u0436\u0435\u043d\u0438\u0439, \u043e\u0431\u044a\u0435\u0434\u0438\u043d\u044f\u044e\u0449\u0438\u0445 \u043f\u043e\u043b\u044f \u0438\u0437 \u043e\u0431\u0435\u0438\u0445 \u0442\u0430\u0431\u043b\u0438\u0446, \u0437\u0430\u0434\u0430\u0432\u0430\u0439\u0442\u0435 \u043f\u0441\u0435\u0432\u0434\u043e\u043d\u0438\u043c \u0434\u043b\u044f \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u0430 \u0432\u044b\u0440\u0430\u0436\u0435\u043d\u0438\u044f:\nCODE_BLOCK_35\n\n4. **\u0424\u0438\u043b\u044c\u0442\u0440\u0430\u0446\u0438\u044f \u043f\u043e \u043f\u0441\u0435\u0432\u0434\u043e\u043d\u0438\u043c\u0430\u043c \u0432\u044b\u0440\u0430\u0436\u0435\u043d\u0438\u0439**: \u041d\u0435\u043b\u044c\u0437\u044f \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u043f\u0441\u0435\u0432\u0434\u043e\u043d\u0438\u043c\u044b \u0434\u043b\u044f \u0432\u044b\u0440\u0430\u0436\u0435\u043d\u0438\u0439, \u0432\u043a\u043b\u044e\u0447\u0430\u044e\u0449\u0438\u0445 \u043f\u043e\u043b\u044f \u0438\u0437 \u043e\u0431\u0435\u0438\u0445 \u0442\u0430\u0431\u043b\u0438\u0446, \u0432 \u043f\u0440\u0435\u0434\u043b\u043e\u0436\u0435\u043d\u0438\u0438 WHERE.\n\n5. **\u0410\u0442\u0440\u0438\u0431\u0443\u0442\u044b JSON**: \u041f\u0440\u0438 \u043e\u0431\u044a\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0438 \u043f\u043e \u0430\u0442\u0440\u0438\u0431\u0443\u0442\u0430\u043c JSON \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u044f\u0432\u043d\u043e \u043f\u0440\u0438\u0432\u043e\u0434\u0438\u0442\u044c \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f \u043a \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u044e\u0449\u0435\u043c\u0443 \u0442\u0438\u043f\u0443:\nCODE_BLOCK_36\n\n6. **\u041e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0430 NULL**: \u041c\u043e\u0436\u043d\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0443\u0441\u043b\u043e\u0432\u0438\u044f IS NULL \u0438 IS NOT NULL \u0434\u043b\u044f \u043f\u043e\u043b\u0435\u0439 \u043e\u0431\u044a\u0435\u0434\u0438\u043d\u0451\u043d\u043d\u044b\u0445 \u0442\u0430\u0431\u043b\u0438\u0446:\nCODE_BLOCK_37\n\n7. **\u0418\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435 ANY \u0441 MVA**: \u041f\u0440\u0438 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0438 \u0444\u0443\u043d\u043a\u0446\u0438\u0438 `ANY()` \u0441 \u043c\u043d\u043e\u0433\u043e\u0437\u043d\u0430\u0447\u043d\u044b\u043c\u0438 \u0430\u0442\u0440\u0438\u0431\u0443\u0442\u0430\u043c\u0438 \u0432 \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u044f\u0445 JOIN \u0437\u0430\u0434\u0430\u0432\u0430\u0439\u0442\u0435 \u043f\u0441\u0435\u0432\u0434\u043e\u043d\u0438\u043c \u0434\u043b\u044f \u043c\u043d\u043e\u0433\u043e\u0437\u043d\u0430\u0447\u043d\u043e\u0433\u043e \u0430\u0442\u0440\u0438\u0431\u0443\u0442\u0430 \u0438\u0437 \u043e\u0431\u044a\u0435\u0434\u0438\u043d\u044f\u0435\u043c\u043e\u0439 \u0442\u0430\u0431\u043b\u0438\u0446\u044b:\nCODE_BLOCK_38\n\n\u0421\u043b\u0435\u0434\u0443\u044f \u044d\u0442\u0438\u043c \u0440\u0435\u043a\u043e\u043c\u0435\u043d\u0434\u0430\u0446\u0438\u044f\u043c, \u0432\u044b \u0441\u043c\u043e\u0436\u0435\u0442\u0435 \u044d\u0444\u0444\u0435\u043a\u0442\u0438\u0432\u043d\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u0438 JOIN \u0432 Manticore Search \u0434\u043b\u044f \u043e\u0431\u044a\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u044f \u0434\u0430\u043d\u043d\u044b\u0445 \u0438\u0437 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u0438\u0445 \u0438\u043d\u0434\u0435\u043a\u0441\u043e\u0432 \u0438 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f \u0441\u043b\u043e\u0436\u043d\u044b\u0445 \u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432.\n\n\n\n" + }, + "is_code_or_comment": false, + "model": "deepseek/deepseek-v3.2", + "updated_at": 1772599148 } } \ No newline at end of file diff --git a/manual/chinese/Searching/Joining.md b/manual/chinese/Searching/Joining.md index 5a0b56b563..f75d6ea5b8 100644 --- a/manual/chinese/Searching/Joining.md +++ b/manual/chinese/Searching/Joining.md @@ -996,9 +996,11 @@ POST /search 在 Manticore Search 中使用 JOIN 时,请记住以下几点: -1. **字段选择**:在 JOIN 中选择两个表的字段时,不要为左表的字段添加前缀,但需要为右表的字段添加前缀。例如: +1. **字段选择**:在 JOIN 中从两个表选择字段时,右表的字段需以表名作为前缀。左表的字段可以使用或不使用表名前缀。例如: ```sql SELECT field_name, right_table.field_name FROM ... + -- or with left table prefix: + SELECT left_table.field_name, right_table.field_name FROM ... ``` 2. **JOIN 条件**:始终在 JOIN 条件中明确指定表名: diff --git a/manual/english/Searching/Joining.md b/manual/english/Searching/Joining.md index 487f624df7..8995cf05f0 100755 --- a/manual/english/Searching/Joining.md +++ b/manual/english/Searching/Joining.md @@ -996,9 +996,11 @@ Distributed tables consisting only of local tables are supported on both the lef When using JOINs in Manticore Search, keep the following points in mind: -1. **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: +1. **Field selection**: When selecting fields from two tables in a JOIN, prefix fields from the right table with the table name. Left table fields can be used with or without the table prefix. For example: ```sql SELECT field_name, right_table.field_name FROM ... + -- or with left table prefix: + SELECT left_table.field_name, right_table.field_name FROM ... ``` 2. **JOIN conditions**: Always explicitly specify the table names in your JOIN conditions: diff --git a/manual/russian/Searching/Joining.md b/manual/russian/Searching/Joining.md index b2327c929d..904913a6f1 100644 --- a/manual/russian/Searching/Joining.md +++ b/manual/russian/Searching/Joining.md @@ -996,9 +996,11 @@ POST /search При использовании JOIN в Manticore Search учитывайте следующие моменты: -1. **Выбор полей**: При выборе полей из двух таблиц в JOIN не используйте префикс для полей из левой таблицы, но используйте префикс для полей из правой таблицы. Например: +1. **Выбор полей**: При выборе полей из двух таблиц в JOIN добавляйте префикс имени таблицы к полям из правой таблицы. Поля левой таблицы можно использовать как с префиксом таблицы, так и без него. Например: ```sql SELECT field_name, right_table.field_name FROM ... + -- or with left table prefix: + SELECT left_table.field_name, right_table.field_name FROM ... ``` 2. **Условия JOIN**: Всегда явно указывайте имена таблиц в ваших условиях JOIN: diff --git a/src/gtests/gtests_functions.cpp b/src/gtests/gtests_functions.cpp index 4f2379aba0..d0e081907c 100644 --- a/src/gtests/gtests_functions.cpp +++ b/src/gtests/gtests_functions.cpp @@ -2115,7 +2115,7 @@ TEST ( functions, histogram_expression ) CSphString sError; ExprParseArgs_t tExprArgs; - ISphExprRefPtr_c pExpr ( sphExprParse ( "histogram(price*100, {hist_interval=1})", tSchema, nullptr, sError, tExprArgs ) ); + ISphExprRefPtr_c pExpr ( sphExprParse ( "histogram(price*100, {hist_interval=1})", tSchema, sError, tExprArgs ) ); ASSERT_TRUE ( pExpr.Ptr () ) << sError.cstr(); sphSetRowAttr ( pRow, tSchema.GetAttr(0).m_tLocator, sphF2DW ( 0.5f ) ); diff --git a/src/gtests/gtests_text.cpp b/src/gtests/gtests_text.cpp index eb81f0b95f..772051582a 100644 --- a/src/gtests/gtests_text.cpp +++ b/src/gtests/gtests_text.cpp @@ -419,7 +419,7 @@ TEST ( Text, expression_parser ) { CSphString sError; ExprParseArgs_t tExprArgs; - ISphExprRefPtr_c pExpr ( sphExprParse ( dTest.m_sExpr, tSchema, nullptr, sError, tExprArgs ) ); + ISphExprRefPtr_c pExpr ( sphExprParse ( dTest.m_sExpr, tSchema, sError, tExprArgs ) ); ASSERT_TRUE ( pExpr.Ptr () ) << "parsing " << dTest.m_sExpr << ":" << sError.cstr (); ASSERT_FLOAT_EQ ( dTest.m_fValue, pExpr->Eval ( tMatch ) ); } @@ -511,7 +511,7 @@ TEST ( Text, expression_parser_many ) { CSphString sError; ExprParseArgs_t tExprArgs; - ISphExprRefPtr_c pExpr ( sphExprParse ( sTest.cstr (), tSchema, nullptr, sError, tExprArgs ) ); + ISphExprRefPtr_c pExpr ( sphExprParse ( sTest.cstr (), tSchema, sError, tExprArgs ) ); ASSERT_TRUE ( pExpr.Ptr () ) << sError.cstr () << ": " << sTest.cstr(); } diff --git a/src/joinsorter.cpp b/src/joinsorter.cpp index b253e5ff41..ddaf79fff2 100644 --- a/src/joinsorter.cpp +++ b/src/joinsorter.cpp @@ -1686,12 +1686,11 @@ bool JoinSorter_c::ValidateLeftTableNotPrefixedInFilters ( CSphString & sError ) sLeftPrefix.SetSprintf ( "%s.", sLeftTableName.cstr() ); ARRAY_FOREACH ( i, m_tQuery.m_dFilters ) { - const auto & tFilter = m_tQuery.m_dFilters[i]; + auto & tFilter = m_tQuery.m_dFilters[i]; if ( tFilter.m_sAttrName.Begins ( sLeftPrefix.cstr() ) ) { - CSphString sAttrName = tFilter.m_sAttrName.SubString ( sLeftPrefix.Length(), tFilter.m_sAttrName.Length() - sLeftPrefix.Length() ); - sError.SetSprintf ( "table %s: unknown column: %s (do not prefix left table attributes in JOIN queries, use '%s' instead of '%s')", sLeftTableName.cstr(), sAttrName.cstr(), sAttrName.cstr(), tFilter.m_sAttrName.cstr() ); - return false; + // Allow left table prefix: strip it so the rest of the pipeline sees bare attribute names + tFilter.m_sAttrName = tFilter.m_sAttrName.SubString ( sLeftPrefix.Length(), tFilter.m_sAttrName.Length() - sLeftPrefix.Length() ); } } @@ -1716,6 +1715,7 @@ bool JoinSorter_c::SetupRightFilters ( CSphString & sError ) tCtx.m_pIndexSchema = &m_pIndex->GetMatchSchema(); tCtx.m_bScan = m_tQuery.m_sQuery.IsEmpty(); tCtx.m_sJoinIdx = GetJoinedIndexName(); + tCtx.m_sJoinIdxLeft = m_pIndex->GetName(); tCtx.m_eJoinType = m_tQuery.m_eJoinType; if ( !sphCreateFilters ( tCtx, sError, sError ) ) { diff --git a/src/queuecreator.cpp b/src/queuecreator.cpp index 2fadc2d806..74086fcd34 100644 --- a/src/queuecreator.cpp +++ b/src/queuecreator.cpp @@ -403,8 +403,13 @@ void QueueCreator_c::CreateGrouperByAttr ( ESphAttr eType, const CSphColumnInfo { ExprParseArgs_t tExprArgs; tExprArgs.m_eCollation = m_tQuery.m_eCollation; + if ( m_tSettings.m_pJoinArgs ) + { + tExprArgs.m_pJoinIdx = &m_tSettings.m_pJoinArgs->m_sIndex2; + tExprArgs.m_pJoinIdxLeft = &m_tSettings.m_pJoinArgs->m_sIndex1; + } - ISphExprRefPtr_c pExpr { sphExprParse ( m_tQuery.m_sGroupBy.cstr(), tSchema, m_tSettings.m_pJoinArgs ? &(m_tSettings.m_pJoinArgs->m_sIndex2) : nullptr, m_sError, tExprArgs ) }; + ISphExprRefPtr_c pExpr { sphExprParse ( m_tQuery.m_sGroupBy.cstr(), tSchema, m_sError, tExprArgs ) }; m_tGroupSorterSettings.m_pGrouper = CreateGrouperJsonField ( tLoc, pExpr ); m_tGroupSorterSettings.m_bJson = true; } @@ -586,7 +591,12 @@ bool QueueCreator_c::SetupGroupbySettings ( bool bHasImplicitGrouping ) if ( !sJsonExpr.IsEmpty() ) { ExprParseArgs_t tExprArgs; - dJsonKeys.Add ( sphExprParse ( sJsonExpr.cstr(), tSchema, m_tSettings.m_pJoinArgs ? &(m_tSettings.m_pJoinArgs->m_sIndex2) : nullptr, m_sError, tExprArgs ) ); + if ( m_tSettings.m_pJoinArgs ) + { + tExprArgs.m_pJoinIdx = &m_tSettings.m_pJoinArgs->m_sIndex2; + tExprArgs.m_pJoinIdxLeft = &m_tSettings.m_pJoinArgs->m_sIndex1; + } + dJsonKeys.Add ( sphExprParse ( sJsonExpr.cstr(), tSchema, m_sError, tExprArgs ) ); } else if ( tAttr.m_eAttrType==SPH_ATTR_JSON_FIELD ) { @@ -623,8 +633,13 @@ bool QueueCreator_c::SetupGroupbySettings ( bool bHasImplicitGrouping ) ExprParseArgs_t tExprArgs; tExprArgs.m_eCollation = m_tQuery.m_eCollation; + if ( m_tSettings.m_pJoinArgs ) + { + tExprArgs.m_pJoinIdx = &m_tSettings.m_pJoinArgs->m_sIndex2; + tExprArgs.m_pJoinIdxLeft = &m_tSettings.m_pJoinArgs->m_sIndex1; + } - ISphExprRefPtr_c pExpr { sphExprParse ( m_tQuery.m_sGroupBy.cstr(), tSchema, m_tSettings.m_pJoinArgs ? &(m_tSettings.m_pJoinArgs->m_sIndex2) : nullptr, m_sError, tExprArgs ) }; + ISphExprRefPtr_c pExpr { sphExprParse ( m_tQuery.m_sGroupBy.cstr(), tSchema, m_sError, tExprArgs ) }; m_tGroupSorterSettings.m_pGrouper = CreateGrouperJsonField ( tSchema.GetAttr(iAttr).m_tLocator, pExpr ); m_tGroupSorterSettings.m_bJson = true; m_bJoinedGroupSort |= IsJoinAttr(sJsonColumn); @@ -1124,10 +1139,22 @@ bool QueueCreator_c::ParseQueryItem ( const CSphQueryItem & tItem ) { CSphString sExpr2; sExpr2.SetSprintf ( "TO_STRING(%s)", sExpr.cstr() ); - tExprCol.m_pExpr = sphExprParse ( sExpr2.cstr(), *m_pSorterSchema, m_tSettings.m_pJoinArgs ? &(m_tSettings.m_pJoinArgs->m_sIndex2) : nullptr, m_sError, tExprParseArgs ); + if ( m_tSettings.m_pJoinArgs ) + { + tExprParseArgs.m_pJoinIdx = &m_tSettings.m_pJoinArgs->m_sIndex2; + tExprParseArgs.m_pJoinIdxLeft = &m_tSettings.m_pJoinArgs->m_sIndex1; + } + tExprCol.m_pExpr = sphExprParse ( sExpr2.cstr(), *m_pSorterSchema, m_sError, tExprParseArgs ); } else - tExprCol.m_pExpr = sphExprParse ( sExpr.cstr(), *m_pSorterSchema, m_tSettings.m_pJoinArgs ? &(m_tSettings.m_pJoinArgs->m_sIndex2) : nullptr, m_sError, tExprParseArgs ); + { + if ( m_tSettings.m_pJoinArgs ) + { + tExprParseArgs.m_pJoinIdx = &m_tSettings.m_pJoinArgs->m_sIndex2; + tExprParseArgs.m_pJoinIdxLeft = &m_tSettings.m_pJoinArgs->m_sIndex1; + } + tExprCol.m_pExpr = sphExprParse ( sExpr.cstr(), *m_pSorterSchema, m_sError, tExprParseArgs ); + } m_uPackedFactorFlags |= uQueryPackedFactorFlags; m_bZonespanlist |= bHasZonespanlist; @@ -1264,8 +1291,13 @@ bool QueueCreator_c::MaybeAddExprColumn () tExprArgs.m_pProfiler = m_tSettings.m_pProfiler; tExprArgs.m_eCollation = m_tQuery.m_eCollation; tExprArgs.m_pZonespanlist = &bHasZonespanlist; + if ( m_tSettings.m_pJoinArgs ) + { + tExprArgs.m_pJoinIdx = &m_tSettings.m_pJoinArgs->m_sIndex2; + tExprArgs.m_pJoinIdxLeft = &m_tSettings.m_pJoinArgs->m_sIndex1; + } - tCol.m_pExpr = sphExprParse ( m_tQuery.m_sSortBy.cstr (), *m_pSorterSchema, m_tSettings.m_pJoinArgs ? &(m_tSettings.m_pJoinArgs->m_sIndex2) : nullptr, m_sError, tExprArgs ); + tCol.m_pExpr = sphExprParse ( m_tQuery.m_sSortBy.cstr (), *m_pSorterSchema, m_sError, tExprArgs ); if ( !tCol.m_pExpr ) return false; @@ -1460,7 +1492,12 @@ void QueueCreator_c::ReplaceJsonGroupbyWithStrings ( CSphString & sJsonGroupBy ) if ( !sJsonExpr.IsEmpty() ) { ExprParseArgs_t tExprArgs; - dJsonKeys.Add ( sphExprParse ( sJsonExpr.cstr(), *m_pSorterSchema, m_tSettings.m_pJoinArgs ? &(m_tSettings.m_pJoinArgs->m_sIndex2) : nullptr, m_sError, tExprArgs ) ); + if ( m_tSettings.m_pJoinArgs ) + { + tExprArgs.m_pJoinIdx = &m_tSettings.m_pJoinArgs->m_sIndex2; + tExprArgs.m_pJoinIdxLeft = &m_tSettings.m_pJoinArgs->m_sIndex1; + } + dJsonKeys.Add ( sphExprParse ( sJsonExpr.cstr(), *m_pSorterSchema, m_sError, tExprArgs ) ); } else dJsonKeys.Add(nullptr); @@ -1655,8 +1692,13 @@ bool QueueCreator_c::ParseJoinExpr ( CSphColumnInfo & tExprCol, const CSphString tExprParseArgs.m_pAttrType = &tExprCol.m_eAttrType; tExprParseArgs.m_pProfiler = m_tSettings.m_pProfiler; tExprParseArgs.m_eCollation = m_tQuery.m_eCollation; + if ( m_tSettings.m_pJoinArgs ) + { + tExprParseArgs.m_pJoinIdx = &m_tSettings.m_pJoinArgs->m_sIndex2; + tExprParseArgs.m_pJoinIdxLeft = &m_tSettings.m_pJoinArgs->m_sIndex1; + } tExprCol.m_eStage = SPH_EVAL_PRESORT; - tExprCol.m_pExpr = sphExprParse ( sExpr.cstr(), *m_pSorterSchema, m_tSettings.m_pJoinArgs ? &(m_tSettings.m_pJoinArgs->m_sIndex2) : nullptr, m_sError, tExprParseArgs ); + tExprCol.m_pExpr = sphExprParse ( sExpr.cstr(), *m_pSorterSchema, m_sError, tExprParseArgs ); tExprCol.m_uAttrFlags |= CSphColumnInfo::ATTR_JOINED; return !!tExprCol.m_pExpr; } diff --git a/src/schematransform.cpp b/src/schematransform.cpp index adda89fb8f..6a55273830 100644 --- a/src/schematransform.cpp +++ b/src/schematransform.cpp @@ -173,7 +173,7 @@ void TransformedSchemaBuilder_c::ReplaceColumnarAttrWithExpression ( CSphColumnI // parse expression as if it is not columnar CSphString sError; ExprParseArgs_t tExprArgs; - tAttr.m_pExpr = sphExprParse ( tAttr.m_sName.cstr(), m_tNewSchema, nullptr, sError, tExprArgs ); + tAttr.m_pExpr = sphExprParse ( tAttr.m_sName.cstr(), m_tNewSchema, sError, tExprArgs ); assert ( tAttr.m_pExpr ); // now remove it from schema (it will be added later with the supplied expression) @@ -310,7 +310,7 @@ void MatchesToNewSchema_c::SetupAction ( const CSphColumnInfo & tOld, const CSph { CSphString sError; ExprParseArgs_t tExprArgs; - tAction.m_pExpr = sphExprParse ( tOld.m_sName.cstr(), *pOldSchema, nullptr, sError, tExprArgs ); + tAction.m_pExpr = sphExprParse ( tOld.m_sName.cstr(), *pOldSchema, sError, tExprArgs ); assert ( tAction.m_pExpr ); switch ( tNew.m_eAttrType ) diff --git a/src/searchd.cpp b/src/searchd.cpp index fb3d7950b7..d6f485fadb 100644 --- a/src/searchd.cpp +++ b/src/searchd.cpp @@ -7712,7 +7712,7 @@ static void ReturnZeroCount ( const CSphSchema & tSchema, const CSphBitvec & tAt CSphString sError; ExprParseArgs_t tExprArgs; tExprArgs.m_pAttrType = &eAttrType; - ISphExprRefPtr_c pExpr { sphExprParse ( tCol.m_sName.cstr(), tSchema, nullptr, sError, tExprArgs )}; + ISphExprRefPtr_c pExpr { sphExprParse ( tCol.m_sName.cstr(), tSchema, sError, tExprArgs )}; if ( !pExpr || !pExpr->IsConst() ) eAttrType = SPH_ATTR_NONE; @@ -9503,7 +9503,7 @@ void HandleMysqlSelectColumns ( RowBuffer_i & tOut, const SqlStmt_t & tStmt, Cli ESphAttr eAttrType; ExprParseArgs_t tExprArgs; tExprArgs.m_pAttrType = &eAttrType; - ISphExprRefPtr_c pExpr { sphExprParse ( sVar.cstr(), tSchema, nullptr, sError, tExprArgs ) }; + ISphExprRefPtr_c pExpr { sphExprParse ( sVar.cstr(), tSchema, sError, tExprArgs ) }; if ( pExpr ) { dColumns.Add ( { eAttrType, ESphAttr2MysqlColumn ( eAttrType ), pExpr, -1, tItem.m_sAlias.cstr() } ); diff --git a/src/searchdsql.cpp b/src/searchdsql.cpp index c83ff3d872..556c9936e9 100644 --- a/src/searchdsql.cpp +++ b/src/searchdsql.cpp @@ -2716,7 +2716,7 @@ bool PercolateParseFilters ( const char * sFilters, ESphCollation eCollation, co ExprParseArgs_t tExprArgs; tExprArgs.m_pAttrType = &eAttrType; tExprArgs.m_eCollation = eCollation; - ISphExprRefPtr_c pExpr { sphExprParse ( sFilters, tSchema, nullptr, sError, tExprArgs ) }; + ISphExprRefPtr_c pExpr { sphExprParse ( sFilters, tSchema, sError, tExprArgs ) }; if ( pExpr ) { sError = ""; diff --git a/src/sortsetup.cpp b/src/sortsetup.cpp index 3b33025bae..1dfb8c5b59 100644 --- a/src/sortsetup.cpp +++ b/src/sortsetup.cpp @@ -339,7 +339,12 @@ bool SortStateSetup_c::SetupJsonField ( CSphString & sError ) if ( m_iAttr>=0 ) { ExprParseArgs_t tExprArgs; - ISphExpr * pExpr = sphExprParse ( m_szTok, m_tSchema, m_pJoinArgs ? &(m_pJoinArgs->m_sIndex2) : nullptr, sError, tExprArgs ); + if ( m_pJoinArgs ) + { + tExprArgs.m_pJoinIdx = &m_pJoinArgs->m_sIndex2; + tExprArgs.m_pJoinIdxLeft = &m_pJoinArgs->m_sIndex1; + } + ISphExpr * pExpr = sphExprParse ( m_szTok, m_tSchema, sError, tExprArgs ); if ( !pExpr ) return false; @@ -362,7 +367,12 @@ bool SortStateSetup_c::SetupColumnar ( CSphString & sError ) ExprParseArgs_t tExprArgs; tExprArgs.m_pAttrType = &m_eAttrType; - ISphExpr * pExpr = sphExprParse ( m_szTok, m_tSchema, m_pJoinArgs ? &(m_pJoinArgs->m_sIndex2) : nullptr, sError, tExprArgs ); + if ( m_pJoinArgs ) + { + tExprArgs.m_pJoinIdx = &m_pJoinArgs->m_sIndex2; + tExprArgs.m_pJoinIdxLeft = &m_pJoinArgs->m_sIndex1; + } + ISphExpr * pExpr = sphExprParse ( m_szTok, m_tSchema, sError, tExprArgs ); if ( !pExpr ) return false; @@ -398,7 +408,12 @@ void SortStateSetup_c::SetupJsonConversions() ExprParseArgs_t tExprArgs; tExprArgs.m_pAttrType = &eAttrType; CSphString sError; // ignored - ISphExpr * pExpr = sphExprParse ( m_szTok, m_tSchema, m_pJoinArgs ? &(m_pJoinArgs->m_sIndex2) : nullptr, sError, tExprArgs ); + if ( m_pJoinArgs ) + { + tExprArgs.m_pJoinIdx = &m_pJoinArgs->m_sIndex2; + tExprArgs.m_pJoinIdxLeft = &m_pJoinArgs->m_sIndex1; + } + ISphExpr * pExpr = sphExprParse ( m_szTok, m_tSchema, sError, tExprArgs ); if ( !pExpr ) return; diff --git a/src/sphinx.cpp b/src/sphinx.cpp index 19939065ca..ec535f0d98 100644 --- a/src/sphinx.cpp +++ b/src/sphinx.cpp @@ -804,7 +804,7 @@ void UpdateContext_t::PrepareListOfUpdatedAttributes ( CSphString & sError ) if ( iUpdAttrId>=0 ) { ExprParseArgs_t tExprArgs; - tUpdAttr.m_pExpr = sphExprParse ( sUpdAttrName.cstr(), m_tSchema, nullptr, sError, tExprArgs ); + tUpdAttr.m_pExpr = sphExprParse ( sUpdAttrName.cstr(), m_tSchema, sError, tExprArgs ); } } } diff --git a/src/sphinxexpr.cpp b/src/sphinxexpr.cpp index 73e03f78b5..47858606a3 100644 --- a/src/sphinxexpr.cpp +++ b/src/sphinxexpr.cpp @@ -4264,7 +4264,7 @@ class ExprParser_t } ~ExprParser_t (); - ISphExpr * Parse ( const char * szExpr, const ISphSchema & tSchema, const CSphString * pJoinIdx, ESphAttr * pAttrType, bool * pUsesWeight, CSphString & sError ); + ISphExpr * Parse ( const char * szExpr, const ISphSchema & tSchema, const CSphString * pJoinIdx, const CSphString * pJoinIdxLeft, ESphAttr * pAttrType, bool * pUsesWeight, CSphString & sError ); protected: int m_iParsed = 0; ///< filled by yyparse() at the very end @@ -4319,7 +4319,8 @@ class ExprParser_t void * m_pScanner = nullptr; Str_t m_sExpr; const ISphSchema * m_pSchema = nullptr; - const CSphString * m_pJoinIdx = nullptr; + const CSphString * m_pJoinIdx = nullptr; ///< right table name in JOIN + const CSphString * m_pJoinIdxLeft = nullptr; ///< left table name in JOIN CSphVector m_dNodes; StrVec_t m_dUservars; CSphVector m_dIdents; @@ -4707,8 +4708,8 @@ int ExprParser_t::ProcessRawToken ( const char * sToken, int iLen, YYSTYPE * lva return iRes; } - // check for table name - if ( m_pJoinIdx && *m_pJoinIdx==sTok ) + // check for table name (left or right in JOIN) + if ( ( m_pJoinIdxLeft && *m_pJoinIdxLeft==sTok ) || ( m_pJoinIdx && *m_pJoinIdx==sTok ) ) { CSphString sTokMixed { sToken, iLen }; m_dIdents.Add ( sTokMixed.Leak() ); @@ -10580,8 +10581,13 @@ int ExprParser_t::ParseJoinAttr ( const char * szTable, uint64_t uOffset ) CSphString sAttrName; sAttrName.SetBinary ( m_sExpr.first + GetConstStrOffset(uOffset), GetConstStrLength(uOffset) ); + // Left table columns are stored in schema without prefix; right table with "right.attr" CSphString sAttrWithTable; - sAttrWithTable.SetSprintf ( "%s.%s", szTable, sAttrName.cstr() ); + if ( m_pJoinIdxLeft && *m_pJoinIdxLeft==szTable ) + sAttrWithTable = sAttrName; + else + sAttrWithTable.SetSprintf ( "%s.%s", szTable, sAttrName.cstr() ); + int iAttr = m_pSchema->GetAttrIndex ( sAttrWithTable.cstr() ); if ( iAttr==-1 ) m_sParserError.SetSprintf ( "unknown attribute '%s'", sAttrWithTable.cstr() ); @@ -10769,7 +10775,7 @@ void SetMaxExprNodeEvalStackItemSize ( std::pair tSize ) } -ISphExpr * ExprParser_t::Parse ( const char * sExpr, const ISphSchema & tSchema, const CSphString * pJoinIdx, ESphAttr * pAttrType, bool * pUsesWeight, CSphString & sError ) +ISphExpr * ExprParser_t::Parse ( const char * sExpr, const ISphSchema & tSchema, const CSphString * pJoinIdx, const CSphString * pJoinIdxLeft, ESphAttr * pAttrType, bool * pUsesWeight, CSphString & sError ) { const char* szExpr = sExpr; @@ -10784,6 +10790,7 @@ ISphExpr * ExprParser_t::Parse ( const char * sExpr, const ISphSchema & tSchema, m_sExpr = { szExpr, (int)strlen (szExpr) }; m_pSchema = &tSchema; m_pJoinIdx = pJoinIdx; + m_pJoinIdxLeft = pJoinIdxLeft; // setup constant functions m_iConstNow = (int) time ( nullptr ); @@ -10948,11 +10955,11 @@ JoinArgs_t::JoinArgs_t ( const ISphSchema & tJoinedSchema, const CSphString & sI {} /// parser entry point -ISphExpr * sphExprParse ( const char * szExpr, const ISphSchema & tSchema, const CSphString * pJoinIdx, CSphString & sError, ExprParseArgs_t & tArgs ) +ISphExpr * sphExprParse ( const char * szExpr, const ISphSchema & tSchema, CSphString & sError, ExprParseArgs_t & tArgs ) { // parse into opcodes ExprParser_t tParser ( tArgs.m_pHook, tArgs.m_pProfiler, tArgs.m_eCollation ); - ISphExpr * pRes = tParser.Parse ( szExpr, tSchema, pJoinIdx, tArgs.m_pAttrType, tArgs.m_pUsesWeight, sError ); + ISphExpr * pRes = tParser.Parse ( szExpr, tSchema, tArgs.m_pJoinIdx, tArgs.m_pJoinIdxLeft, tArgs.m_pAttrType, tArgs.m_pUsesWeight, sError ); if ( tArgs.m_pZonespanlist ) *tArgs.m_pZonespanlist = tParser.m_bHasZonespanlist; if ( tArgs.m_pEvalStage ) diff --git a/src/sphinxexpr.h b/src/sphinxexpr.h index 472e6156d2..8ea09dc2ab 100644 --- a/src/sphinxexpr.h +++ b/src/sphinxexpr.h @@ -352,6 +352,8 @@ struct ExprParseArgs_t ESphEvalStage * m_pEvalStage = nullptr; DWORD * m_pStoredField = nullptr; bool * m_pNeedDocIds = nullptr; + const CSphString * m_pJoinIdx = nullptr; + const CSphString * m_pJoinIdxLeft = nullptr; }; struct JoinArgs_t @@ -375,7 +377,7 @@ class AttrDependencyMap_c }; struct CommonFilterSettings_t; -ISphExpr * sphExprParse ( const char * szExpr, const ISphSchema & tSchema, const CSphString * pJoinIdx, CSphString & sError, ExprParseArgs_t & tArgs ); +ISphExpr * sphExprParse ( const char * szExpr, const ISphSchema & tSchema, CSphString & sError, ExprParseArgs_t & tArgs ); ISphExpr * sphJsonFieldConv ( ISphExpr * pExpr ); ISphExpr * ExprJsonIn ( const VecTraits_T & dVals, ISphExpr * pArg, ESphCollation eCollation ); ISphExpr * ExprJsonIn ( const VecTraits_T & dVals, ISphExpr * pArg, ESphCollation eCollation ); diff --git a/src/sphinxfilter.cpp b/src/sphinxfilter.cpp index 46c9eb57fa..506dbcfd6f 100644 --- a/src/sphinxfilter.cpp +++ b/src/sphinxfilter.cpp @@ -1373,9 +1373,9 @@ static std::unique_ptr CreateFilterExpr ( ISphExpr * _pExpr, const C } -static std::unique_ptr TryToCreateExpressionFilter ( CSphRefcountedPtr & pExpr, const CSphString & sAttrName, const ISphSchema & tSchema, const CSphFilterSettings & tSettings, const CommonFilterSettings_t & tFixedSettings, ExprParseArgs_t & tExprArgs, const CSphString * pJoinIdx, CSphString & sError ) +static std::unique_ptr TryToCreateExpressionFilter ( CSphRefcountedPtr & pExpr, const CSphString & sAttrName, const ISphSchema & tSchema, const CSphFilterSettings & tSettings, const CommonFilterSettings_t & tFixedSettings, ExprParseArgs_t & tExprArgs, CSphString & sError ) { - pExpr = sphExprParse ( sAttrName.cstr(), tSchema, pJoinIdx, sError, tExprArgs ); + pExpr = sphExprParse ( sAttrName.cstr(), tSchema, sError, tExprArgs ); if ( pExpr && pExpr->UsesDocstore() ) { sError.SetSprintf ( "unsupported filter on field '%s' (filters are supported only on attributes, not stored fields)", sAttrName.cstr() ); @@ -1550,7 +1550,7 @@ static void TryToCreateJoinNullFilter ( std::unique_ptr & pFilter, c } -static void TryToCreateExpressionFilter ( std::unique_ptr & pFilter, const CSphFilterSettings & tSettings, const CreateFilterContext_t & tCtx, const CSphString & sAttrName, const CommonFilterSettings_t & tFixedSettings, ESphAttr & eAttrType, CSphRefcountedPtr & pExpr, const CSphString * pJoinIdx, CSphString & sError, CSphString & sWarning ) +static void TryToCreateExpressionFilter ( std::unique_ptr & pFilter, const CSphFilterSettings & tSettings, const CreateFilterContext_t & tCtx, const CSphString & sAttrName, const CommonFilterSettings_t & tFixedSettings, ESphAttr & eAttrType, CSphRefcountedPtr & pExpr, const CSphString * pJoinIdx, const CSphString * pJoinIdxLeft, CSphString & sError, CSphString & sWarning ) { if ( pFilter ) return; @@ -1574,7 +1574,11 @@ static void TryToCreateExpressionFilter ( std::unique_ptr & pFilter, ExprParseArgs_t tExprArgs; tExprArgs.m_pAttrType = &eAttrType; tExprArgs.m_eCollation = tCtx.m_eCollation; - pFilter = TryToCreateExpressionFilter ( pExpr, sAttrName, tSchema, tSettings, tFixedSettings, tExprArgs, pJoinIdx, sError ); + if ( pJoinIdx ) + tExprArgs.m_pJoinIdx = pJoinIdx; + if ( pJoinIdxLeft ) + tExprArgs.m_pJoinIdxLeft = pJoinIdxLeft; + pFilter = TryToCreateExpressionFilter ( pExpr, sAttrName, tSchema, tSettings, tFixedSettings, tExprArgs, sError ); } @@ -1614,7 +1618,7 @@ static void TryToCreatePlainAttrFilter ( std::unique_ptr& pFilter, c ExprParseArgs_t tExprArgs; tExprArgs.m_pAttrType = &eAttrType; tExprArgs.m_eCollation = tCtx.m_eCollation; - pExpr = sphExprParse ( sAttrName.cstr(), tSchema, nullptr, sError, tExprArgs ); + pExpr = sphExprParse ( sAttrName.cstr(), tSchema, sError, tExprArgs ); } pFilter = CreateFilterExpr ( pExpr, tSettings, tFixedSettings, sError, tCtx.m_eCollation, tAttr.m_eAttrType ); } else @@ -2328,7 +2332,7 @@ static std::unique_ptr CreateFilter ( const CSphFilterSettings & tSe return nullptr; TryToCreateJoinNullFilter ( pFilter, tSettings, tCtx, sAttrName, tFixedSettings ); - TryToCreateExpressionFilter ( pFilter, tSettings, tCtx, sAttrName, tFixedSettings, eAttrType, pExpr, tCtx.m_sJoinIdx.Length() ? &tCtx.m_sJoinIdx : nullptr, sError, sWarning ); + TryToCreateExpressionFilter ( pFilter, tSettings, tCtx, sAttrName, tFixedSettings, eAttrType, pExpr, tCtx.m_sJoinIdx.Length() ? &tCtx.m_sJoinIdx : nullptr, tCtx.m_sJoinIdxLeft.Length() ? &tCtx.m_sJoinIdxLeft : nullptr, sError, sWarning ); TryToCreatePlainAttrFilter ( pFilter, tSettings, tCtx, bHaving, sAttrName, tFixedSettings, eAttrType, pExpr, sError, sWarning ); SetFilterLocator ( pFilter, tSettings, tCtx, sAttrName ); diff --git a/src/sphinxfilter.h b/src/sphinxfilter.h index b4900c33e3..09e81ce6cd 100644 --- a/src/sphinxfilter.h +++ b/src/sphinxfilter.h @@ -86,6 +86,7 @@ struct CreateFilterContext_t const SIContainer_c * m_pSI = nullptr; int64_t m_iTotalDocs = 0; CSphString m_sJoinIdx; + CSphString m_sJoinIdxLeft; ///< left table name in JOIN (for resolving table.attr in expressions) JoinType_e m_eJoinType = JoinType_e::NONE; bool m_bAddKNNDistFilter = false; }; diff --git a/src/sphinxsearch.cpp b/src/sphinxsearch.cpp index 7abac5cb0f..93a77ca532 100644 --- a/src/sphinxsearch.cpp +++ b/src/sphinxsearch.cpp @@ -3496,7 +3496,7 @@ bool RankerState_Expr_fn::Init ( int iFields, tExprArgs.m_pUsesWeight = &bUsesWeight; tExprArgs.m_pHook = &tHook; - m_pExpr = sphExprParse ( m_sExpr, *m_pSchema, nullptr, sError, tExprArgs ); // FIXME!!! profile UDF here too + m_pExpr = sphExprParse ( m_sExpr, *m_pSchema, sError, tExprArgs ); // FIXME!!! profile UDF here too if ( !m_pExpr ) return false; if ( m_eExprType!=SPH_ATTR_INTEGER && m_eExprType!=SPH_ATTR_FLOAT ) diff --git a/src/stackmock.cpp b/src/stackmock.cpp index 95720b01b7..433cf1dfba 100644 --- a/src/stackmock.cpp +++ b/src/stackmock.cpp @@ -193,7 +193,7 @@ class CreateExprStackSize_c final : public StackMeasurer_c tParams.m_sExpr = m_sExpr.cstr(); Threads::MockCallCoroutine ( m_dMockStack, [&tParams] { - tParams.m_pExprBase = sphExprParse ( tParams.m_sExpr, tParams.m_tSchema, nullptr, tParams.m_sError, tParams.m_tArgs ); + tParams.m_pExprBase = sphExprParse ( tParams.m_sExpr, tParams.m_tSchema, tParams.m_sError, tParams.m_tArgs ); } ); tParams.m_bSuccess = !!tParams.m_pExprBase; @@ -265,7 +265,7 @@ class EvalExprStackSize_c final : public StackMeasurer_c { // parse in dedicated coro (hope, 100K frame per level should fit any arch) CSphFixedVector dSafeStack { iStack }; Threads::MockCallCoroutine ( dSafeStack, [&tParams] { // do in coro as for fat expr it might already require dedicated stack - tParams.m_pExprBase = sphExprParse ( tParams.m_sExpr, tParams.m_tSchema, nullptr, tParams.m_sError, tParams.m_tArgs ); + tParams.m_pExprBase = sphExprParse ( tParams.m_sExpr, tParams.m_tSchema, tParams.m_sError, tParams.m_tArgs ); }); tParams.m_bSuccess = !!tParams.m_pExprBase; assert ( tParams.m_pExprBase ); @@ -342,7 +342,7 @@ class DeleteExprStackSize_c final : public StackMeasurer_c { // parse in dedicated coro (hope, 100K frame per level should fit any arch) CSphFixedVector dSafeStack { iStack }; Threads::MockCallCoroutine ( dSafeStack, [&tParams] { // do in coro as for fat expr it might already require dedicated stack - tParams.m_pExprBase = sphExprParse ( tParams.m_sExpr, tParams.m_tSchema, nullptr, tParams.m_sError, tParams.m_tArgs ); + tParams.m_pExprBase = sphExprParse ( tParams.m_sExpr, tParams.m_tSchema, tParams.m_sError, tParams.m_tArgs ); }); } diff --git a/test/test_278/model.bin b/test/test_278/model.bin index 824be45339..5ca292ee9e 100644 Binary files a/test/test_278/model.bin and b/test/test_278/model.bin differ diff --git a/test/test_278/test.xml b/test/test_278/test.xml index 9bf36e3b34..68d6f27fd1 100644 --- a/test/test_278/test.xml +++ b/test/test_278/test.xml @@ -48,6 +48,9 @@ select * from join2 order by id asc; select id, join2.name from join1 inner join join2 on join1.string_id=join2.id order by id asc; + +select join1.id, join2.name from join1 inner join join2 on join1.string_id=join2.id order by join1.id asc; + select id from join1 inner join join2 on join1.string_id=join2.id order by id asc; diff --git a/test/test_279/model.bin b/test/test_279/model.bin index 1f693278fb..fc8c2acd33 100644 --- a/test/test_279/model.bin +++ b/test/test_279/model.bin @@ -1 +1 @@ -a:2:{i:0;a:53:{i:0;a:2:{s:8:"sphinxql";s:105:"CREATE TABLE users (id bigint, name text, surname text, email text, reg_date timestamp) engine='columnar'";s:14:"total_affected";i:0;}i:1;a:2:{s:8:"sphinxql";s:184:"CREATE TABLE orders (id bigint, user_id bigint, name text, description text, price float, order_date timestamp, attr json, region_id int, confirm bool, analogs multi) engine='columnar'";s:14:"total_affected";i:0;}i:2;a:2:{s:8:"sphinxql";s:107:"INSERT INTO users (id, name, surname, email, reg_date) VALUES (1, 'Fedor', 'Zaycev', '1@1.com', 1708865549)";s:14:"total_affected";i:1;}i:3;a:2:{s:8:"sphinxql";s:214:"INSERT INTO orders (id, user_id, name, description, price, order_date, attr, region_id, confirm, analogs) VALUES (0, 1, 'iPhone', '14Pro', 1500.50, 1708866233, '{"color":"black","size":14}', 178, 'TRUE', (9,11,15))";s:14:"total_affected";i:1;}i:4;a:3:{s:8:"sphinxql";s:94:"SELECT id, name FROM users INNER JOIN orders ON users.id=orders.user_id ORDER BY orders.id ASC";s:10:"total_rows";i:1;s:4:"rows";a:1:{i:0;a:2:{s:2:"id";s:1:"1";s:4:"name";s:5:"Fedor";}}}i:5;a:2:{s:8:"sphinxql";s:20:"flush ramchunk users";s:14:"total_affected";i:0;}i:6;a:2:{s:8:"sphinxql";s:21:"flush ramchunk orders";s:14:"total_affected";i:0;}i:7;a:3:{s:8:"sphinxql";s:86:"SELECT * FROM users LEFT JOIN orders ON users.id=orders.user_id ORDER BY orders.id ASC";s:10:"total_rows";i:1;s:4:"rows";a:1:{i:0;a:15:{s:2:"id";s:1:"1";s:4:"name";s:5:"Fedor";s:7:"surname";s:6:"Zaycev";s:5:"email";s:7:"1@1.com";s:8:"reg_date";s:10:"1708865549";s:9:"orders.id";s:13:"1677721600001";s:14:"orders.user_id";s:1:"1";s:12:"orders.price";s:11:"1500.500000";s:17:"orders.order_date";s:10:"1708866233";s:11:"orders.attr";s:27:"{"color":"black","size":14}";s:16:"orders.region_id";s:3:"178";s:14:"orders.confirm";s:1:"1";s:14:"orders.analogs";s:7:"9,11,15";s:11:"orders.name";s:6:"iPhone";s:18:"orders.description";s:5:"14Pro";}}}i:8;a:2:{s:8:"sphinxql";s:16:"DROP TABLE users";s:14:"total_affected";i:0;}i:9;a:2:{s:8:"sphinxql";s:17:"DROP TABLE orders";s:14:"total_affected";i:0;}i:10;a:2:{s:8:"sphinxql";s:35:"CREATE TABLE tbl1 engine='columnar'";s:14:"total_affected";i:0;}i:11;a:2:{s:8:"sphinxql";s:34:"CREATE TABLE tbl2 (tbl1_id bigint)";s:14:"total_affected";i:0;}i:12;a:2:{s:8:"sphinxql";s:27:"INSERT INTO tbl1 VALUES (1)";s:14:"total_affected";i:1;}i:13;a:2:{s:8:"sphinxql";s:30:"INSERT INTO tbl2 VALUES (1, 1)";s:14:"total_affected";i:1;}i:14;a:3:{s:8:"sphinxql";s:69:"select id from tbl1 join tbl2 on tbl1.id=tbl2.tbl1_id order by id asc";s:10:"total_rows";i:1;s:4:"rows";a:1:{i:0;a:1:{s:2:"id";s:1:"1";}}}i:15;a:2:{s:8:"sphinxql";s:15:"drop table tbl2";s:14:"total_affected";i:0;}i:16;a:2:{s:8:"sphinxql";s:15:"drop table tbl1";s:14:"total_affected";i:0;}i:17;a:2:{s:8:"sphinxql";s:111:"create table join1 ( id bigint, title text, string_id integer, tmp string attribute, j json ) engine='columnar'";s:14:"total_affected";i:0;}i:18;a:2:{s:8:"sphinxql";s:93:"create table join2 ( id bigint, title text, name string attribute, j json ) engine='columnar'";s:14:"total_affected";i:0;}i:19;a:2:{s:8:"sphinxql";s:76:"insert into join1 values (1, 'title1', 1, 'tmp1', '{"a":1,"table":"join1"}')";s:14:"total_affected";i:1;}i:20;a:2:{s:8:"sphinxql";s:76:"insert into join1 values (2, 'title2', 2, 'tmp2', '{"b":2,"table":"join1"}')";s:14:"total_affected";i:1;}i:21;a:2:{s:8:"sphinxql";s:76:"insert into join1 values (3, 'title3', 3, 'tmp3', '{"c":3,"table":"join1"}')";s:14:"total_affected";i:1;}i:22;a:2:{s:8:"sphinxql";s:76:"insert into join1 values (4, 'title4', 4, 'tmp4', '{"d":4,"table":"join1"}')";s:14:"total_affected";i:1;}i:23;a:2:{s:8:"sphinxql";s:74:"insert into join2 values (1, 'title1', 'name1', '{"a":1,"table":"join2"}')";s:14:"total_affected";i:1;}i:24;a:2:{s:8:"sphinxql";s:74:"insert into join2 values (2, 'title2', 'name2', '{"b":2,"table":"join2"}')";s:14:"total_affected";i:1;}i:25;a:2:{s:8:"sphinxql";s:74:"insert into join2 values (3, 'title3', 'name3', '{"b":2,"table":"join2"}')";s:14:"total_affected";i:1;}i:26;a:3:{s:8:"sphinxql";s:117:"select * from join1 inner join join2 on join1.string_id = join2.id where join2.name = 'name1' or id=2 order by id asc";s:10:"total_rows";i:2;s:4:"rows";a:2:{i:0;a:9:{s:2:"id";s:1:"1";s:5:"title";s:6:"title1";s:9:"string_id";s:1:"1";s:3:"tmp";s:4:"tmp1";s:1:"j";s:23:"{"a":1,"table":"join1"}";s:8:"join2.id";s:1:"1";s:10:"join2.name";s:5:"name1";s:7:"join2.j";s:23:"{"a":1,"table":"join2"}";s:11:"join2.title";s:6:"title1";}i:1;a:9:{s:2:"id";s:1:"2";s:5:"title";s:6:"title2";s:9:"string_id";s:1:"2";s:3:"tmp";s:4:"tmp2";s:1:"j";s:23:"{"b":2,"table":"join1"}";s:8:"join2.id";s:1:"2";s:10:"join2.name";s:5:"name2";s:7:"join2.j";s:23:"{"b":2,"table":"join2"}";s:11:"join2.title";s:6:"title2";}}}i:27;a:3:{s:8:"sphinxql";s:108:"select * from join1 inner join join2 on join1.string_id = join2.id where id=1 or join2.j.b=2 order by id asc";s:10:"total_rows";i:3;s:4:"rows";a:3:{i:0;a:10:{s:2:"id";s:1:"1";s:5:"title";s:6:"title1";s:9:"string_id";s:1:"1";s:3:"tmp";s:4:"tmp1";s:1:"j";s:23:"{"a":1,"table":"join1"}";s:8:"join2.id";s:1:"1";s:10:"join2.name";s:5:"name1";s:7:"join2.j";s:23:"{"a":1,"table":"join2"}";s:11:"join2.title";s:6:"title1";s:9:"join2.j.b";s:1:"0";}i:1;a:10:{s:2:"id";s:1:"2";s:5:"title";s:6:"title2";s:9:"string_id";s:1:"2";s:3:"tmp";s:4:"tmp2";s:1:"j";s:23:"{"b":2,"table":"join1"}";s:8:"join2.id";s:1:"2";s:10:"join2.name";s:5:"name2";s:7:"join2.j";s:23:"{"b":2,"table":"join2"}";s:11:"join2.title";s:6:"title2";s:9:"join2.j.b";s:1:"2";}i:2;a:10:{s:2:"id";s:1:"3";s:5:"title";s:6:"title3";s:9:"string_id";s:1:"3";s:3:"tmp";s:4:"tmp3";s:1:"j";s:23:"{"c":3,"table":"join1"}";s:8:"join2.id";s:1:"3";s:10:"join2.name";s:5:"name3";s:7:"join2.j";s:23:"{"b":2,"table":"join2"}";s:11:"join2.title";s:6:"title3";s:9:"join2.j.b";s:1:"2";}}}i:28;a:2:{s:8:"sphinxql";s:16:"drop table join1";s:14:"total_affected";i:0;}i:29;a:2:{s:8:"sphinxql";s:16:"drop table join2";s:14:"total_affected";i:0;}i:30;a:2:{s:8:"sphinxql";s:111:"create table join1 ( id bigint, title text, string_id integer, tmp string attribute, j json ) engine='columnar'";s:14:"total_affected";i:0;}i:31;a:2:{s:8:"sphinxql";s:112:"create table join2 ( id bigint, title text, string_id integer, name string attribute engine='columnar', j json )";s:14:"total_affected";i:0;}i:32;a:2:{s:8:"sphinxql";s:85:"insert into join1 values (1, 'title1', 1, 'tmp1', '{"sort":5,"a":1,"table":"join1"}')";s:14:"total_affected";i:1;}i:33;a:2:{s:8:"sphinxql";s:85:"insert into join1 values (2, 'title2', 2, 'tmp2', '{"sort":6,"b":2,"table":"join1"}')";s:14:"total_affected";i:1;}i:34;a:2:{s:8:"sphinxql";s:85:"insert into join1 values (3, 'title3', 3, 'tmp3', '{"sort":7,"c":3,"table":"join1"}')";s:14:"total_affected";i:1;}i:35;a:2:{s:8:"sphinxql";s:85:"insert into join1 values (4, 'title4', 4, 'tmp4', '{"sort":8,"d":4,"table":"join1"}')";s:14:"total_affected";i:1;}i:36;a:2:{s:8:"sphinxql";s:87:"insert into join2 values (1, 'title1', 1, 'name1', '{"sort":10,"a":1,"table":"join2"}')";s:14:"total_affected";i:1;}i:37;a:2:{s:8:"sphinxql";s:86:"insert into join2 values (2, 'title2', 2, 'name2', '{"sort":5,"b":2,"table":"join2"}')";s:14:"total_affected";i:1;}i:38;a:3:{s:8:"sphinxql";s:100:"select id from join1 left join join2 on join1.string_id = join2.string_id where join2.name = 'name2'";s:10:"total_rows";i:1;s:4:"rows";a:1:{i:0;a:1:{s:2:"id";s:1:"2";}}}i:39;a:3:{s:8:"sphinxql";s:101:"select id from join1 inner join join2 on join1.string_id = join2.string_id where join2.name = 'name2'";s:10:"total_rows";i:1;s:4:"rows";a:1:{i:0;a:1:{s:2:"id";s:1:"2";}}}i:40;a:3:{s:8:"sphinxql";s:214:"select title, uint(join2.j.sort) as table2, weight() * (j.sort + table2) as test2 from join1 left join join2 on join1.string_id = join2.string_id where join2.name = 'name2' or join2.name is null order by test2 desc";s:10:"total_rows";i:3;s:4:"rows";a:3:{i:0;a:3:{s:5:"title";s:6:"title2";s:6:"table2";s:1:"5";s:5:"test2";s:2:"11";}i:1;a:3:{s:5:"title";s:6:"title4";s:6:"table2";N;s:5:"test2";s:1:"8";}i:2;a:3:{s:5:"title";s:6:"title3";s:6:"table2";N;s:5:"test2";s:1:"7";}}}i:41;a:3:{s:8:"sphinxql";s:207:"select title, join2.title, uint(join2.j.sort) as table2, weight() * (j.sort + table2) as test2 from join1 left join join2 on join1.string_id = join2.string_id where match('title2', join2) order by test2 desc";s:10:"total_rows";i:1;s:4:"rows";a:1:{i:0;a:4:{s:5:"title";s:6:"title2";s:11:"join2.title";s:6:"title2";s:6:"table2";s:1:"5";s:5:"test2";s:2:"11";}}}i:42;a:2:{s:8:"sphinxql";s:16:"drop table join1";s:14:"total_affected";i:0;}i:43;a:2:{s:8:"sphinxql";s:16:"drop table join2";s:14:"total_affected";i:0;}i:44;a:2:{s:8:"sphinxql";s:22:"drop table if exists a";s:14:"total_affected";i:0;}i:45;a:2:{s:8:"sphinxql";s:14:"create table a";s:14:"total_affected";i:0;}i:46;a:2:{s:8:"sphinxql";s:23:"insert into a values(1)";s:14:"total_affected";i:1;}i:47;a:2:{s:8:"sphinxql";s:22:"drop table if exists b";s:14:"total_affected";i:0;}i:48;a:2:{s:8:"sphinxql";s:24:"create table b(a_id int)";s:14:"total_affected";i:0;}i:49;a:2:{s:8:"sphinxql";s:36:"insert into b(id, a_id) values(1, 1)";s:14:"total_affected";i:1;}i:50;a:3:{s:8:"sphinxql";s:74:"select * from a left join b on a.id = b.a_id where a.id = 1 and b.a_id = 1";s:5:"errno";i:1064;s:5:"error";s:118:"table a: table a: unknown column: id (do not prefix left table attributes in JOIN queries, use 'id' instead of 'a.id')";}i:51;a:2:{s:8:"sphinxql";s:22:"drop table if exists b";s:14:"total_affected";i:0;}i:52;a:2:{s:8:"sphinxql";s:22:"drop table if exists a";s:14:"total_affected";i:0;}}i:1;a:53:{i:0;a:2:{s:8:"sphinxql";s:105:"CREATE TABLE users (id bigint, name text, surname text, email text, reg_date timestamp) engine='columnar'";s:14:"total_affected";i:0;}i:1;a:2:{s:8:"sphinxql";s:184:"CREATE TABLE orders (id bigint, user_id bigint, name text, description text, price float, order_date timestamp, attr json, region_id int, confirm bool, analogs multi) engine='columnar'";s:14:"total_affected";i:0;}i:2;a:2:{s:8:"sphinxql";s:107:"INSERT INTO users (id, name, surname, email, reg_date) VALUES (1, 'Fedor', 'Zaycev', '1@1.com', 1708865549)";s:14:"total_affected";i:1;}i:3;a:2:{s:8:"sphinxql";s:214:"INSERT INTO orders (id, user_id, name, description, price, order_date, attr, region_id, confirm, analogs) VALUES (0, 1, 'iPhone', '14Pro', 1500.50, 1708866233, '{"color":"black","size":14}', 178, 'TRUE', (9,11,15))";s:14:"total_affected";i:1;}i:4;a:3:{s:8:"sphinxql";s:94:"SELECT id, name FROM users INNER JOIN orders ON users.id=orders.user_id ORDER BY orders.id ASC";s:10:"total_rows";i:1;s:4:"rows";a:1:{i:0;a:2:{s:2:"id";s:1:"1";s:4:"name";s:5:"Fedor";}}}i:5;a:2:{s:8:"sphinxql";s:20:"flush ramchunk users";s:14:"total_affected";i:0;}i:6;a:2:{s:8:"sphinxql";s:21:"flush ramchunk orders";s:14:"total_affected";i:0;}i:7;a:3:{s:8:"sphinxql";s:86:"SELECT * FROM users LEFT JOIN orders ON users.id=orders.user_id ORDER BY orders.id ASC";s:10:"total_rows";i:1;s:4:"rows";a:1:{i:0;a:15:{s:2:"id";s:1:"1";s:4:"name";s:5:"Fedor";s:7:"surname";s:6:"Zaycev";s:5:"email";s:7:"1@1.com";s:8:"reg_date";s:10:"1708865549";s:9:"orders.id";s:13:"1677721600001";s:14:"orders.user_id";s:1:"1";s:12:"orders.price";s:11:"1500.500000";s:17:"orders.order_date";s:10:"1708866233";s:11:"orders.attr";s:27:"{"color":"black","size":14}";s:16:"orders.region_id";s:3:"178";s:14:"orders.confirm";s:1:"1";s:14:"orders.analogs";s:7:"9,11,15";s:11:"orders.name";s:6:"iPhone";s:18:"orders.description";s:5:"14Pro";}}}i:8;a:2:{s:8:"sphinxql";s:16:"DROP TABLE users";s:14:"total_affected";i:0;}i:9;a:2:{s:8:"sphinxql";s:17:"DROP TABLE orders";s:14:"total_affected";i:0;}i:10;a:2:{s:8:"sphinxql";s:35:"CREATE TABLE tbl1 engine='columnar'";s:14:"total_affected";i:0;}i:11;a:2:{s:8:"sphinxql";s:34:"CREATE TABLE tbl2 (tbl1_id bigint)";s:14:"total_affected";i:0;}i:12;a:2:{s:8:"sphinxql";s:27:"INSERT INTO tbl1 VALUES (1)";s:14:"total_affected";i:1;}i:13;a:2:{s:8:"sphinxql";s:30:"INSERT INTO tbl2 VALUES (1, 1)";s:14:"total_affected";i:1;}i:14;a:3:{s:8:"sphinxql";s:69:"select id from tbl1 join tbl2 on tbl1.id=tbl2.tbl1_id order by id asc";s:10:"total_rows";i:1;s:4:"rows";a:1:{i:0;a:1:{s:2:"id";s:1:"1";}}}i:15;a:2:{s:8:"sphinxql";s:15:"drop table tbl2";s:14:"total_affected";i:0;}i:16;a:2:{s:8:"sphinxql";s:15:"drop table tbl1";s:14:"total_affected";i:0;}i:17;a:2:{s:8:"sphinxql";s:111:"create table join1 ( id bigint, title text, string_id integer, tmp string attribute, j json ) engine='columnar'";s:14:"total_affected";i:0;}i:18;a:2:{s:8:"sphinxql";s:93:"create table join2 ( id bigint, title text, name string attribute, j json ) engine='columnar'";s:14:"total_affected";i:0;}i:19;a:2:{s:8:"sphinxql";s:76:"insert into join1 values (1, 'title1', 1, 'tmp1', '{"a":1,"table":"join1"}')";s:14:"total_affected";i:1;}i:20;a:2:{s:8:"sphinxql";s:76:"insert into join1 values (2, 'title2', 2, 'tmp2', '{"b":2,"table":"join1"}')";s:14:"total_affected";i:1;}i:21;a:2:{s:8:"sphinxql";s:76:"insert into join1 values (3, 'title3', 3, 'tmp3', '{"c":3,"table":"join1"}')";s:14:"total_affected";i:1;}i:22;a:2:{s:8:"sphinxql";s:76:"insert into join1 values (4, 'title4', 4, 'tmp4', '{"d":4,"table":"join1"}')";s:14:"total_affected";i:1;}i:23;a:2:{s:8:"sphinxql";s:74:"insert into join2 values (1, 'title1', 'name1', '{"a":1,"table":"join2"}')";s:14:"total_affected";i:1;}i:24;a:2:{s:8:"sphinxql";s:74:"insert into join2 values (2, 'title2', 'name2', '{"b":2,"table":"join2"}')";s:14:"total_affected";i:1;}i:25;a:2:{s:8:"sphinxql";s:74:"insert into join2 values (3, 'title3', 'name3', '{"b":2,"table":"join2"}')";s:14:"total_affected";i:1;}i:26;a:3:{s:8:"sphinxql";s:117:"select * from join1 inner join join2 on join1.string_id = join2.id where join2.name = 'name1' or id=2 order by id asc";s:10:"total_rows";i:2;s:4:"rows";a:2:{i:0;a:9:{s:2:"id";s:1:"1";s:5:"title";s:6:"title1";s:9:"string_id";s:1:"1";s:3:"tmp";s:4:"tmp1";s:1:"j";s:23:"{"a":1,"table":"join1"}";s:8:"join2.id";s:1:"1";s:10:"join2.name";s:5:"name1";s:7:"join2.j";s:23:"{"a":1,"table":"join2"}";s:11:"join2.title";s:6:"title1";}i:1;a:9:{s:2:"id";s:1:"2";s:5:"title";s:6:"title2";s:9:"string_id";s:1:"2";s:3:"tmp";s:4:"tmp2";s:1:"j";s:23:"{"b":2,"table":"join1"}";s:8:"join2.id";s:1:"2";s:10:"join2.name";s:5:"name2";s:7:"join2.j";s:23:"{"b":2,"table":"join2"}";s:11:"join2.title";s:6:"title2";}}}i:27;a:3:{s:8:"sphinxql";s:108:"select * from join1 inner join join2 on join1.string_id = join2.id where id=1 or join2.j.b=2 order by id asc";s:10:"total_rows";i:3;s:4:"rows";a:3:{i:0;a:10:{s:2:"id";s:1:"1";s:5:"title";s:6:"title1";s:9:"string_id";s:1:"1";s:3:"tmp";s:4:"tmp1";s:1:"j";s:23:"{"a":1,"table":"join1"}";s:8:"join2.id";s:1:"1";s:10:"join2.name";s:5:"name1";s:7:"join2.j";s:23:"{"a":1,"table":"join2"}";s:11:"join2.title";s:6:"title1";s:9:"join2.j.b";s:1:"0";}i:1;a:10:{s:2:"id";s:1:"2";s:5:"title";s:6:"title2";s:9:"string_id";s:1:"2";s:3:"tmp";s:4:"tmp2";s:1:"j";s:23:"{"b":2,"table":"join1"}";s:8:"join2.id";s:1:"2";s:10:"join2.name";s:5:"name2";s:7:"join2.j";s:23:"{"b":2,"table":"join2"}";s:11:"join2.title";s:6:"title2";s:9:"join2.j.b";s:1:"2";}i:2;a:10:{s:2:"id";s:1:"3";s:5:"title";s:6:"title3";s:9:"string_id";s:1:"3";s:3:"tmp";s:4:"tmp3";s:1:"j";s:23:"{"c":3,"table":"join1"}";s:8:"join2.id";s:1:"3";s:10:"join2.name";s:5:"name3";s:7:"join2.j";s:23:"{"b":2,"table":"join2"}";s:11:"join2.title";s:6:"title3";s:9:"join2.j.b";s:1:"2";}}}i:28;a:2:{s:8:"sphinxql";s:16:"drop table join1";s:14:"total_affected";i:0;}i:29;a:2:{s:8:"sphinxql";s:16:"drop table join2";s:14:"total_affected";i:0;}i:30;a:2:{s:8:"sphinxql";s:111:"create table join1 ( id bigint, title text, string_id integer, tmp string attribute, j json ) engine='columnar'";s:14:"total_affected";i:0;}i:31;a:2:{s:8:"sphinxql";s:112:"create table join2 ( id bigint, title text, string_id integer, name string attribute engine='columnar', j json )";s:14:"total_affected";i:0;}i:32;a:2:{s:8:"sphinxql";s:85:"insert into join1 values (1, 'title1', 1, 'tmp1', '{"sort":5,"a":1,"table":"join1"}')";s:14:"total_affected";i:1;}i:33;a:2:{s:8:"sphinxql";s:85:"insert into join1 values (2, 'title2', 2, 'tmp2', '{"sort":6,"b":2,"table":"join1"}')";s:14:"total_affected";i:1;}i:34;a:2:{s:8:"sphinxql";s:85:"insert into join1 values (3, 'title3', 3, 'tmp3', '{"sort":7,"c":3,"table":"join1"}')";s:14:"total_affected";i:1;}i:35;a:2:{s:8:"sphinxql";s:85:"insert into join1 values (4, 'title4', 4, 'tmp4', '{"sort":8,"d":4,"table":"join1"}')";s:14:"total_affected";i:1;}i:36;a:2:{s:8:"sphinxql";s:87:"insert into join2 values (1, 'title1', 1, 'name1', '{"sort":10,"a":1,"table":"join2"}')";s:14:"total_affected";i:1;}i:37;a:2:{s:8:"sphinxql";s:86:"insert into join2 values (2, 'title2', 2, 'name2', '{"sort":5,"b":2,"table":"join2"}')";s:14:"total_affected";i:1;}i:38;a:3:{s:8:"sphinxql";s:100:"select id from join1 left join join2 on join1.string_id = join2.string_id where join2.name = 'name2'";s:10:"total_rows";i:1;s:4:"rows";a:1:{i:0;a:1:{s:2:"id";s:1:"2";}}}i:39;a:3:{s:8:"sphinxql";s:101:"select id from join1 inner join join2 on join1.string_id = join2.string_id where join2.name = 'name2'";s:10:"total_rows";i:1;s:4:"rows";a:1:{i:0;a:1:{s:2:"id";s:1:"2";}}}i:40;a:3:{s:8:"sphinxql";s:214:"select title, uint(join2.j.sort) as table2, weight() * (j.sort + table2) as test2 from join1 left join join2 on join1.string_id = join2.string_id where join2.name = 'name2' or join2.name is null order by test2 desc";s:10:"total_rows";i:3;s:4:"rows";a:3:{i:0;a:3:{s:5:"title";s:6:"title2";s:6:"table2";s:1:"5";s:5:"test2";s:2:"11";}i:1;a:3:{s:5:"title";s:6:"title4";s:6:"table2";N;s:5:"test2";s:1:"8";}i:2;a:3:{s:5:"title";s:6:"title3";s:6:"table2";N;s:5:"test2";s:1:"7";}}}i:41;a:3:{s:8:"sphinxql";s:207:"select title, join2.title, uint(join2.j.sort) as table2, weight() * (j.sort + table2) as test2 from join1 left join join2 on join1.string_id = join2.string_id where match('title2', join2) order by test2 desc";s:10:"total_rows";i:1;s:4:"rows";a:1:{i:0;a:4:{s:5:"title";s:6:"title2";s:11:"join2.title";s:6:"title2";s:6:"table2";s:1:"5";s:5:"test2";s:2:"11";}}}i:42;a:2:{s:8:"sphinxql";s:16:"drop table join1";s:14:"total_affected";i:0;}i:43;a:2:{s:8:"sphinxql";s:16:"drop table join2";s:14:"total_affected";i:0;}i:44;a:2:{s:8:"sphinxql";s:22:"drop table if exists a";s:14:"total_affected";i:0;}i:45;a:2:{s:8:"sphinxql";s:14:"create table a";s:14:"total_affected";i:0;}i:46;a:2:{s:8:"sphinxql";s:23:"insert into a values(1)";s:14:"total_affected";i:1;}i:47;a:2:{s:8:"sphinxql";s:22:"drop table if exists b";s:14:"total_affected";i:0;}i:48;a:2:{s:8:"sphinxql";s:24:"create table b(a_id int)";s:14:"total_affected";i:0;}i:49;a:2:{s:8:"sphinxql";s:36:"insert into b(id, a_id) values(1, 1)";s:14:"total_affected";i:1;}i:50;a:3:{s:8:"sphinxql";s:74:"select * from a left join b on a.id = b.a_id where a.id = 1 and b.a_id = 1";s:5:"errno";i:1064;s:5:"error";s:118:"table a: table a: unknown column: id (do not prefix left table attributes in JOIN queries, use 'id' instead of 'a.id')";}i:51;a:2:{s:8:"sphinxql";s:22:"drop table if exists b";s:14:"total_affected";i:0;}i:52;a:2:{s:8:"sphinxql";s:22:"drop table if exists a";s:14:"total_affected";i:0;}}} \ No newline at end of file +a:2:{i:0;a:53:{i:0;a:2:{s:8:"sphinxql";s:105:"CREATE TABLE users (id bigint, name text, surname text, email text, reg_date timestamp) engine='columnar'";s:14:"total_affected";i:0;}i:1;a:2:{s:8:"sphinxql";s:184:"CREATE TABLE orders (id bigint, user_id bigint, name text, description text, price float, order_date timestamp, attr json, region_id int, confirm bool, analogs multi) engine='columnar'";s:14:"total_affected";i:0;}i:2;a:2:{s:8:"sphinxql";s:107:"INSERT INTO users (id, name, surname, email, reg_date) VALUES (1, 'Fedor', 'Zaycev', '1@1.com', 1708865549)";s:14:"total_affected";i:1;}i:3;a:2:{s:8:"sphinxql";s:214:"INSERT INTO orders (id, user_id, name, description, price, order_date, attr, region_id, confirm, analogs) VALUES (0, 1, 'iPhone', '14Pro', 1500.50, 1708866233, '{"color":"black","size":14}', 178, 'TRUE', (9,11,15))";s:14:"total_affected";i:1;}i:4;a:3:{s:8:"sphinxql";s:94:"SELECT id, name FROM users INNER JOIN orders ON users.id=orders.user_id ORDER BY orders.id ASC";s:10:"total_rows";i:1;s:4:"rows";a:1:{i:0;a:2:{s:2:"id";s:1:"1";s:4:"name";s:5:"Fedor";}}}i:5;a:2:{s:8:"sphinxql";s:20:"flush ramchunk users";s:14:"total_affected";i:0;}i:6;a:2:{s:8:"sphinxql";s:21:"flush ramchunk orders";s:14:"total_affected";i:0;}i:7;a:3:{s:8:"sphinxql";s:86:"SELECT * FROM users LEFT JOIN orders ON users.id=orders.user_id ORDER BY orders.id ASC";s:10:"total_rows";i:1;s:4:"rows";a:1:{i:0;a:15:{s:2:"id";s:1:"1";s:4:"name";s:5:"Fedor";s:7:"surname";s:6:"Zaycev";s:5:"email";s:7:"1@1.com";s:8:"reg_date";s:10:"1708865549";s:9:"orders.id";s:13:"1677721600001";s:14:"orders.user_id";s:1:"1";s:12:"orders.price";s:11:"1500.500000";s:17:"orders.order_date";s:10:"1708866233";s:11:"orders.attr";s:27:"{"color":"black","size":14}";s:16:"orders.region_id";s:3:"178";s:14:"orders.confirm";s:1:"1";s:14:"orders.analogs";s:7:"9,11,15";s:11:"orders.name";s:6:"iPhone";s:18:"orders.description";s:5:"14Pro";}}}i:8;a:2:{s:8:"sphinxql";s:16:"DROP TABLE users";s:14:"total_affected";i:0;}i:9;a:2:{s:8:"sphinxql";s:17:"DROP TABLE orders";s:14:"total_affected";i:0;}i:10;a:2:{s:8:"sphinxql";s:35:"CREATE TABLE tbl1 engine='columnar'";s:14:"total_affected";i:0;}i:11;a:2:{s:8:"sphinxql";s:34:"CREATE TABLE tbl2 (tbl1_id bigint)";s:14:"total_affected";i:0;}i:12;a:2:{s:8:"sphinxql";s:27:"INSERT INTO tbl1 VALUES (1)";s:14:"total_affected";i:1;}i:13;a:2:{s:8:"sphinxql";s:30:"INSERT INTO tbl2 VALUES (1, 1)";s:14:"total_affected";i:1;}i:14;a:3:{s:8:"sphinxql";s:69:"select id from tbl1 join tbl2 on tbl1.id=tbl2.tbl1_id order by id asc";s:10:"total_rows";i:1;s:4:"rows";a:1:{i:0;a:1:{s:2:"id";s:1:"1";}}}i:15;a:2:{s:8:"sphinxql";s:15:"drop table tbl2";s:14:"total_affected";i:0;}i:16;a:2:{s:8:"sphinxql";s:15:"drop table tbl1";s:14:"total_affected";i:0;}i:17;a:2:{s:8:"sphinxql";s:111:"create table join1 ( id bigint, title text, string_id integer, tmp string attribute, j json ) engine='columnar'";s:14:"total_affected";i:0;}i:18;a:2:{s:8:"sphinxql";s:93:"create table join2 ( id bigint, title text, name string attribute, j json ) engine='columnar'";s:14:"total_affected";i:0;}i:19;a:2:{s:8:"sphinxql";s:76:"insert into join1 values (1, 'title1', 1, 'tmp1', '{"a":1,"table":"join1"}')";s:14:"total_affected";i:1;}i:20;a:2:{s:8:"sphinxql";s:76:"insert into join1 values (2, 'title2', 2, 'tmp2', '{"b":2,"table":"join1"}')";s:14:"total_affected";i:1;}i:21;a:2:{s:8:"sphinxql";s:76:"insert into join1 values (3, 'title3', 3, 'tmp3', '{"c":3,"table":"join1"}')";s:14:"total_affected";i:1;}i:22;a:2:{s:8:"sphinxql";s:76:"insert into join1 values (4, 'title4', 4, 'tmp4', '{"d":4,"table":"join1"}')";s:14:"total_affected";i:1;}i:23;a:2:{s:8:"sphinxql";s:74:"insert into join2 values (1, 'title1', 'name1', '{"a":1,"table":"join2"}')";s:14:"total_affected";i:1;}i:24;a:2:{s:8:"sphinxql";s:74:"insert into join2 values (2, 'title2', 'name2', '{"b":2,"table":"join2"}')";s:14:"total_affected";i:1;}i:25;a:2:{s:8:"sphinxql";s:74:"insert into join2 values (3, 'title3', 'name3', '{"b":2,"table":"join2"}')";s:14:"total_affected";i:1;}i:26;a:3:{s:8:"sphinxql";s:117:"select * from join1 inner join join2 on join1.string_id = join2.id where join2.name = 'name1' or id=2 order by id asc";s:10:"total_rows";i:2;s:4:"rows";a:2:{i:0;a:9:{s:2:"id";s:1:"1";s:5:"title";s:6:"title1";s:9:"string_id";s:1:"1";s:3:"tmp";s:4:"tmp1";s:1:"j";s:23:"{"a":1,"table":"join1"}";s:8:"join2.id";s:1:"1";s:10:"join2.name";s:5:"name1";s:7:"join2.j";s:23:"{"a":1,"table":"join2"}";s:11:"join2.title";s:6:"title1";}i:1;a:9:{s:2:"id";s:1:"2";s:5:"title";s:6:"title2";s:9:"string_id";s:1:"2";s:3:"tmp";s:4:"tmp2";s:1:"j";s:23:"{"b":2,"table":"join1"}";s:8:"join2.id";s:1:"2";s:10:"join2.name";s:5:"name2";s:7:"join2.j";s:23:"{"b":2,"table":"join2"}";s:11:"join2.title";s:6:"title2";}}}i:27;a:3:{s:8:"sphinxql";s:108:"select * from join1 inner join join2 on join1.string_id = join2.id where id=1 or join2.j.b=2 order by id asc";s:10:"total_rows";i:3;s:4:"rows";a:3:{i:0;a:10:{s:2:"id";s:1:"1";s:5:"title";s:6:"title1";s:9:"string_id";s:1:"1";s:3:"tmp";s:4:"tmp1";s:1:"j";s:23:"{"a":1,"table":"join1"}";s:8:"join2.id";s:1:"1";s:10:"join2.name";s:5:"name1";s:7:"join2.j";s:23:"{"a":1,"table":"join2"}";s:11:"join2.title";s:6:"title1";s:9:"join2.j.b";s:1:"0";}i:1;a:10:{s:2:"id";s:1:"2";s:5:"title";s:6:"title2";s:9:"string_id";s:1:"2";s:3:"tmp";s:4:"tmp2";s:1:"j";s:23:"{"b":2,"table":"join1"}";s:8:"join2.id";s:1:"2";s:10:"join2.name";s:5:"name2";s:7:"join2.j";s:23:"{"b":2,"table":"join2"}";s:11:"join2.title";s:6:"title2";s:9:"join2.j.b";s:1:"2";}i:2;a:10:{s:2:"id";s:1:"3";s:5:"title";s:6:"title3";s:9:"string_id";s:1:"3";s:3:"tmp";s:4:"tmp3";s:1:"j";s:23:"{"c":3,"table":"join1"}";s:8:"join2.id";s:1:"3";s:10:"join2.name";s:5:"name3";s:7:"join2.j";s:23:"{"b":2,"table":"join2"}";s:11:"join2.title";s:6:"title3";s:9:"join2.j.b";s:1:"2";}}}i:28;a:2:{s:8:"sphinxql";s:16:"drop table join1";s:14:"total_affected";i:0;}i:29;a:2:{s:8:"sphinxql";s:16:"drop table join2";s:14:"total_affected";i:0;}i:30;a:2:{s:8:"sphinxql";s:111:"create table join1 ( id bigint, title text, string_id integer, tmp string attribute, j json ) engine='columnar'";s:14:"total_affected";i:0;}i:31;a:2:{s:8:"sphinxql";s:112:"create table join2 ( id bigint, title text, string_id integer, name string attribute engine='columnar', j json )";s:14:"total_affected";i:0;}i:32;a:2:{s:8:"sphinxql";s:85:"insert into join1 values (1, 'title1', 1, 'tmp1', '{"sort":5,"a":1,"table":"join1"}')";s:14:"total_affected";i:1;}i:33;a:2:{s:8:"sphinxql";s:85:"insert into join1 values (2, 'title2', 2, 'tmp2', '{"sort":6,"b":2,"table":"join1"}')";s:14:"total_affected";i:1;}i:34;a:2:{s:8:"sphinxql";s:85:"insert into join1 values (3, 'title3', 3, 'tmp3', '{"sort":7,"c":3,"table":"join1"}')";s:14:"total_affected";i:1;}i:35;a:2:{s:8:"sphinxql";s:85:"insert into join1 values (4, 'title4', 4, 'tmp4', '{"sort":8,"d":4,"table":"join1"}')";s:14:"total_affected";i:1;}i:36;a:2:{s:8:"sphinxql";s:87:"insert into join2 values (1, 'title1', 1, 'name1', '{"sort":10,"a":1,"table":"join2"}')";s:14:"total_affected";i:1;}i:37;a:2:{s:8:"sphinxql";s:86:"insert into join2 values (2, 'title2', 2, 'name2', '{"sort":5,"b":2,"table":"join2"}')";s:14:"total_affected";i:1;}i:38;a:3:{s:8:"sphinxql";s:100:"select id from join1 left join join2 on join1.string_id = join2.string_id where join2.name = 'name2'";s:10:"total_rows";i:1;s:4:"rows";a:1:{i:0;a:1:{s:2:"id";s:1:"2";}}}i:39;a:3:{s:8:"sphinxql";s:101:"select id from join1 inner join join2 on join1.string_id = join2.string_id where join2.name = 'name2'";s:10:"total_rows";i:1;s:4:"rows";a:1:{i:0;a:1:{s:2:"id";s:1:"2";}}}i:40;a:3:{s:8:"sphinxql";s:214:"select title, uint(join2.j.sort) as table2, weight() * (j.sort + table2) as test2 from join1 left join join2 on join1.string_id = join2.string_id where join2.name = 'name2' or join2.name is null order by test2 desc";s:10:"total_rows";i:3;s:4:"rows";a:3:{i:0;a:3:{s:5:"title";s:6:"title2";s:6:"table2";s:1:"5";s:5:"test2";s:2:"11";}i:1;a:3:{s:5:"title";s:6:"title4";s:6:"table2";N;s:5:"test2";s:1:"8";}i:2;a:3:{s:5:"title";s:6:"title3";s:6:"table2";N;s:5:"test2";s:1:"7";}}}i:41;a:3:{s:8:"sphinxql";s:207:"select title, join2.title, uint(join2.j.sort) as table2, weight() * (j.sort + table2) as test2 from join1 left join join2 on join1.string_id = join2.string_id where match('title2', join2) order by test2 desc";s:10:"total_rows";i:1;s:4:"rows";a:1:{i:0;a:4:{s:5:"title";s:6:"title2";s:11:"join2.title";s:6:"title2";s:6:"table2";s:1:"5";s:5:"test2";s:2:"11";}}}i:42;a:2:{s:8:"sphinxql";s:16:"drop table join1";s:14:"total_affected";i:0;}i:43;a:2:{s:8:"sphinxql";s:16:"drop table join2";s:14:"total_affected";i:0;}i:44;a:2:{s:8:"sphinxql";s:22:"drop table if exists a";s:14:"total_affected";i:0;}i:45;a:2:{s:8:"sphinxql";s:14:"create table a";s:14:"total_affected";i:0;}i:46;a:2:{s:8:"sphinxql";s:23:"insert into a values(1)";s:14:"total_affected";i:1;}i:47;a:2:{s:8:"sphinxql";s:22:"drop table if exists b";s:14:"total_affected";i:0;}i:48;a:2:{s:8:"sphinxql";s:24:"create table b(a_id int)";s:14:"total_affected";i:0;}i:49;a:2:{s:8:"sphinxql";s:36:"insert into b(id, a_id) values(1, 1)";s:14:"total_affected";i:1;}i:50;a:3:{s:8:"sphinxql";s:74:"select * from a left join b on a.id = b.a_id where a.id = 1 and b.a_id = 1";s:10:"total_rows";i:1;s:4:"rows";a:1:{i:0;a:3:{s:2:"id";s:1:"1";s:4:"b.id";s:1:"1";s:6:"b.a_id";s:1:"1";}}}i:51;a:2:{s:8:"sphinxql";s:22:"drop table if exists b";s:14:"total_affected";i:0;}i:52;a:2:{s:8:"sphinxql";s:22:"drop table if exists a";s:14:"total_affected";i:0;}}i:1;a:53:{i:0;a:2:{s:8:"sphinxql";s:105:"CREATE TABLE users (id bigint, name text, surname text, email text, reg_date timestamp) engine='columnar'";s:14:"total_affected";i:0;}i:1;a:2:{s:8:"sphinxql";s:184:"CREATE TABLE orders (id bigint, user_id bigint, name text, description text, price float, order_date timestamp, attr json, region_id int, confirm bool, analogs multi) engine='columnar'";s:14:"total_affected";i:0;}i:2;a:2:{s:8:"sphinxql";s:107:"INSERT INTO users (id, name, surname, email, reg_date) VALUES (1, 'Fedor', 'Zaycev', '1@1.com', 1708865549)";s:14:"total_affected";i:1;}i:3;a:2:{s:8:"sphinxql";s:214:"INSERT INTO orders (id, user_id, name, description, price, order_date, attr, region_id, confirm, analogs) VALUES (0, 1, 'iPhone', '14Pro', 1500.50, 1708866233, '{"color":"black","size":14}', 178, 'TRUE', (9,11,15))";s:14:"total_affected";i:1;}i:4;a:3:{s:8:"sphinxql";s:94:"SELECT id, name FROM users INNER JOIN orders ON users.id=orders.user_id ORDER BY orders.id ASC";s:10:"total_rows";i:1;s:4:"rows";a:1:{i:0;a:2:{s:2:"id";s:1:"1";s:4:"name";s:5:"Fedor";}}}i:5;a:2:{s:8:"sphinxql";s:20:"flush ramchunk users";s:14:"total_affected";i:0;}i:6;a:2:{s:8:"sphinxql";s:21:"flush ramchunk orders";s:14:"total_affected";i:0;}i:7;a:3:{s:8:"sphinxql";s:86:"SELECT * FROM users LEFT JOIN orders ON users.id=orders.user_id ORDER BY orders.id ASC";s:10:"total_rows";i:1;s:4:"rows";a:1:{i:0;a:15:{s:2:"id";s:1:"1";s:4:"name";s:5:"Fedor";s:7:"surname";s:6:"Zaycev";s:5:"email";s:7:"1@1.com";s:8:"reg_date";s:10:"1708865549";s:9:"orders.id";s:13:"1677721600001";s:14:"orders.user_id";s:1:"1";s:12:"orders.price";s:11:"1500.500000";s:17:"orders.order_date";s:10:"1708866233";s:11:"orders.attr";s:27:"{"color":"black","size":14}";s:16:"orders.region_id";s:3:"178";s:14:"orders.confirm";s:1:"1";s:14:"orders.analogs";s:7:"9,11,15";s:11:"orders.name";s:6:"iPhone";s:18:"orders.description";s:5:"14Pro";}}}i:8;a:2:{s:8:"sphinxql";s:16:"DROP TABLE users";s:14:"total_affected";i:0;}i:9;a:2:{s:8:"sphinxql";s:17:"DROP TABLE orders";s:14:"total_affected";i:0;}i:10;a:2:{s:8:"sphinxql";s:35:"CREATE TABLE tbl1 engine='columnar'";s:14:"total_affected";i:0;}i:11;a:2:{s:8:"sphinxql";s:34:"CREATE TABLE tbl2 (tbl1_id bigint)";s:14:"total_affected";i:0;}i:12;a:2:{s:8:"sphinxql";s:27:"INSERT INTO tbl1 VALUES (1)";s:14:"total_affected";i:1;}i:13;a:2:{s:8:"sphinxql";s:30:"INSERT INTO tbl2 VALUES (1, 1)";s:14:"total_affected";i:1;}i:14;a:3:{s:8:"sphinxql";s:69:"select id from tbl1 join tbl2 on tbl1.id=tbl2.tbl1_id order by id asc";s:10:"total_rows";i:1;s:4:"rows";a:1:{i:0;a:1:{s:2:"id";s:1:"1";}}}i:15;a:2:{s:8:"sphinxql";s:15:"drop table tbl2";s:14:"total_affected";i:0;}i:16;a:2:{s:8:"sphinxql";s:15:"drop table tbl1";s:14:"total_affected";i:0;}i:17;a:2:{s:8:"sphinxql";s:111:"create table join1 ( id bigint, title text, string_id integer, tmp string attribute, j json ) engine='columnar'";s:14:"total_affected";i:0;}i:18;a:2:{s:8:"sphinxql";s:93:"create table join2 ( id bigint, title text, name string attribute, j json ) engine='columnar'";s:14:"total_affected";i:0;}i:19;a:2:{s:8:"sphinxql";s:76:"insert into join1 values (1, 'title1', 1, 'tmp1', '{"a":1,"table":"join1"}')";s:14:"total_affected";i:1;}i:20;a:2:{s:8:"sphinxql";s:76:"insert into join1 values (2, 'title2', 2, 'tmp2', '{"b":2,"table":"join1"}')";s:14:"total_affected";i:1;}i:21;a:2:{s:8:"sphinxql";s:76:"insert into join1 values (3, 'title3', 3, 'tmp3', '{"c":3,"table":"join1"}')";s:14:"total_affected";i:1;}i:22;a:2:{s:8:"sphinxql";s:76:"insert into join1 values (4, 'title4', 4, 'tmp4', '{"d":4,"table":"join1"}')";s:14:"total_affected";i:1;}i:23;a:2:{s:8:"sphinxql";s:74:"insert into join2 values (1, 'title1', 'name1', '{"a":1,"table":"join2"}')";s:14:"total_affected";i:1;}i:24;a:2:{s:8:"sphinxql";s:74:"insert into join2 values (2, 'title2', 'name2', '{"b":2,"table":"join2"}')";s:14:"total_affected";i:1;}i:25;a:2:{s:8:"sphinxql";s:74:"insert into join2 values (3, 'title3', 'name3', '{"b":2,"table":"join2"}')";s:14:"total_affected";i:1;}i:26;a:3:{s:8:"sphinxql";s:117:"select * from join1 inner join join2 on join1.string_id = join2.id where join2.name = 'name1' or id=2 order by id asc";s:10:"total_rows";i:2;s:4:"rows";a:2:{i:0;a:9:{s:2:"id";s:1:"1";s:5:"title";s:6:"title1";s:9:"string_id";s:1:"1";s:3:"tmp";s:4:"tmp1";s:1:"j";s:23:"{"a":1,"table":"join1"}";s:8:"join2.id";s:1:"1";s:10:"join2.name";s:5:"name1";s:7:"join2.j";s:23:"{"a":1,"table":"join2"}";s:11:"join2.title";s:6:"title1";}i:1;a:9:{s:2:"id";s:1:"2";s:5:"title";s:6:"title2";s:9:"string_id";s:1:"2";s:3:"tmp";s:4:"tmp2";s:1:"j";s:23:"{"b":2,"table":"join1"}";s:8:"join2.id";s:1:"2";s:10:"join2.name";s:5:"name2";s:7:"join2.j";s:23:"{"b":2,"table":"join2"}";s:11:"join2.title";s:6:"title2";}}}i:27;a:3:{s:8:"sphinxql";s:108:"select * from join1 inner join join2 on join1.string_id = join2.id where id=1 or join2.j.b=2 order by id asc";s:10:"total_rows";i:3;s:4:"rows";a:3:{i:0;a:10:{s:2:"id";s:1:"1";s:5:"title";s:6:"title1";s:9:"string_id";s:1:"1";s:3:"tmp";s:4:"tmp1";s:1:"j";s:23:"{"a":1,"table":"join1"}";s:8:"join2.id";s:1:"1";s:10:"join2.name";s:5:"name1";s:7:"join2.j";s:23:"{"a":1,"table":"join2"}";s:11:"join2.title";s:6:"title1";s:9:"join2.j.b";s:1:"0";}i:1;a:10:{s:2:"id";s:1:"2";s:5:"title";s:6:"title2";s:9:"string_id";s:1:"2";s:3:"tmp";s:4:"tmp2";s:1:"j";s:23:"{"b":2,"table":"join1"}";s:8:"join2.id";s:1:"2";s:10:"join2.name";s:5:"name2";s:7:"join2.j";s:23:"{"b":2,"table":"join2"}";s:11:"join2.title";s:6:"title2";s:9:"join2.j.b";s:1:"2";}i:2;a:10:{s:2:"id";s:1:"3";s:5:"title";s:6:"title3";s:9:"string_id";s:1:"3";s:3:"tmp";s:4:"tmp3";s:1:"j";s:23:"{"c":3,"table":"join1"}";s:8:"join2.id";s:1:"3";s:10:"join2.name";s:5:"name3";s:7:"join2.j";s:23:"{"b":2,"table":"join2"}";s:11:"join2.title";s:6:"title3";s:9:"join2.j.b";s:1:"2";}}}i:28;a:2:{s:8:"sphinxql";s:16:"drop table join1";s:14:"total_affected";i:0;}i:29;a:2:{s:8:"sphinxql";s:16:"drop table join2";s:14:"total_affected";i:0;}i:30;a:2:{s:8:"sphinxql";s:111:"create table join1 ( id bigint, title text, string_id integer, tmp string attribute, j json ) engine='columnar'";s:14:"total_affected";i:0;}i:31;a:2:{s:8:"sphinxql";s:112:"create table join2 ( id bigint, title text, string_id integer, name string attribute engine='columnar', j json )";s:14:"total_affected";i:0;}i:32;a:2:{s:8:"sphinxql";s:85:"insert into join1 values (1, 'title1', 1, 'tmp1', '{"sort":5,"a":1,"table":"join1"}')";s:14:"total_affected";i:1;}i:33;a:2:{s:8:"sphinxql";s:85:"insert into join1 values (2, 'title2', 2, 'tmp2', '{"sort":6,"b":2,"table":"join1"}')";s:14:"total_affected";i:1;}i:34;a:2:{s:8:"sphinxql";s:85:"insert into join1 values (3, 'title3', 3, 'tmp3', '{"sort":7,"c":3,"table":"join1"}')";s:14:"total_affected";i:1;}i:35;a:2:{s:8:"sphinxql";s:85:"insert into join1 values (4, 'title4', 4, 'tmp4', '{"sort":8,"d":4,"table":"join1"}')";s:14:"total_affected";i:1;}i:36;a:2:{s:8:"sphinxql";s:87:"insert into join2 values (1, 'title1', 1, 'name1', '{"sort":10,"a":1,"table":"join2"}')";s:14:"total_affected";i:1;}i:37;a:2:{s:8:"sphinxql";s:86:"insert into join2 values (2, 'title2', 2, 'name2', '{"sort":5,"b":2,"table":"join2"}')";s:14:"total_affected";i:1;}i:38;a:3:{s:8:"sphinxql";s:100:"select id from join1 left join join2 on join1.string_id = join2.string_id where join2.name = 'name2'";s:10:"total_rows";i:1;s:4:"rows";a:1:{i:0;a:1:{s:2:"id";s:1:"2";}}}i:39;a:3:{s:8:"sphinxql";s:101:"select id from join1 inner join join2 on join1.string_id = join2.string_id where join2.name = 'name2'";s:10:"total_rows";i:1;s:4:"rows";a:1:{i:0;a:1:{s:2:"id";s:1:"2";}}}i:40;a:3:{s:8:"sphinxql";s:214:"select title, uint(join2.j.sort) as table2, weight() * (j.sort + table2) as test2 from join1 left join join2 on join1.string_id = join2.string_id where join2.name = 'name2' or join2.name is null order by test2 desc";s:10:"total_rows";i:3;s:4:"rows";a:3:{i:0;a:3:{s:5:"title";s:6:"title2";s:6:"table2";s:1:"5";s:5:"test2";s:2:"11";}i:1;a:3:{s:5:"title";s:6:"title4";s:6:"table2";N;s:5:"test2";s:1:"8";}i:2;a:3:{s:5:"title";s:6:"title3";s:6:"table2";N;s:5:"test2";s:1:"7";}}}i:41;a:3:{s:8:"sphinxql";s:207:"select title, join2.title, uint(join2.j.sort) as table2, weight() * (j.sort + table2) as test2 from join1 left join join2 on join1.string_id = join2.string_id where match('title2', join2) order by test2 desc";s:10:"total_rows";i:1;s:4:"rows";a:1:{i:0;a:4:{s:5:"title";s:6:"title2";s:11:"join2.title";s:6:"title2";s:6:"table2";s:1:"5";s:5:"test2";s:2:"11";}}}i:42;a:2:{s:8:"sphinxql";s:16:"drop table join1";s:14:"total_affected";i:0;}i:43;a:2:{s:8:"sphinxql";s:16:"drop table join2";s:14:"total_affected";i:0;}i:44;a:2:{s:8:"sphinxql";s:22:"drop table if exists a";s:14:"total_affected";i:0;}i:45;a:2:{s:8:"sphinxql";s:14:"create table a";s:14:"total_affected";i:0;}i:46;a:2:{s:8:"sphinxql";s:23:"insert into a values(1)";s:14:"total_affected";i:1;}i:47;a:2:{s:8:"sphinxql";s:22:"drop table if exists b";s:14:"total_affected";i:0;}i:48;a:2:{s:8:"sphinxql";s:24:"create table b(a_id int)";s:14:"total_affected";i:0;}i:49;a:2:{s:8:"sphinxql";s:36:"insert into b(id, a_id) values(1, 1)";s:14:"total_affected";i:1;}i:50;a:3:{s:8:"sphinxql";s:74:"select * from a left join b on a.id = b.a_id where a.id = 1 and b.a_id = 1";s:10:"total_rows";i:1;s:4:"rows";a:1:{i:0;a:3:{s:2:"id";s:1:"1";s:4:"b.id";s:1:"1";s:6:"b.a_id";s:1:"1";}}}i:51;a:2:{s:8:"sphinxql";s:22:"drop table if exists b";s:14:"total_affected";i:0;}i:52;a:2:{s:8:"sphinxql";s:22:"drop table if exists a";s:14:"total_affected";i:0;}}} \ No newline at end of file