diff --git a/docs/images/inner_join.svg b/docs/images/inner_join.svg new file mode 100644 index 0000000000000000000000000000000000000000..ae7d82c70accf88f620753d9a0a99aea774850ae Binary files /dev/null and b/docs/images/inner_join.svg differ diff --git a/docs/images/left_join.svg b/docs/images/left_join.svg new file mode 100644 index 0000000000000000000000000000000000000000..70bfd86ea0009463f718d47ba2a329a738c9933a Binary files /dev/null and b/docs/images/left_join.svg differ diff --git a/docs/images/multiple_joins.svg b/docs/images/multiple_joins.svg new file mode 100644 index 0000000000000000000000000000000000000000..7ab69ff82dc50e461cf14e002496b18238785985 Binary files /dev/null and b/docs/images/multiple_joins.svg differ diff --git a/docs/reference/sql/join.md b/docs/reference/sql/join.md new file mode 100644 index 0000000000000000000000000000000000000000..d8c4d85af33fcec5db54020bccab7df300b992e7 --- /dev/null +++ b/docs/reference/sql/join.md @@ -0,0 +1,365 @@ +# ИÑпользование JOIN {: #join } + +JOIN предÑтавлÑет Ñобой параметр, иÑпользуемый при SELECT-запроÑах Ñ +целью Ð¿Ð¾Ð»ÑƒÑ‡ÐµÐ½Ð¸Ñ Ð´Ð°Ð½Ð½Ñ‹Ñ… из двух или более таблиц. Данный параметр +позволÑет ÑоединÑÑ‚ÑŒ колонки таблиц по заданному уÑловию (оператор `ON`) +и тем Ñамым Ñоздавать новую результирующую таблицу из указанных Ñтолбцов +изначальных таблиц. Соединение таблиц производитÑÑ Ñ Ð¸Ñпользованием +перекреÑтного (декартова) Ð¿Ñ€Ð¾Ð¸Ð·Ð²ÐµÐ´ÐµÐ½Ð¸Ñ Ð¸Ñ… кортежей (Ñтрок). + +См. также: + +- [SELECT](select.md) + +## РаÑположение таблиц {: #join_tables } + +Ð—Ð°Ð¿Ñ€Ð¾Ñ Ñ Ñоединением вÑегда подразумевает наличие _внешней_ таблицы, а +также одной или неÑкольких _внутренних_ таблиц. ВнешнÑÑ Ñ‚Ð°Ð±Ð»Ð¸Ñ†Ð° +находитÑÑ Ñлева от параметра `JOIN`, а внутренние — Ñправа. + +Соединение таблиц вÑегда производитÑÑ Ð¿Ð¾Ñледовательно, в порÑдке, +указанном в запроÑе: + +```sql +"table1" JOIN "table2" JOIN "table3" -> ("table1" JOIN "table2") JOIN "table3" +``` + +## Типы ÑÐ¾ÐµÐ´Ð¸Ð½ÐµÐ½Ð¸Ñ {: #join_types } + +Picodata поддерживает два типа ÑоединениÑ: `INNER JOIN` и `LEFT JOIN`. + +### INNER JOIN {: #inner_join } + +`INNER JOIN` — внутреннее Ñоединение. ИÑпользуетÑÑ Ð¿Ð¾ умолчанию в тех +ÑлучаÑÑ…, когда в запроÑе указан параметр `JOIN` без уточнениÑ. + +<div align="center"> + +</div> + +Данный тип означает, что к колонкам каждого кортежа из внутренней +(правой) чаÑти запроÑа приÑоединÑÑŽÑ‚ÑÑ Ñ‚Ð¾Ð»ÑŒÐºÐ¾ колонки тех кортежей +внешней (левой) чаÑти, которые удовлетворÑÑŽÑ‚ уÑловию ÑÐ¾ÐµÐ´Ð¸Ð½ÐµÐ½Ð¸Ñ `ON`. +ЕÑли во внешней чаÑти не нашлоÑÑŒ подходÑщего кортежа, то внутренний +кортеж не попадает в результат. + +### LEFT JOIN {: #left_join } + +`LEFT JOIN ` / `LEFT OUTER JOIN `— внешнее левое Ñоединение. + +<div align="center"> + +</div> + +Данный тип означает, что к колонкам каждого кортежа из внешней (левой) +чаÑти запроÑа приÑоединÑÑŽÑ‚ÑÑ Ñ‚Ð¾Ð»ÑŒÐºÐ¾ колонки тех кортежей внутренней +(правой) чаÑти, которые удовлетворÑÑŽÑ‚ уÑловию ÑÐ¾ÐµÐ´Ð¸Ð½ÐµÐ½Ð¸Ñ `ON`. ЕÑли во +внутренней чаÑти не нашлоÑÑŒ подходÑщего кортежа, то вмеÑто значений +его колонок будет подÑтавлен `NULL`. + +## УÑÐ»Ð¾Ð²Ð¸Ñ ÑÐ¾ÐµÐ´Ð¸Ð½ÐµÐ½Ð¸Ñ {: #join_condition } + +УÑловие ÑÐ¾ÐµÐ´Ð¸Ð½ÐµÐ½Ð¸Ñ Ð¿Ð¾Ð·Ð²Ð¾Ð»Ñет ÑопоÑтавить Ñтроки разных таблиц и ÑвлÑетÑÑ +обÑзательным Ð´Ð»Ñ Ð·Ð°Ð¿Ñ€Ð¾Ñов Ñо любым типом JOIN. УÑловие Ñледует поÑле +ключевого Ñлова `ON` и, в большинÑтве Ñлучаев, ÑоответÑтвует одному из +Ñледующих типов: + +- равенÑтво колонок (`characters.id = assets.id`) +- математичеÑкое выражение (`characters.id > 2`) +- литерал (`TRUE` / `FALSE`) + +Любое Ñоединение Ñ JOIN ÑвлÑетÑÑ Ð´ÐµÐºÐ°Ñ€Ñ‚Ð¾Ð²Ñ‹Ð¼ произведением кортежей из +внешней и внутренней таблицы Ñ Ñ„Ð¸Ð»ÑŒÑ‚Ñ€Ð°Ñ†Ð¸ÐµÐ¹ по уÑловию ÑоединениÑ. +ПоÑтому Ñ€ÐµÐ·ÑƒÐ»ÑŒÑ‚Ð¸Ñ€ÑƒÑŽÑ‰Ð°Ñ Ñ‚Ð°Ð±Ð»Ð¸Ñ†Ð° может быть как макÑимально возможного +размера (например, при уÑловии `ON TRUE` будут взаимно перемножены _вÑе_ +кортежи), так и некоторого меньшего размера, в завиÑимоÑти от заданного +уÑловиÑ. + +См. также: + +- [Выражение (expression)](select.md#expression) + +## Примеры запроÑов {: #join_examples } + +Разница между левым и внутренним Ñоединением проÑвлÑетÑÑ Ð² ÑлучаÑÑ…, +когда Ð´Ð»Ñ Ñ‡Ð°Ñти кортежей внешней таблицы отÑутÑтвуют подходÑщие под +уÑловие ÑÐ¾ÐµÐ´Ð¸Ð½ÐµÐ½Ð¸Ñ ÐºÐ¾Ñ€Ñ‚ÐµÐ¶Ð¸ из внутренней таблицы. При внутреннем +Ñоединении они будут отфильтрованы, при левом внешнем — оÑтавлены, но на +меÑте отÑутÑтвующих значений будет `nil`. + +Покажем Ñто на примере ÑÐ¾ÐµÐ´Ð¸Ð½ÐµÐ½Ð¸Ñ Ð¿Ð¾ равенÑтву колонок Ð´Ð»Ñ Ñ‚Ð°Ð±Ð»Ð¸Ñ† +`characters` и `assets`: + +<details><summary>Содержимое таблиц</summary><p> + +```sql +picodata> select * from "characters" ++----+-------------------+------+ +| id | name | year | ++===============================+ +| 1 | "Woody" | 1995 | +|----+-------------------+------| +| 2 | "Buzz Lightyear" | 1995 | +|----+-------------------+------| +| 3 | "Bo Peep" | 1995 | +|----+-------------------+------| +| 4 | "Mr. Potato Head" | 1995 | +|----+-------------------+------| +| 5 | "Woody" | 1995 | +|----+-------------------+------| +| 10 | "Duke Caboom" | 2019 | ++----+-------------------+------+ +(6 rows) +picodata> select * from "assets" ++----+------------------+-------+ +| id | name | stock | ++===============================+ +| 1 | "Woody" | 2561 | +|----+------------------+-------| +| 2 | "Buzz Lightyear" | 4781 | ++----+------------------+-------+ +(2 rows) +``` +</p></details> + +Пример левого ÑоединениÑ: + +```sql +SELECT "characters"."name", "characters"."year", "assets"."stock" +FROM "characters" +LEFT JOIN "assets" +ON "characters"."id" = "assets"."id" +``` + +Результат: + +```shell ++-------------------+-----------------+--------------+ +| characters.name | characters.year | assets.stock | ++====================================================+ +| "Woody" | 1995 | 2561 | +|-------------------+-----------------+--------------| +| "Buzz Lightyear" | 1995 | 4781 | +|-------------------+-----------------+--------------| +| "Bo Peep" | 1995 | nil | +|-------------------+-----------------+--------------| +| "Mr. Potato Head" | 1995 | nil | +|-------------------+-----------------+--------------| +| "Woody" | 1995 | nil | +|-------------------+-----------------+--------------| +| "Duke Caboom" | 2019 | nil | ++-------------------+-----------------+--------------+ +(6 rows) +``` + +Пример внутреннего ÑоединениÑ: + +```sql +SELECT "characters"."name", "characters"."year", "assets"."stock" +FROM "characters" +INNER JOIN "assets" +ON "characters"."id" = "assets"."id" +``` + +Результат: + +```shell ++------------------+-----------------+--------------+ +| characters.name | characters.year | assets.stock | ++===================================================+ +| "Woody" | 1995 | 2561 | +|------------------+-----------------+--------------| +| "Buzz Lightyear" | 1995 | 4781 | ++------------------+-----------------+--------------+ +(2 rows) +``` + +## МножеÑтвенные ÑÐ¾ÐµÐ´Ð¸Ð½ÐµÐ½Ð¸Ñ {: #multiple_joins } + +СоединÑÑ‚ÑŒ можно не только две, но и большее чиÑло таблиц. Ð’ запроÑе Ñ +неÑколькими ÑоединениÑми могут быть иÑпользованы разные комбинации +левого и внутреннего ÑоединениÑ. + +Ð”Ð»Ñ Ð¿Ñ€Ð¸Ð¼ÐµÑ€Ð° Ñ Ð´Ð²ÑƒÐ¼Ñ ÑоединениÑми задейÑтвуем третью теÑтовую таблицу. + +<details><summary>Содержимое таблицы</summary><p> + +```sql +picodata> select * from "cast" ++------------------+---------------+-------------+ +| character | actor | film | ++================================================+ +| "Bo Peep" | "Annie Potts" | "Toy Story" | +|------------------+---------------+-------------| +| "Buzz Lightyear" | "Tim Allen" | "Toy Story" | +|------------------+---------------+-------------| +| "Woody" | "Tom Hanks" | "Toy Story" | ++------------------+---------------+-------------+ +(3 rows) +``` +</details> + +Сделаем Ñоединение трех таблиц Ñ Ñ‚ÐµÐ¼, чтобы узнать актеров вÑех +перÑонажей из `characters` незавиÑимо от того, еÑÑ‚ÑŒ ли Ð´Ð»Ñ +ÑоответÑтвующих игрушек данные об оÑтатках на Ñкладе. + +<p align="center"> + +</p> + +ЗапроÑ: + +```sql +SELECT "characters"."name", "assets"."stock", "cast"."actor" +FROM "characters" +LEFT JOIN "assets" +ON "characters"."id" = "assets"."id" +JOIN "cast" +ON "characters"."name" = "cast"."character" +``` + +Результат: + +```shell ++------------------+--------------+---------------+ +| characters.name | assets.stock | cast.actor | ++=================================================+ +| "Woody" | 2561 | "Tom Hanks" | +|------------------+--------------+---------------| +| "Buzz Lightyear" | 4781 | "Tim Allen" | +|------------------+--------------+---------------| +| "Bo Peep" | nil | "Annie Potts" | +|------------------+--------------+---------------| +| "Woody" | nil | "Tom Hanks" | ++------------------+--------------+---------------+ +(4 rows) +``` + +## Перемещение данных {: #join_motions } + +При выполнении раÑпределенного запроÑа, Ñоединение таблиц может +ÑопровождатьÑÑ Ð¿Ð¾Ð»Ð½Ñ‹Ð¼ или чаÑтичным перемещением данных, либо не +требовать Ð¿ÐµÑ€ÐµÐ¼ÐµÑ‰ÐµÐ½Ð¸Ñ Ð´Ð°Ð½Ð½Ñ‹Ñ… ÑовÑем. Критерием здеÑÑŒ выÑтупает уÑловие +ÑÐ¾ÐµÐ´Ð¸Ð½ÐµÐ½Ð¸Ñ (`ON`). + +При необходимоÑти Ð¿ÐµÑ€ÐµÐ¼ÐµÑ‰ÐµÐ½Ð¸Ñ Ð´Ð°Ð½Ð½Ñ‹Ñ…, в план Ð²Ñ‹Ð¿Ð¾Ð»Ð½ÐµÐ½Ð¸Ñ Ð·Ð°Ð¿Ñ€Ð¾Ñа перед +Ñканированием внутренней таблицы добавлÑетÑÑ motion-узел, который +обеÑпечивает перекачку недоÑтающих данных на бакеты, Ñодержащие данные +внешней таблицы. + +См. также: + +- [EXPLAIN и варианты Ð¿ÐµÑ€ÐµÐ¼ÐµÑ‰ÐµÐ½Ð¸Ñ Ð´Ð°Ð½Ð½Ñ‹Ñ…](explain.md#data_motion_types) + +### ОтÑутÑтвие Ð¿ÐµÑ€ÐµÐ¼ÐµÑ‰ÐµÐ½Ð¸Ñ {: #no_motion } + +Перемещение данных не проиÑходит, еÑли в уÑловии ÑÐ¾ÐµÐ´Ð¸Ð½ÐµÐ½Ð¸Ñ Ð¸Ñпользовано +равенÑтво колонок, которые входÑÑ‚ в ключи раÑÐ¿Ñ€ÐµÐ´ÐµÐ»ÐµÐ½Ð¸Ñ ÑоответÑтвующих +таблиц. + +К примеру, в уÑловии ÑÐ¾ÐµÐ´Ð¸Ð½ÐµÐ½Ð¸Ñ ÑƒÐºÐ°Ð·Ð°Ð½Ð¾ `ON "characters"."id" = +"assets"."id"`, и таблицы "characters" и "assets" обе раÑпределены по +Ñвоим колонкам "id": + +```sql +picodata> EXPLAIN SELECT "characters"."name", "characters"."year", "assets"."stock" +FROM "characters" +LEFT JOIN "assets" +ON "characters"."id" = "assets"."id" +projection ("characters"."name"::string -> "name", "characters"."year"::integer -> "year", "assets"."stock"::integer -> "stock") + left join on ROW("characters"."id"::integer) = ROW("assets"."id"::integer) + scan "characters" + projection ("characters"."id"::integer -> "id", "characters"."name"::string -> "name", "characters"."year"::integer -> "year") + scan "characters" + scan "assets" + projection ("assets"."id"::integer -> "id", "assets"."name"::string -> "name", "assets"."stock"::integer -> "stock") + scan "assets" +``` + +### ЧаÑтичное перемещение {: #segment_motion } + +ЧаÑтичное перемещение означает, что недоÑÑ‚Ð°ÑŽÑ‰Ð°Ñ Ñ‡Ð°ÑÑ‚ÑŒ внутренней таблицы +должна быть Ñкопирована на узлы, Ñодержащие данные внешней таблицы. + +К примеру, в уÑловии ÑÐ¾ÐµÐ´Ð¸Ð½ÐµÐ½Ð¸Ñ ÑƒÐºÐ°Ð·Ð°Ð½Ð¾ `ON "characters"."id" = +"assets"."id"`, но таблица "characters" раÑпределена по колонке "id", а +"assets" — по какой-то другой колонке: + +```sql +picodata> EXPLAIN SELECT "characters"."name", "characters"."year", "assets"."stock" +FROM "characters" +LEFT JOIN "assets" +ON "characters"."id" = "assets"."id" +projection ("characters"."name"::string -> "name", "characters"."year"::integer -> "year", "assets2"."stock"::integer -> "stock") + left join on ROW("characters"."id"::integer) = ROW("assets"."id"::integer) + scan "characters" + projection ("characters"."id"::integer -> "id", "characters"."name"::string -> "name", "characters"."year"::integer -> "year") + scan "characters" + motion [policy: segment([ref("id")])] + scan "assets" + projection ("assets"."id"::integer -> "id", "assets"."name"::string -> "name", "assets"."stock"::integer -> "stock") + scan "assets" +``` + +### Полное перемещение {: #full_motion } + +Полное перемещение означает, что вÑÑ Ð²Ð½ÑƒÑ‚Ñ€ÐµÐ½Ð½ÑÑ Ñ‚Ð°Ð±Ð»Ð¸Ñ†Ð° +должна быть Ñкопирована на узлы, Ñодержащие данные внешней таблицы. + +Ð¢Ð°ÐºÐ°Ñ ÑÐ¸Ñ‚ÑƒÐ°Ñ†Ð¸Ñ Ð²Ð¾Ð·Ð½Ð¸ÐºÐ°ÐµÑ‚, еÑли в уÑловии ÑÐ¾ÐµÐ´Ð¸Ð½ÐµÐ½Ð¸Ñ ÑƒÐºÐ°Ð·Ð°Ð½Ð° колонка +внешней таблицы, не входÑÑ‰Ð°Ñ Ð² ее ключ раÑпределениÑ. + +К примеру, в уÑловии ÑÐ¾ÐµÐ´Ð¸Ð½ÐµÐ½Ð¸Ñ ÑƒÐºÐ°Ð·Ð°Ð½Ð¾ `ON "characters"."id" = +"assets"."id"`, но таблица "characters" раÑпределена по какой-то другой +колонке, в то Ð²Ñ€ÐµÐ¼Ñ ÐºÐ°Ðº "assets" раÑпределена по "id": + +```sql +picodata> EXPLAIN SELECT "characters"."name", "characters"."year", "assets"."stock" +FROM "characters" +LEFT JOIN "assets" +ON "characters"."id" = "assets"."id" +projection ("characters"."name"::string -> "name", "characters"."year"::integer -> "year", "assets"."stock"::integer -> "stock") + left join on ROW("characters"."id"::integer) = ROW("assets"."id"::integer) + scan "characters" + projection ("characters"."id"::integer -> "id", "characters"."name"::string -> "name", "characters"."year"::integer -> "year") + scan "characters" + motion [policy: full] + scan "assets" + projection ("assets"."id"::integer -> "id", "assets"."name"::string -> "name", "assets"."stock"::integer -> "stock") + scan "assets" +``` + +Также, при иÑпользовании математичеÑких выражений или литералов, перемещение вÑегда будет полным: + +```sql +picodata> EXPLAIN SELECT "characters"."name", "characters"."year", "assets"."stock" +FROM "characters" +LEFT JOIN "assets" +ON "characters"."id" = 1 +projection ("characters"."name"::string -> "name", "characters"."year"::integer -> "year", "assets"."stock"::integer -> "stock") + left join on ROW("characters"."id"::integer) = ROW(1::unsigned) + scan "characters" + projection ("characters"."id"::integer -> "id", "characters"."name"::string -> "name", "characters"."year"::integer -> "year") + scan "characters" + motion [policy: full] + scan "assets" + projection ("assets"."id"::integer -> "id", "assets"."name"::string -> "name", "assets"."stock"::integer -> "stock") + scan "assets" +``` + +```sql +picodata> EXPLAIN SELECT "characters"."name", "characters"."year", "assets"."stock" +FROM "characters" +LEFT JOIN "assets" +ON TRUE +projection ("characters"."name"::string -> "name", "characters"."year"::integer -> "year", "assets"."stock"::integer -> "stock") + left join on true::boolean + scan "characters" + projection ("characters"."id"::integer -> "id", "characters"."name"::string -> "name", "characters"."year"::integer -> "year") + scan "characters" + motion [policy: full] + scan "assets" + projection ("assets"."id"::integer -> "id", "assets"."name"::string -> "name", "assets"."stock"::integer -> "stock") + scan "assets" + +``` diff --git a/docs/reference/sql/select.md b/docs/reference/sql/select.md index 8b32ac76a19ab9ce6b0560d9656fdeda6752e607..8fcad789f02acbbd5ca913295fa5bb6369c6acb0 100644 --- a/docs/reference/sql/select.md +++ b/docs/reference/sql/select.md @@ -74,7 +74,11 @@ NOTE: **Примечание** Кортежи в выводе идут в том неÑколько `EXCEPT DISTINCT` подрÑд. Чтобы обойти Ñто ограничение, Ñледует воÑпользоватьÑÑ Ð¿Ð¾Ð´Ð·Ð°Ð¿Ñ€Ð¾Ñами. -## Примеры {: #examples } +См. также: + +- [ИÑпользование JOIN](join.md) + +## Примеры {: #examples } Получение данных из таблицы Ñ Ñ„Ð¸Ð»ÑŒÑ‚Ñ€Ð°Ñ†Ð¸ÐµÐ¹: diff --git a/docs/sql_index.md b/docs/sql_index.md index 40041bb24cf1c1efe59ea013d155db9c40150e09..1c0257a68983b49bbccdd2824d2a55fdbe0e54bd 100644 --- a/docs/sql_index.md +++ b/docs/sql_index.md @@ -32,6 +32,10 @@ * [UPDATE](reference/sql/update.md) * [VALUES](reference/sql/values.md) +## СинтакÑÐ¸Ñ {: #syntax } + +* [ИÑпользование JOIN](reference/sql/join.md) + ## Функции и операторы {: #functions } * [Ðгрегатные функции](reference/sql/aggregate.md) diff --git a/mkdocs.yml b/mkdocs.yml index c5da806a8fb902f4174b5158e130df5db1042d93..04e4a8da570948a401a7009322f91775f125a93a 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -75,6 +75,8 @@ nav: - reference/sql/select.md - reference/sql/update.md - reference/sql/values.md + - СинтакÑиÑ: + - reference/sql/join.md - Функции и операторы: - reference/sql/aggregate.md - reference/sql/cast.md