Предприятие join php. Разбираемся с mySQL JOIN, визуальное представление
Поводом для написания данной статьи послужили некоторые дебаты в одной из групп linkedin, связанной с MySQL, а также общение с коллегами и хабролюдьми:-)
В данной статье хотел написать что такое вообще JOINы в MySQL и как можно оптимизировать запросы с ними.
Что такое JOINы в MySQL
В MySQL термин JOIN используется гораздо шире, чем можно было бы предположить. Здесь JOINом может называться не только запрос объединяющий результаты из нескольких таблиц, но и запрос к одной таблице, например, SELECT по одной таблице - это тоже джоин.
Все потому, что алгоритм выполнения джоинов в MySQL реализован с использованием вложенных циклов. Т.е. каждый последующий JOIN это дополнительный вложенный цикл. Чтобы выполнить запрос и вернуть все записи удовлетворяющие условию MySQL выполняет цикл и пробегает по записям первой таблицы параллельно проверяя соответствия условиям описанных в теле запроса, когда находятся записи, удовлетворяющие условиям - во вложенном цикле по второй таблице ищутся записи соответствующие первым и удовлетворяющие условиям проверки и т.д.
Прмер обычного запроса с INNER JOIN
SELECT
*
FROM
Table1
INNER JOIN
Table2 ON P1(Table1,Table2)
INNER JOIN
Table3 ON P2(Table2,Table3)
WHERE
P(Table1,Table2,Table3).
Где Р - условия склейки таблиц и фильтры в WHERE условии.
Можно представить такой псевдокод выполнения такого запроса.
FOR each row t1 in Table1 {
IF(P(t1)) {
FOR each row t2 in Table2 {
IF(P(t2)) {
FOR each row t3 in Table3 {
IF P(t3) {
t:=t1||t2||t3; OUTPUT t;
}
}
}
}
}
}
* This source code was highlighted with Source Code Highlighter .
Где конструкция t1||t2||t3 означает конкатенацию столбцов из разных таблиц.
Если в запросе встречаются OUTER JOINs, например, LEFT OUTER JOIN
SELECT
*
FROM
Table1
LEFT JOIN
Table2 LEFT JOIN Table3 ON P2(Table2,Table3)
ON P1(Table1,Table2)
WHERE
P(Table1,Table2,Tabke3)
* This source code was highlighted with Source Code Highlighter .
То алгоритм выполнения этого запроса MySQL будет выглядеть как-то так
FOR each row t1 in T1 {
BOOL f1:=FALSE;
FOR each row t2 in T2 such that P1(t1,t2) {
BOOL f2:=FALSE;
FOR each row t3 in T3 such that P2(t2,t3) {
IF P(t1,t2,t3) {
t:=t1||t2||t3; OUTPUT t;
}
f2=TRUE;
f1=TRUE;
}
IF (!f2) {
IF P(t1,t2,NULL) {
t:=t1||t2||NULL; OUTPUT t;
}
f1=TRUE;
}
}
IF (!f1) {
IF P(t1,NULL,NULL) {
t:=t1||NULL||NULL; OUTPUT t;
}
}
}
* This source code was highlighted with Source Code Highlighter .
Итак, как мы видим, JOINы это просто группа вложенных циклов. Так почему же в MySQL и UNION и SELECT и запросы с SUBQUERY тоже джоины?
MySQL оптимизатор старается приводить запросы к тому виду к которому ему удобней обрабатывать и выполнять запросы по стандартной схеме.
С SELECT все понятно - просто цикл без вложенных циклов. Все UNION выполняются как отдельные запросы и результаты складываются во временную таблицу, и потом MySQL работает уже с этой таблицей, т.е. проходясь циклом по записям в ней. С Subquery та же история.
Приводя все к одному шаблону, например, МySQL переписывает все RIGHT JOIN запросы на LEFT JOIN эквиваленты.
Но стратегия выполнения запросов через вложенные циклы накладывает некоторые ограничения, например, в связи с такой схемой MySQL не поддерживает выполнение FULL OUTER JOIN запросов.
Но результат такого запроса можно получить с помощью UNION двух запросов на LEFT JOIN и на RIGHT JOIN
Пример самого запроса можно посмотреть по ссылке на вики.
План выполнения JOIN запросов
В отличии от других СУРБД MySQL не генерирует байткод для выполнения запроса, вместо этого MySQL генерирует список инструкций в древовидной форме, которых придерживается engine выполнения запроса выполняя запрос.
Это дерево имеет следующий вид и имеет название «left-deep tree»
В отличии от сбалансированных деревьев (Bushy plan), которые применяются в других СУБД (например Oracle)
JOIN оптимизация
Теперь перейдем к самому интересному - к оптимизации джоинов.
MySQL оптимизатор, а именно та его часть, которая отвечает за оптимизацию JOIN-ов выбирает порядок в котором будет производиться склейка имеющихся таблиц, т.к. можно получить один и тот же результат (датасет) при различном порядке таблиц в склейке. MySQL оптимизатор оценивает стоимость различных планов и выбирает с наименьшей стоимостью. Единицей оценки является операция единичного чтения страницы данных размером в 4 килобайта из произвольного места на диске.
Для выбранного плана можно узнать стоимость путем выполнения команды
SHOW SESSION STATUS LIKE "Last_query_cost";
Оценка основана на статистике: количество страниц памяти, занимаемое таблицей и/или индексами для этой таблицы, cardinality (число уникальных значений) индексов, длинна записей и индексов, их распределение и т.д. Во время своей оценки оптимизатор не рассчитывает на то, что какие-то части попадут в кеш, оптимизатор предполагает, что каждая операция чтения это обращение к диску.
Иногда анализатор-оптимизатор не может проанализировать все возможные планы выполнения и выбирает неправильный. Например, если у нас INNER JOIN по 3м таблицам, то возможных вариантов у анализатора - 3! = 6, а если у нас склейка по 10 таблицам, то тут возможных вариантов уже 10! = 3628800… MySQL не может проанализировать столько вариантов, поэтому в таком случае он использует алгоритм "жадного " поиска.
Но не стоит применять этот хак ко всем запросам, расчитывая произвести оптимизацию на спичках и сэкономить время на составление плана выполнения запроса оптимизатором и добавлять STRAIGH_JOIN ко всем запросам с джоинами , т.к. данные меняются и склейка, которая оптимальна сейчас может перестать быть оптимальной со временем, и тогда запросы начнуть очень сильно лагать.
Также, как уже говорилось выше, результаты джоинов помещаются во временные таблицы, поэтому зачастую уместно применять «derived table» в котором мы накладываем все необходимые нам условия на выборку, а также указываем LIMIT и порядок сортировки. В данном случае мы избавимся от избыточности данных во временной таблице, а также проведем сортировку на раннем этапе (по результату одной выборки, а не финальной склейки, что уменьшит размеры записей которые будут сортироваться).
Стандартный пример подхода описанного выше. Простая выборка для отношения много к многим: новости и теги к ним.
SELECT
t.tid, t.description, n.nid, n.title, n.extract , n.modtime
FROM
SELECT
n.nid
FROM
news n
WHERE
n.type = 1321
AND n.published = 1
AND status = 1
ORDER BY
n.modtime DESC
LIMIT
200
) as news
INNER JOIN
news n ON n.nid = news.nid
INNER JOIN
news_tag nt ON n.nid = nt.nid
INNER JOIN
tags t ON nt.tid = t.tid
* This source code was highlighted with Source Code Highlighter .
Ну и на последок небольшая задачка, которую я иногда задаю на собеседованиях:-)
Есть новостной блоггерный сайт. Есть такие сущности как новости и комментарии к ним.
Задача - нужно написать запрос, который выводит список из 10 новостей определенного типа (задается пользователем) отсортированные по времени издания в хронологическом порядке, а также к каждой из этих новостей показать не более 10 последних коментариев, т.е. если коментариев больше - показываем только последние 10.
Все нужно сделать одним запросом. Да, это, может, и не самый лучший способ, и вы вольны предложить другое решение:-)
Теги:
- MySQL
- mysql performance
- JOIN
MySQL поддерживает следующий синтаксис оператора JOIN при использовании в командах SELECT:
Table_reference, table_reference table_reference JOIN table_reference table_reference INNER JOIN table_reference join_condition table_reference STRAIGHT_JOIN table_reference table_reference LEFT JOIN table_reference join_condition table_reference LEFT JOIN table_reference table_reference NATURAL ] JOIN table_reference { oj table_reference LEFT OUTER JOIN table_reference ON conditional_expr } table_reference RIGHT JOIN table_reference join_condition table_reference RIGHT JOIN table_reference table_reference NATURAL ] JOIN table_reference
где table_reference определено, как:
Table_name [ alias]
и join_condition определено, как:
ON conditional_expr | USING (column_list)
Никогда не следует указывать в части ON какие бы то ни было условия, накладывающие ограничения на строки в наборе результатов. Если необходимо указать, какие строки должны присутствовать в результате, следует сделать это в выражении WHERE .
Необходимо учитывать, что в версиях до 3.23.17 оператор INNER JOIN не принимает параметр join_condition !
Наличие последней из приведенных выше конструкций выражения LEFT OUTER JOIN обусловлено только требованиями совместимости с ODBC:
- Вместо ссылки на таблицу может использоваться псевдоним, который присваивается при помощи выражений tbl_name AS alias_name или tbl_name alias_name: mysql> SELECT t1.name, t2.salary FROM employee AS t1, info AS t2 WHERE t1.name = t2.name;
- Условный оператор ON представляет собой условие в любой форме из числа тех, которые можно использовать в выражении WHERE .
- Если запись для правой таблицы в частях ON или USING в LEFT JOIN не найдена, то для данной таблицы используется строка, в которой все столбцы установлены в NULL . Эту возможность можно применять для нахождения результатов в таблице, не имеющей эквивалента в другой таблице: mysql> SELECT table1.* FROM table1 LEFT JOIN table2 ON table1.id=table2.id WHERE table2.id IS NULL; Этот пример находит все строки в таблице table1 с величиной id , которая не присутствует в таблице table2 (т.е. все строки в table1 , для которых нет соответствующих строк в table2). Конечно, это предполагает, что table2.id объявлен как NOT NULL . See section 5.2.6 Как MySQL оптимизирует LEFT JOIN и RIGHT JOIN .
- USING (column_list) служит для указания списка столбцов, которые должны существовать в обеих таблицах. Такое выражение USING , как: A LEFT JOIN B USING (C1,C2,C3,...) семантически идентично выражению ON , например: A.C1=B.C1 AND A.C2=B.C2 AND A.C3=B.C3,...
- Выражение NATURAL JOIN для двух таблиц определяется так, чтобы оно являлось семантическим эквивалентом INNER JOIN или LEFT JOIN с выражением USING , в котором указаны все столбцы, имеющиеся в обеих таблицах.
- INNER JOIN и, (запятая) являются семантическими эквивалентами. Оба осуществляют полное объединение используемых таблиц. Способ связывания таблиц обычно задается в условии WHERE .
- RIGHT JOIN работает аналогично LEFT JOIN . Для сохранения переносимости кода между различными базами данных рекомендуется вместо RIGHT JOIN использовать LEFT JOIN .
- STRAIGHT_JOIN идентично JOIN , за исключением того, что левая таблица всегда читается раньше правой. Это выражение может использоваться для тех (немногих) случаев, когда оптимизатор объединения располагает таблицы в неправильном порядке.
- Начиная с версии MySQL 3.23.12, можно давать MySQL указания о том, какой индекс должен использоваться при извлечении информации из таблицы. Эта возможность полезна, если оператор EXPLAIN (выводящий информацию о структуре и порядке выполнения запроса SELECT), показывает, что MySQL использует ошибочный индекс. Задавая значение индекса в USE INDEX (key_list) , можно заставить MySQL применять для поиска записи только один из указанных индексов. Альтернативное выражение IGNORE INDEX (key_list) запрещает использование в MySQL данного конкретного индекса. Выражения USE/IGNORE KEY являются синонимами для USE/IGNORE INDEX .
Несколько примеров:
Mysql> SELECT * FROM table1,table2 WHERE table1.id=table2.id; mysql> SELECT * FROM table1 LEFT JOIN table2 ON table1.id=table2.id; mysql> SELECT * FROM table1 LEFT JOIN table2 USING (id); mysql> SELECT * FROM table1 LEFT JOIN table2 ON table1.id=table2.id LEFT JOIN table3 ON table2.id=table3.id; mysql> SELECT * FROM table1 USE INDEX (key1,key2) WHERE key1=1 AND key2=2 AND key3=3; mysql> SELECT * FROM table1 IGNORE INDEX (key3) WHERE key1=1 AND key2=2 AND key3=3;
В этом учебном пособии вы узнаете, как использовать JOINS (INNER и OUTER)
в MySQL с синтаксисом, рисунками и примерами.
Описание
MySQL JOINS используются для извлечения данных из нескольких таблиц. JOIN выполняется всякий раз, когда две или более таблиц объединяются в SQL предложении.
Существуют различные типы соединений MySQL:
Рассмотрим синтаксис MySQL JOIN, а также изучим примеры MySQL JOIN.
INNER JOIN (простое соединение)
Скорее всего, вы уже писали запросы в которых используются MySQL INNER JOIN. Это наиболее распространенный тип соединения. MySQL INNER JOINS возвращает все строки из нескольких таблиц, где выполняется условия соединения.
Синтаксис
Синтаксис INNER JOIN в MySQL:
SELECT columns
FROM table1
INNER JOIN table2
В этом рисунке, MySQL INNER JOIN возвращает затененную область:
MySQL INNER JOIN будет возвращать записи, где table1 и table2 будут пересекаться.
Пример
Ниже приведен пример MySQL INNER JOIN:
MySQL
У нас есть еще одна таблица orders с тремя полями (order_id , supplier_id и order_date ). Она содержит следующие данные:
order_id | supplier_id | order_date |
---|---|---|
500125 | 10000 | 05.05.2015 |
500126 | 10001 | 08.02.2016 |
500127 | 10004 | 06.01.2017 |
Если мы выполним MySQL оператор SELECT (который содержит INNER JOIN) ниже:
MySQL
SELECT suppliers.supplier_id, suppliers.supplier_name, orders.order_date FROM suppliers INNER JOIN orders ON suppliers.supplier_id = orders.supplier_id;
Строки для Microsoft и NVIDIA из таблицы suppliers будут опущены, так как значения supplier_id 10002 и 10003 не существует в обеих таблицах. Строка order_id 500127 из таблицы orders будет опущена, так как supplier_id 10004 не существует в таблице suppliers .
Старый Синтаксис
В качестве последнего примечания, стоит отметить, что приведенный выше пример MySQL INNER JOIN можно переписать, используя старый неявный синтаксис следующим образом (но рекомендуется использовать синтаксис INNER JOIN):
Другой тип соединения называется MySQL LEFT OUTER JOIN. Этот тип соединения возвращает все строки из таблиц с левосторонним соединением, указанным в условии ON, и только те строки из другой таблицы, где объединяемые поля равны.
Синтаксис
Синтаксис для LEFT OUTER JOIN в MySQL:
SELECT columns
FROM table1
LEFT JOIN table2
ON table1.column = table2.column;
В некоторых базах данных LEFT OUTER JOIN заменяется на LEFT JOIN.
На этом рисунке, MySQL LEFT OUTER JOIN возвращает затененную область:
MySQL LEFT OUTER JOIN возвратит все записи из table1 и только те записи из table2 , которые пересекаются с table1 .
Пример
MySQL
SELECT suppliers.supplier_id, suppliers.supplier_name, orders.order_date FROM suppliers LEFT JOIN orders ON suppliers.supplier_id = orders.supplier_id;
SELECT suppliers.supplier_id,suppliers.supplier_name,orders.order_date FROM suppliers LEFT JOIN orders ON suppliers.supplier_id=orders.supplier_id; |
Этот пример LEFT OUTER JOIN возвратит все строки из таблицы suppliers , и только те строки из таблицы orders , где объединяемые поля равны.
Если значение supplier_id в таблице suppliers не существует в таблице orders , все поля таблицы orders будут отображаться в результирующем наборе как NULL.
Рассмотрим некоторые данные, чтобы понять, как работает LEFT OUTER JOIN:
У нас есть таблица suppliers с двумя полями (supplier_id и supplier_name ) которая содержит следующие данные:
Если мы выполним MySQL оператор SELECT (который содержит LEFT OUTER JOIN) ниже:
MySQL
SELECT suppliers.supplier_id, suppliers.supplier_name, orders.order_date FROM suppliers LEFT OUTER JOIN orders ON suppliers.supplier_id = orders.supplier_id;
Строки для Microsoft и NVIDIA будут включены, так как был использован LEFT OUTER JOIN. Тем не менее, вы заметите, что поле order_date для этих записей содержит значение NULL.
Другой тип соединения называется MySQL RIGHT OUTER JOIN. Этот тип соединения возвращает все строки из таблиц с правосторонним соединением, указанным в условии ON, и только те строки из другой таблицы, где объединяемые поля равны.
Синтаксис
СинтаксиRIGHT OUTER JOIN в MySQL:
SELECT columns
FROM table1
RIGHT JOIN table2
ON table1.column = table2.column;
В некоторых базах данных, RIGHT OUTER JOIN заменяется на RIGHT JOIN.
На этом рисунке, MySQL RIGHT OUTER JOIN возвращает затененную область:
MySQL RIGHT OUTER JOIN возвратит все записи из table2 и только те записи из table1 , которые пересекаются с table2 .
Пример
Ниже приведен пример MySQL RIGHT OUTER JOIN:
MySQL
SELECT orders.order_id, orders.order_date, suppliers.supplier_name FROM suppliers RIGHT JOIN orders ON suppliers.supplier_id = orders.supplier_id;
Поводом для написания данной статьи послужили некоторые дебаты в одной из групп linkedin, связанной с MySQL, а также общение с коллегами и хабролюдьми:-)
В данной статье хотел написать что такое вообще JOINы в MySQL и как можно оптимизировать запросы с ними.
Что такое JOINы в MySQL
В MySQL термин JOIN используется гораздо шире, чем можно было бы предположить. Здесь JOINом может называться не только запрос объединяющий результаты из нескольких таблиц, но и запрос к одной таблице, например, SELECT по одной таблице - это тоже джоин.
Все потому, что алгоритм выполнения джоинов в MySQL реализован с использованием вложенных циклов. Т.е. каждый последующий JOIN это дополнительный вложенный цикл. Чтобы выполнить запрос и вернуть все записи удовлетворяющие условию MySQL выполняет цикл и пробегает по записям первой таблицы параллельно проверяя соответствия условиям описанных в теле запроса, когда находятся записи, удовлетворяющие условиям - во вложенном цикле по второй таблице ищутся записи соответствующие первым и удовлетворяющие условиям проверки и т.д.
Прмер обычного запроса с INNER JOIN
SELECT
*
FROM
Table1
INNER JOIN
Table2 ON P1(Table1,Table2)
INNER JOIN
Table3 ON P2(Table2,Table3)
WHERE
P(Table1,Table2,Table3).
Где Р - условия склейки таблиц и фильтры в WHERE условии.
Можно представить такой псевдокод выполнения такого запроса.
FOR each row t1 in Table1 {
IF(P(t1)) {
FOR each row t2 in Table2 {
IF(P(t2)) {
FOR each row t3 in Table3 {
IF P(t3) {
t:=t1||t2||t3; OUTPUT t;
}
}
}
}
}
}
* This source code was highlighted with Source Code Highlighter .
Где конструкция t1||t2||t3 означает конкатенацию столбцов из разных таблиц.
Если в запросе встречаются OUTER JOINs, например, LEFT OUTER JOIN
SELECT
*
FROM
Table1
LEFT JOIN
Table2 LEFT JOIN Table3 ON P2(Table2,Table3)
ON P1(Table1,Table2)
WHERE
P(Table1,Table2,Tabke3)
* This source code was highlighted with Source Code Highlighter .
То алгоритм выполнения этого запроса MySQL будет выглядеть как-то так
FOR each row t1 in T1 {
BOOL f1:=FALSE;
FOR each row t2 in T2 such that P1(t1,t2) {
BOOL f2:=FALSE;
FOR each row t3 in T3 such that P2(t2,t3) {
IF P(t1,t2,t3) {
t:=t1||t2||t3; OUTPUT t;
}
f2=TRUE;
f1=TRUE;
}
IF (!f2) {
IF P(t1,t2,NULL) {
t:=t1||t2||NULL; OUTPUT t;
}
f1=TRUE;
}
}
IF (!f1) {
IF P(t1,NULL,NULL) {
t:=t1||NULL||NULL; OUTPUT t;
}
}
}
* This source code was highlighted with Source Code Highlighter .
Итак, как мы видим, JOINы это просто группа вложенных циклов. Так почему же в MySQL и UNION и SELECT и запросы с SUBQUERY тоже джоины?
MySQL оптимизатор старается приводить запросы к тому виду к которому ему удобней обрабатывать и выполнять запросы по стандартной схеме.
С SELECT все понятно - просто цикл без вложенных циклов. Все UNION выполняются как отдельные запросы и результаты складываются во временную таблицу, и потом MySQL работает уже с этой таблицей, т.е. проходясь циклом по записям в ней. С Subquery та же история.
Приводя все к одному шаблону, например, МySQL переписывает все RIGHT JOIN запросы на LEFT JOIN эквиваленты.
Но стратегия выполнения запросов через вложенные циклы накладывает некоторые ограничения, например, в связи с такой схемой MySQL не поддерживает выполнение FULL OUTER JOIN запросов.
Но результат такого запроса можно получить с помощью UNION двух запросов на LEFT JOIN и на RIGHT JOIN
Пример самого запроса можно посмотреть по ссылке на вики.
План выполнения JOIN запросов
В отличии от других СУРБД MySQL не генерирует байткод для выполнения запроса, вместо этого MySQL генерирует список инструкций в древовидной форме, которых придерживается engine выполнения запроса выполняя запрос.
Это дерево имеет следующий вид и имеет название «left-deep tree»
В отличии от сбалансированных деревьев (Bushy plan), которые применяются в других СУБД (например Oracle)
JOIN оптимизация
Теперь перейдем к самому интересному - к оптимизации джоинов.
MySQL оптимизатор, а именно та его часть, которая отвечает за оптимизацию JOIN-ов выбирает порядок в котором будет производиться склейка имеющихся таблиц, т.к. можно получить один и тот же результат (датасет) при различном порядке таблиц в склейке. MySQL оптимизатор оценивает стоимость различных планов и выбирает с наименьшей стоимостью. Единицей оценки является операция единичного чтения страницы данных размером в 4 килобайта из произвольного места на диске.
Для выбранного плана можно узнать стоимость путем выполнения команды
SHOW SESSION STATUS LIKE "Last_query_cost";
Оценка основана на статистике: количество страниц памяти, занимаемое таблицей и/или индексами для этой таблицы, cardinality (число уникальных значений) индексов, длинна записей и индексов, их распределение и т.д. Во время своей оценки оптимизатор не рассчитывает на то, что какие-то части попадут в кеш, оптимизатор предполагает, что каждая операция чтения это обращение к диску.
Иногда анализатор-оптимизатор не может проанализировать все возможные планы выполнения и выбирает неправильный. Например, если у нас INNER JOIN по 3м таблицам, то возможных вариантов у анализатора - 3! = 6, а если у нас склейка по 10 таблицам, то тут возможных вариантов уже 10! = 3628800… MySQL не может проанализировать столько вариантов, поэтому в таком случае он использует алгоритм "жадного " поиска.
Но не стоит применять этот хак ко всем запросам, расчитывая произвести оптимизацию на спичках и сэкономить время на составление плана выполнения запроса оптимизатором и добавлять STRAIGH_JOIN ко всем запросам с джоинами , т.к. данные меняются и склейка, которая оптимальна сейчас может перестать быть оптимальной со временем, и тогда запросы начнуть очень сильно лагать.
Также, как уже говорилось выше, результаты джоинов помещаются во временные таблицы, поэтому зачастую уместно применять «derived table» в котором мы накладываем все необходимые нам условия на выборку, а также указываем LIMIT и порядок сортировки. В данном случае мы избавимся от избыточности данных во временной таблице, а также проведем сортировку на раннем этапе (по результату одной выборки, а не финальной склейки, что уменьшит размеры записей которые будут сортироваться).
Стандартный пример подхода описанного выше. Простая выборка для отношения много к многим: новости и теги к ним.
SELECT
t.tid, t.description, n.nid, n.title, n.extract , n.modtime
FROM
SELECT
n.nid
FROM
news n
WHERE
n.type = 1321
AND n.published = 1
AND status = 1
ORDER BY
n.modtime DESC
LIMIT
200
) as news
INNER JOIN
news n ON n.nid = news.nid
INNER JOIN
news_tag nt ON n.nid = nt.nid
INNER JOIN
tags t ON nt.tid = t.tid
* This source code was highlighted with Source Code Highlighter .
Ну и на последок небольшая задачка, которую я иногда задаю на собеседованиях:-)
Есть новостной блоггерный сайт. Есть такие сущности как новости и комментарии к ним.
Задача - нужно написать запрос, который выводит список из 10 новостей определенного типа (задается пользователем) отсортированные по времени издания в хронологическом порядке, а также к каждой из этих новостей показать не более 10 последних коментариев, т.е. если коментариев больше - показываем только последние 10.
Все нужно сделать одним запросом. Да, это, может, и не самый лучший способ, и вы вольны предложить другое решение:-)
Теги: Добавить метки
Кроме обычных простых запросов с простыми условиями язык SQL позволяет составлять довольно сложные выражения, на вил которых порой страшно смотреть, такими непонятными они выглядят. Однако такие запросы встречаются не очень часто, и следующий уровень сложности после обычного SELECT на мой взгляд идет команда JOIN. Давайте разберемся как с ней работать.
Для начала давайте посмотрим простейшие часто употребимые запросы.
SELECT COUNT(*) FROM table; //подсчет кол-ва записей SELECT * FROM table LIMIT 5,10; //начиная с 5-ой записи выберет 10 строк. чаще всего используется для постраничной навигации SELECT * FROM table ORDER BY num; //отсортирует все записи из таблицы по полю num SELECT * FROM table WHERE year="1990"; //выбрать запись/записи где значение year равно 1990 SELECT * FROM table WHERE name LIKE "%ova%"; //поиск в таблице имени в котором есть последовательность "ova" SELECT DISTINCT name FROM table; //если есть повторяющиеся поля то они будут учтены только один раз SELECT * FROM name WHERE age IN (12,15,18); // выведет имена которым соответствуют поля с возрастом 12,15,18 SELECT MAX(age) FROM table; //MAX/MIN - выберет запись с максимальным или минимальным значением age
Теперь перейдем непосредственно к примерам JOIN:
Очень популярная задача где используется команда JOIN это объединение нескольких таблиц имеющий какой то общий признак. Рассмотрим пример. Допустим у нас есть две таблицы. В первой хранятся данные об имени и адресе юзера и также есть колонка user_id. Во второй колонке у нас также есть колонка user_id и колонка с домашним индексом юзера.
SELECT name, addres FROM table1 LEFT JOIN table2 ON table1.user_id=table2.user_id;
Чтобы было еще понятнее, то этот запрос можно переписать классическим образом:
SELECT table1.name, table1.addres, table2.index FROM table1, table2 WHERE table1.user_id = table2.user_id;
Таким образом можно получить данные из нескольких таблиц, а если есть дополнительное условие то допишем и его:
SELECT name, addres FROM table1 LEFT JOIN table2 ON table1.user_id=table2.user_id WHERE name = "Вася";
Если окунуться в теорию, то добавим что есть следующие варианты JOIN:
Тут будет уместно нарисовать знаменитые кружочки:
INNER JOIN
возвращает пересечение двух множеств
SELECT t1.name, t2.city FROM Table1 t1 INNER JOIN Table2 t2 ON t1.key2 = t2.key2;
Отражением INNER JOIN является OUTER JOIN. Нам предоставлено три типа OUTER JOIN – FULL, LEFT и RIGHT. Слово OUTER писать не обязательно.
FULL JOIN
Объединяет два множества
FULL JOIN вернет ВСЕ записи из таблиц table и table2, без повторяющихся данных. Где данных нет, будет подставленно NULL.
LEFT JOIN
Возвращает данные из левой таблицы, а также данные из правой, которые пересекаются с левой.
Если данных из правой таблицы будет не хватать, то подставится NULL значение.
RIGHT JOIN – как вы наверное поняли, вернет все значения из правой таблицы и пересекающиеся данные из левой.
Исключения
Если нам понадобятся данные из первой таблицы для которых нет данных в правой, то нам просто надо добавить условие WHERE
SELECT t1.name, t2.city FROM Table1 t1 LEFT JOIN Table2 t2 ON t1.key2 = t2.key2 WHERE t2.key2 IS NULL;
Множественные JOIN
С помощью JOIN можно соединить не только 2 таблицы, а сколько надо. Если вам надо соединить 2 таблицы, то потребуется 2 команды JOIN. Но не стоит впадать в крайность и объединять кучу таблиц, все это ощутимо скажется на производительности, поэтому иногда лучше выполнить несколько подзапросов. Пример соединения трех таблиц:
SELECT t1.Name, t2.City, t3.Profession FROM Table1 t1 INNER JOIN Table2 t2 ON t1.key2 = t2.key2 INNER JOIN Table3 t3 ON t1.key3 = t3.key3;
Что же, надеюсь перечисленные простые примеры JOIN помогли вам разобраться с этим оператором.