Проблема медленных SQL запросов
Сайт, использующий в процессе работы базу данных, в процессе генерации одной страницы сайта обращается к серверу баз данных несколько десятков, а иногда и сотен раз. При этом каждый запрос представляет собой инструкцию, которую, несмотря на большой объем данных, сервер БД способен выполнить за очень короткое время – в пределах нескольких миллисекунд. Общее время исполнения сотен запросов не превышает одной – двух секунд, в штатном режиме работы один сервер баз данных обслуживает до 500 запросов в секунду.
Такая высокая производительность – результат технически отлаженного строения реляционной структуры баз данных, эффективного хранения информации, индексации данных. Все эти меры помогают найти необходимые данные немедленно.
Общее представление о процессе исполнение SQL запроса
Типичный SQL запрос заключается в просьбе найти и предоставить данные, имеющие какие-то характеристики. Например – найти текст определенного сообщения в форуме, либо построить список сообщений в одной теме, либо оценить их количество, другие аналогичные запросы. Существует несколько сценариев исполнения запросов.
Обычное исполнение запроса заключается в переборе данных, находящихся в таблице. Например, при поиске сообщения в форуме, общее количество сообщений которого занимает 20 Мб, самым простым способом ответа на такой вопрос был бы перебор всех сообщений с целью поиска нужного (сравнение номеров сообщений с требуемым номером). При этом с диска считывается информация общим объемом 20 Мб (для данного примера). Это заняло бы несколько секунд или даже несколько десятков секунд.
Индекс в SQL сервере – дополнительная информация, связанная с данными в таблицах, которая помогает получить ответы на некоторые запросы без перебора всего объема данных таблицы. В данном примере индекс по номеру сообщения позволил бы найти сообщение, считав с диска только минимальный набор информации, необходимый для поиска сообщения – в данном примере это был бы объем в несколько килобайт. Это в тысячи или десятки тысяч раз меньше, чем требуется для ответа на вопрос обычным способом, без использования индексов.
При проектировании структуры базы данных для одной таблицы может быть создано любое количество индексов, которые позволяют мгновенно отвечать на вопросы определенных типов.
Например, индекс по номеру сообщения позволяет мгновенно получить сообщение, зная его номер. Индекс по автору сообщение позволяет быстро получить список сообщений, написанных определенным автором. Индекс по дате позволяет быстро получить номера или текст последних 10 сообщений (для примера).
Любая грамотная созданная база данных включает в себя индексы, которые позволяют быстро ответить на все SQL запросы, которые могут возникнуть в процессе использования приложения.
Медленные запросы
Случаются ситуации, при которых сервер баз данных не может ответить на запрос данных в пределах короткого времени. Запрос считается медленным, если его обработка заняла более 10 секунд.
Для каждого медленного запроса фиксируются следующие параметры:
- Время выполнения запроса – общее время, которое затратил сервер баз данных на исполнение вашего запроса, проделывая какие-то операции.
- Время блокировок – время, которое сервер баз данных потратил на исполнение вашего запроса, но во время которого никаких действий не производилось, т.к. сервер баз данных ожидал готовности других объектов или завершения предыдущих либо конфликтующих операций.
- Количество просмотренных строк – количество строк таблицы, которое пришлось считать с диска в процессе обработки SQL запроса.
- Количество просмотренных строк (tmp) – аналогично предыдущему пункту, но отражает количество созданных и просмотренных строк временных и вспомогательных таблиц, которыми пришлось воспользоваться.
Использование индексов кардинально уменьшает количество просмотренных строк таблицы, так как при наличии подходящего индекса для поиска информации можно не просматривать сами строки вообще.
Использование индекса крайне эффективно и поэтому не подвергается никакому учету. Можно считать, что запрос, использующий только индексы, исполняется практически мгновенно и без использования каких либо заметных ресурсов.
Случайный медленный запрос
По причинам случайного характера любой запрос может иногда выполняться медленно.
- Запрос INSERT, UPDATE может выполниться медленно по причине ожидания окончания обработки других запросов, либо по причине занятости дисковой системы сервера в данный момент времени. Это является нормальным поведением сервера и не мешает его работе.
- Запрос SELECT мог случайно выполниться медленно также по причине локальной (мгновенной) нагрузки на сервере. Признаком случайности данного факта служат однократное повторение явления и небольшое количество используемых строк таблицы (обычно десятки, максимум — в пределах нескольких тысяч).
Случайные медленные запросы не являются признаком проблем со стороны скриптов или структуры базы данных пользователя.
Проблемный медленный запрос
Если вы получили информацию о том, что запрос, который исполняется в вашей базе данных, вызывает проблему, значит, медленное исполнение вашего запроса носит системный характер. Медленная скорость выполнения такого запроса обусловлена не случайным стечением обстоятельств, а тем, что для ответа на ваш запрос требуются большое количество дисковых либо процессорных ресурсов сервера.
Исполнение такого запроса вызывает заметное замедление в работе других клиентов того же сервера базы данных.
Медленные запросы такого типа классифицировать на следующие популярные случаи (для примера):
- Простой запрос вида .. WHERE field=[value], WHERE field>[value], WHERE fieldОбычно это означает либо отсутствие необходимого индекса по полю field, либо большое количество строк в качестве ответа на запрос.
- Простой запрос вида .. WHERE field LIKE ‘%. %’, заметное количество просмотренных строк (больше 1 -2 тысяч).
В процессе исполнения запроса LIKE использование индексов невозможно. Происходит полный перебор всех данных, которые хранятся в базе. - Сложный запрос для нескольких таблиц, большое количество просмотренных строк (от 10 тыс и до миллионов).
В процессе исполнения запроса использование индексов невозможно, либо оптимизатор mySQL не смог понять, как использовать индексы для ответа на данный вопрос. В результате серверу пришлось создать временную таблицу с указанным количеством строк.
Возможны многочисленные иные случаи, описать их, к сожалению, нельзя, т.к. они разнообразны.
Общее место всех описанных сценариев заключается в том, что для ответа на подобный запрос сервер вынужден использовать обращение к диску, общим количеством сравнимым с количеством просмотренных строк, общим объемом сравнимым с объемом данных в таблицах, к которым происходит обращение.
Суть сообщения о превышении нагрузки заключается в том, что десятки тысяч обращений к диску либо десятки мегабайт считанной информации – огромная избыточная нагрузка на сервер, которая на виртуальном хостинге просто неприемлема.
Проблема отладки запроса
Поскольку в большинстве случаев для ответа на медленный запрос требуются дисковые ресурсы сервера, может сложиться ситуация, когда повторное исполнение медленного запроса происходит почти мгновенно. Это происходит потому, что система сервера какое-то время сохраняет в памяти содержимое используемых таблиц, и дальнейшее обращение к ним происходит мгновенно.
При условии большого количества просмотренных строк это не является признаком того, что запрос исполняется эффективно.
Пути решения проблемы
В общем случае виртуальный хостинг не может предложить решения для описанной проблемы со стороны заказа или оплаты дополнительных услуг. Нагрузка, которая происходит при исполнении неоптимального длинного запроса, требует эксклюзивного использования ресурсов сервера, виртуальный хостинг не обладает техническими возможностями для такой работы.
Можно предложить следующие пути решения проблемы:
- Анализ необходимости данного запроса. Возможно, модуль или часть сайта, которая использует данный запрос, не нужна или может быть легко заменена другой, переконфигурирована.
- Необходимо рассмотреть возможность сокращения количества информации в таблицах, которые имеют отношение к запросу. Часто бывает, что медленные запросы происходят при обработке таблиц, накапливающих статистику посещений сайта или иную информацию, которая, фактически, может быть сокращена.
- От запросов LIKE можно отказаться, т.к. поисковая система, основанная на запросах такого вида, в сотни и тысячи раз менее эффективна, чем любая традиционная поисковая система, основанная на списке слов. Поиск через списки слов реализован, например, во всех популярных форумах. Программисту не составит труда реализовать нормальный поиск без использования оператора LIKE, либо использовать полнотекстовый поиск.
- С помощью оператора EXPLAIN SELECT можно проанализировать, используются ли при ответе на запрос индексы, и если да, то в каком объеме. Возможно, простым способом оптимизации явилось бы создание необходимых или дополнительных индексов, если они еще не созданы.
Программист, хорошо понимающий принципы работы баз данных, способен проанализировать и оптимизировать практически любую ситуацию, избавившись от медленных, неэффективных запросов.
К сожалению, множество стандартных продуктов, таких как форумы, порталы, не всегда написаны с учетом возможности подобной оптимизации. Квалификация их авторов не всегда достаточна для написания оптимальных запросов и создания оптимальной структуры базы данных. Популярность пакета, форума, портала не является аргументом в пользу его технической грамотности.
Тарифные планы с большой абонентской платой, такие, как планы линейки «Профи» (П-20 и выше), позволяют до какой-то степени использовать медленные запросы в повседневной работе. Однако это не является рекомендованной практикой, также, возможность исполнения медленных запросов не может гарантироваться.
Если исполнение большого количества медленных запросов приводит к угрозе работоспособности сервера, ваша база данных, вне зависимости от тарифного плана, может быть отключена.
Дополнительная информация
Для оптимизации работы с mySQL вы можете воспользоваться документацией, которая доступна на сайте http://dev.mysql.com/doc/, в том числе на русском языке.
Вопросы, описанные в этой статье, являются основными вопросами работы базы данных и описаны в документации гораздо более подробно и точно.
- Технический регламент, правила работы
- Быстродействие, ресурсы сервера
- Если сайт открывается медленно
- Количество запросов в день и лимиты
- Перегрузка процессора (CPU)
- Проблема медленных SQL запросов
- Решение проблем — услуга VIP VDS
- Решение проблем — VDS или VIP хостинг
- Другая информация по этой теме
Другие разделы документации
- FAQ (часто задаваемые вопросы)
- Техническое описание услуг
- 1GbWiki — множество полезных статей от наших сотрудников
- Рекомендации по программированию
Работа с запросами в MySQL
Базы данных являются ключевым компонентом многих веб-сайтов и приложений и лежат в основе хранения и обмена данными в Интернете. Одним из наиболее важных аспектов управления базой данных является практика извлечения данных из базы данных, будь то на разовой основе или частью процесса, который был закодирован в приложении. Существует несколько способов получения информации из базы данных, но один из наиболее часто используемых методов выполняется путем отправки запросов через командную строку.
В системах управления реляционными базами данных запрос — это любая команда, используемая для извлечения данных из таблицы. В языке структурированных запросов (SQL) запросы почти всегда выполняются с помощью оператора SELECT.
В этом руководстве мы обсудим основной синтаксис SQL-запросов, а также некоторые из наиболее часто используемых функций и операторов. Мы также будем практиковаться в создании запросов SQL с использованием некоторых примеров данных в базе данных MySQL.
MySQL — это система управления реляционными базами данных с открытым исходным кодом. MySQL, одна из наиболее распространенных баз данных SQL, отдает приоритет скорости, надежности и удобству использования. Как правило, она соответствует стандарту ANSI SQL, хотя в некоторых случаях MySQL выполняет операции не так, как признанный стандарт.
Подготовка окружения
В целом, команды и концепции, представленные в этом руководстве, могут использоваться в любой операционной системе на базе Linux, на которой работает любое программное обеспечение базы данных SQL. Тем не менее, он был написан специально для сервера Ubuntu 18.04. Для настройки вам понадобится следующее:
- Сервер с Ubuntu 18.04
- Установленный на сервере MySQL
Если сервер создается в NetAngels, то при создании сервера мы рекомендуем выбрать Ubuntu 18.04 Bionic LAMP
Создание образца базы данных
Прежде чем мы сможем начать делать запросы в SQL, мы сначала создадим базу данных и пару таблиц, а затем заполним эти таблицы некоторыми примерами данных. Это позволит вам получить практический опыт, когда вы начнете делать запросы позже.
Для примера базы данных, которую мы будем использовать в этом руководстве, представьте следующий сценарий:
Вы и несколько ваших друзей празднуете свои дни рождения друг с другом. В каждом случае члены группы направляются в местный боулинг, участвуют в дружеском турнире, а затем все направляются к вам, где вы готовите любимое блюдо для именинника.
Теперь, что эта традиция продолжается некоторое время, вы решили начать отслеживать записи с этих турниров. Кроме того, чтобы упростить планирование обедов, вы решаете создать запись о днях рождения ваших друзей и их любимых блюдах, сторонах и десертах. Вместо того чтобы хранить эту информацию в физической книге, вы решаете использовать свои навыки работы с базами данных, записав ее в базу данных MySQL.
Если вы создали сервер в NetAngels на основе образа Ubuntu 18.04 Bionic LAMP, то откройте приглашение MySQL выполнив от пользователя root команду:
Примечание: Если зайти в MySQL таким образом не удается, то для аутентификации с использованием пароля используйте команду:
Затем создайте базу данных, запустив:
CREATE DATABASE `birthdays`;Затем выберите эту базу данных, набрав:
USE birthdays;Затем создайте две таблицы в этой базе данных. Мы будем использовать первую таблицу, чтобы отслеживать записи ваших друзей в боулинге. Следующая команда создаст таблицу под названием «tourneys» со столбцами для «name» каждого из ваших друзей, количества турниров, которые они выиграли («wins»), их лучший результат за все время и каков размер обувь для боулинга, которую они носят ( размер ):
CREATE TABLE tourneys ( name varchar(30), wins real, best real, size real );Как только вы запустите команду CREATE TABLE и заполните ее заголовками столбцов, вы получите следующий вывод:
Query OK, 0 rows affected (0.00 sec)Заполните таблицу ‘tourneys’ некоторыми примерами данных:
INSERT INTO tourneys (name, wins, best, size) VALUES ('Dolly', '7', '245', '8.5'), ('Etta', '4', '283', '9'), ('Irma', '9', '266', '7'), ('Barbara', '2', '197', '7.5'), ('Gladys', '13', '273', '8');Вы получите такой вывод:
Query OK, 5 rows affected (0.01 sec) Records: 5 Duplicates: 0 Warnings: 0После этого создайте еще одну таблицу в той же базе данных, которую мы будем использовать для хранения информации о любимых блюдах ваших друзей на день рождения. Следующая команда создает таблицу с именем dinners и столбцами для«имя» каждого из ваших друзей, их «дата рождения», их любимое «блюдо», их любимое «гарнир» и их любимый «десерт»:
CREATE TABLE dinners ( name varchar(30), birthdate date, entree varchar(30), side varchar(30), dessert varchar(30) );Аналогично для этой таблицы вы получите отзыв, подтверждающий успешное выполнение команды:
Query OK, 0 rows affected (0.01 sec)Заполните эту таблицу также некоторыми примерами данных:
INSERT INTO dinners (name, birthdate, entree, side, dessert) VALUES ('Dolly', '1946-01-19', 'steak', 'salad', 'cake'), ('Etta', '1938-01-25', 'chicken', 'fries', 'ice cream'), ('Irma', '1941-02-18', 'tofu', 'fries', 'cake'), ('Barbara', '1948-12-25', 'tofu', 'salad', 'ice cream'), ('Gladys', '1944-05-28', 'steak', 'fries', 'ice cream'); Query OK, 5 rows affected (0.00 sec) Records: 5 Duplicates: 0 Warnings: 0Как только эта команда завершится успешно, вы закончили настройку базы данных. Далее мы рассмотрим основную структуру команд запросов SELECT.
Понимание операторов SELECT
Как упоминалось во введении, SQL-запросы почти всегда начинаются с оператора SELECT . SELECT используется в запросах, чтобы указать, какие столбцы из таблицы должны быть возвращены в наборе результатов. Запросы также почти всегда включают FROM , который используется для указания таблицы, к которой будет обращаться оператор.
Как правило, SQL-запросы следуют этому синтаксису:
SELECT column_to_select FROM table_to_select WHERE certain_conditions_apply;Например, следующий оператор вернет весь столбец name из таблицы dinners :
SELECT name FROM dinners; +---------+ | name | +---------+ | Dolly | | Etta | | Irma | | Barbara | | Gladys | +---------+ 5 rows in set (0.00 sec)Вы можете выбрать несколько столбцов из одной таблицы, разделяя их имена запятыми, например:
SELECT name, birthdate FROM dinners; +---------+------------+ | name | birthdate | +---------+------------+ | Dolly | 1946-01-19 | | Etta | 1938-01-25 | | Irma | 1941-02-18 | | Barbara | 1948-12-25 | | Gladys | 1944-05-28 | +---------+------------+ 5 rows in set (0.00 sec)Вместо того, чтобы называть конкретный столбец или набор столбцов, вы можете следовать за оператором SELECT со звездочкой ( * ), которая служит заполнителем, представляющим все столбцы в таблице. Следующая команда возвращает каждый столбец из таблицы tourneys :
SELECT * FROM tourneys; +---------+------+------+------+ | name | wins | best | size | +---------+------+------+------+ | Dolly | 7 | 245 | 8.5 | | Etta | 4 | 283 | 9 | | Irma | 9 | 266 | 7 | | Barbara | 2 | 197 | 7.5 | | Gladys | 13 | 273 | 8 | +---------+------+------+------+ 5 rows in set (0.00 sec)WHERE используется в запросах для фильтрации записей, которые удовлетворяют указанному условию, и любые строки, которые не удовлетворяют этому условию, исключаются из результата. Предложение WHERE обычно соответствует следующему синтаксису:
. . . WHERE column_name comparison_operator valueОператор сравнения в предложении WHERE определяет способ сравнения указанного столбца со значением. Вот некоторые распространенные операторы сравнения SQL:
Оператор Что он делает = тесты для равенства != тесты для неравенства тесты для больше = тесты для больше чем или равный к BETWEEN проверяет лежит ли в заданном диапазоне IN проверяет содержатся ли строки в наборе значений EXISTS тесты на соответствие строки существует при заданных условиях LIKE проверяет совпадает ли значение с указанной строкой IS NULL тесты для `NULL` значения IS NOT NULL тесты для всех других значений, чем `NULL` Например, если вы хотите найти размер обуви Ирмы, вы можете использовать следующий запрос:
SELECT size FROM tourneys WHERE name = 'Irma'; +------+ | size | +------+ | 7 | +------+ 1 row in set (0.00 sec)SQL допускает использование подстановочных знаков, и это особенно удобно при использовании в предложениях WHERE. Знаки процента ( % ) представляют ноль или более неизвестных символов, а подчеркивания ( _ ) представляют один неизвестный символ. Они полезны, если вы пытаетесь найти конкретную запись в таблице, но не уверены, что эта запись. Чтобы проиллюстрировать это, скажем, что вы забыли любимое блюдо нескольких своих друзей, но вы уверены, что это конкретное блюдо начинается с буквы “t”. Вы можете найти его имя, выполнив следующий запрос:
SELECT entree FROM dinners WHERE entree LIKE 't%'; +--------+ | entree | +--------+ | tofu | | tofu | +--------+ 2 rows in set (0.00 sec)Основываясь на вышеприведенном выводе, мы видим, что блюдо — это тофу.
Могут быть случаи, когда вы работаете с базами данных, в которых есть столбцы или таблицы с относительно длинными или трудно читаемыми именами. В этих случаях вы можете сделать эти имена более читабельными, создав псевдоним с ключевым словом AS . Псевдонимы, созданные с помощью AS , являются временными и существуют только на время запроса, для которого они созданы:
SELECT name AS n, birthdate AS b, dessert AS d FROM dinners; +---------+------------+-----------+ | n | b | d | +---------+------------+-----------+ | Dolly | 1946-01-19 | cake | | Etta | 1938-01-25 | ice cream | | Irma | 1941-02-18 | cake | | Barbara | 1948-12-25 | ice cream | | Gladys | 1944-05-28 | ice cream | +---------+------------+-----------+ 5 rows in set (0.00 sec)Здесь мы сказали SQL отображать столбец name как n , столбец birthdate как b , а столбец sert как d .
Примеры, которые мы рассмотрели до этого момента, включают в себя некоторые из наиболее часто используемых ключевых слов и предложений в запросах SQL. Они полезны для базовых запросов, но они бесполезны, если вы пытаетесь выполнить вычисление или получить скалярное значение (одно значение, а не набор из нескольких различных значений) на основе ваших данных. Это где агрегатные функции вступают в игру.
Агрегатные функции
Часто при работе с данными необязательно просматривать сами данные. Скорее, вам нужна информация о данных. Синтаксис SQL включает в себя ряд функций, которые позволяют интерпретировать или выполнять вычисления для ваших данных, просто выполнив запрос «SELECT». Они известны как aggregate functions.
Функция COUNT считает и возвращает количество строк, соответствующих определенным критериям. Например, если вы хотите узнать, сколько ваших друзей предпочитают тофу для своего дня рождения, вы можете выполнить этот запрос:
SELECT COUNT(entree) FROM dinners WHERE entree = 'tofu'; +---------------+ | COUNT(entree) | +---------------+ | 2 | +---------------+ 1 row in set (0.00 sec)Функция AVG возвращает среднее (среднее) значение столбца. Используя наш пример таблицы, вы можете найти средний лучший результат среди ваших друзей с помощью этого запроса:
SELECT AVG(best) FROM tourneys; +-----------+ | AVG(best) | +-----------+ | 252.8 | +-----------+ 1 row in set (0.00 sec)SUM используется для поиска общей суммы данного столбца. Например, если вы хотите посмотреть, сколько игр вы и ваши друзья играли в боулинг за эти годы, вы можете выполнить этот запрос:
SELECT SUM(wins) FROM tourneys; +-----------+ | SUM(wins) | +-----------+ | 35 | +-----------+ 1 row in set (0.00 sec)Обратите внимание, что функции AVG и SUM будут работать правильно только при использовании с числовыми данными. Если вы попытаетесь использовать их для нечисловых данных, это приведет к ошибке или просто к «0», в зависимости от того, какую СУБД вы используете:
SELECT SUM(entree) FROM dinners; +-------------+ | SUM(entree) | +-------------+ | 0 | +-------------+ 1 row in set, 5 warnings (0.00 sec)MIN используется для поиска наименьшего значения в указанном столбце. Вы можете использовать этот запрос, чтобы увидеть, какой худший общий рекорд в боулинге (с точки зрения количества побед):
SELECT MIN(wins) FROM tourneys; +-----------+ | MIN(wins) | +-----------+ | 2 | +-----------+ 1 row in set (0.00 sec)Аналогично, MAX используется для поиска наибольшего числового значения в данном столбце. Следующий запрос покажет лучший общий результат в боулинге:
SELECT MAX(wins) FROM tourneys; +-----------+ | MAX(wins) | +-----------+ | 13 | +-----------+ 1 row in set (0.00 sec)В отличие от SUM и AVG , функции MIN и MAX могут использоваться как для числовых, так и для буквенных типов данных. При запуске в столбце, содержащем строковые значения, функция MIN будет отображать первое значение в алфавитном порядке:
SELECT MIN(name) FROM dinners; +-----------+ | MIN(name) | +-----------+ | Barbara | +-----------+ 1 row in set (0.00 sec)Аналогично, при запуске в столбце, содержащем строковые значения, функция MAX покажет последнее значение в алфавитном порядке:
SELECT MAX(name) FROM dinners; +-----------+ | MAX(name) | +-----------+ | Irma | +-----------+ 1 row in set (0.00 sec)Агрегатные функции умеют больше того, что было описано в этом разделе. Они особенно полезны при использовании с предложением GROUP BY , которое рассматривается в следующем разделе, а также с несколькими другими предложениями запроса, которые влияют на сортировку наборов результатов.
Управление запросами
В дополнение к предложениям FROM и WHERE , есть несколько других предложений, которые используются для манипулирования результатами запроса SELECT . В этом разделе мы объясним и предоставим примеры для некоторых из наиболее часто используемых предложений запросов.
Одним из наиболее часто используемых предложений запроса, помимо FROM и WHERE , является предложение GROUP BY . Обычно он используется, когда вы выполняете статистическую функцию для одного столбца, но в отношении сопоставления значений в другом.
Например, скажем, вы хотели знать, сколько ваших друзей предпочитают каждый из трех блюд, которые вы делаете. Вы можете найти эту информацию с помощью следующего запроса:
SELECT COUNT(name), entree FROM dinners GROUP BY entree; +-------------+---------+ | COUNT(name) | entree | +-------------+---------+ | 1 | chicken | | 2 | steak | | 2 | tofu | +-------------+---------+ 3 rows in set (0.00 sec)Предложение ORDER BY используется для сортировки результатов запроса. По умолчанию числовые значения сортируются в порядке возрастания, а текстовые значения сортируются в алфавитном порядке. Чтобы проиллюстрировать это, следующий запрос перечисляет столбцы name и birthdate , но сортирует результаты по дате рождения:
SELECT name, birthdate FROM dinners ORDER BY birthdate; +---------+------------+ | name | birthdate | +---------+------------+ | Etta | 1938-01-25 | | Irma | 1941-02-18 | | Gladys | 1944-05-28 | | Dolly | 1946-01-19 | | Barbara | 1948-12-25 | +---------+------------+ 5 rows in set (0.00 sec)Обратите внимание, что поведение по умолчанию ORDER BY состоит в сортировке набора результатов в порядке возрастания. Чтобы изменить это и отсортировать набор результатов в порядке убывания, закройте запрос с помощью DESC :
SELECT name, birthdate FROM dinners ORDER BY birthdate DESC; +---------+------------+ | name | birthdate | +---------+------------+ | Barbara | 1948-12-25 | | Dolly | 1946-01-19 | | Gladys | 1944-05-28 | | Irma | 1941-02-18 | | Etta | 1938-01-25 | +---------+------------+ 5 rows in set (0.00 sec)Как уже упоминалось ранее, предложение WHERE используется для фильтрации результатов на основе определенных условий. Однако, если вы используете предложение WHERE с агрегатной функцией, оно вернет ошибку, как в случае со следующей попыткой выяснить, какие стороны являются фаворитами по крайней мере трех ваших друзей:
SELECT COUNT(name), side FROM dinners WHERE COUNT(name) >= 3; ERROR 1111 (HY000): Invalid use of group functionПредложение HAVING было добавлено в SQL для обеспечения функциональности, аналогичной функциональности предложения WHERE, а также совместимости с агрегатными функциями. Полезно думать о разнице между этими двумя пунктами как о том, что WHERE применяется к отдельным записям, в то время как HAVING применяется к групповым записям. С этой целью каждый раз, когда вы вводите предложение HAVING , также должно присутствовать предложение GROUP BY .
Следующий пример — еще одна попытка найти, какие гарниры являются фаворитами как минимум трех ваших друзей, хотя этот вернет результат без ошибок:
SELECT COUNT(name), side FROM dinners GROUP BY side HAVING COUNT(name) >= 3; +-------------+-------+ | COUNT(name) | side | +-------------+-------+ | 3 | fries | +-------------+-------+ 1 row in set (0.00 sec)Агрегатные функции полезны для суммирования результатов определенного столбца в данной таблице. Однако во многих случаях необходимо запросить содержимое более чем одной таблицы. Мы рассмотрим несколько способов сделать это в следующем разделе.
Запрос нескольких таблиц
Чаще всего база данных содержит несколько таблиц, каждая из которых содержит разные наборы данных. SQL предоставляет несколько разных способов выполнения одного запроса для нескольких таблиц.
Предложение JOIN может использоваться для объединения строк из двух или более таблиц в результате запроса. Это достигается путем нахождения связанного столбца между таблицами и соответствующей сортировки результатов в выходных данных.
Операторы SELECT , которые включают предложение JOIN , обычно следуют этому синтаксису:
SELECT table1.column1, table2.column2 FROM table1 JOIN table2 ON table1.related_column=table2.related_column;Обратите внимание, что поскольку предложения JOIN сравнивают содержимое нескольких таблиц, в предыдущем примере указывается, из какой таблицы выбрать каждый столбец, предшествуя имени столбца с именем таблицы и точкой. Вы можете указать, из какой таблицы должен быть выбран столбец, например, для любого запроса, хотя это не обязательно при выборе из одной таблицы, как мы делали в предыдущих разделах. Давайте рассмотрим пример, используя наш образец Dата.
Представьте, что вы хотите купить каждому из ваших друзей пару ботинок для боулинга в качестве подарка на день рождения. Поскольку информация о датах рождения и размерах обуви ваших друзей хранится в отдельных таблицах, вы можете запросить обе таблицы по отдельности, а затем сравнить результаты для каждой из них. Тем не менее, с помощью предложения JOIN вы можете найти всю необходимую информацию с помощью одного запроса:
SELECT tourneys.name, tourneys.size, dinners.birthdate FROM tourneys JOIN dinners ON tourneys.name=dinners.name; +---------+------+------------+ | name | size | birthdate | +---------+------+------------+ | Dolly | 8.5 | 1946-01-19 | | Etta | 9 | 1938-01-25 | | Irma | 7 | 1941-02-18 | | Barbara | 7.5 | 1948-12-25 | | Gladys | 8 | 1944-05-28 | +---------+------+------------+ 5 rows in set (0.00 sec)Предложение JOIN , используемое в этом примере, без каких-либо других аргументов, является предложением inner JOIN . Это означает, что он выбирает все записи, которые имеют совпадающие значения в обеих таблицах, и печатает их в наборе результатов, в то время как все несоответствующие записи исключаются. Чтобы проиллюстрировать эту идею, давайте добавим новую строку в каждую таблицу, у которой нет соответствующей записи в другой:
INSERT INTO tourneys (name, wins, best, size) VALUES ('Bettye', '0', '193', '9'); INSERT INTO dinners (name, birthdate, entree, side, dessert) VALUES ('Lesley', '1946-05-02', 'steak', 'salad', 'ice cream');Затем повторно запустите предыдущий оператор SELECT с предложением JOIN:
SELECT tourneys.name, tourneys.size, dinners.birthdate FROM tourneys JOIN dinners ON tourneys.name=dinners.name; +---------+------+------------+ | name | size | birthdate | +---------+------+------------+ | Dolly | 8.5 | 1946-01-19 | | Etta | 9 | 1938-01-25 | | Irma | 7 | 1941-02-18 | | Barbara | 7.5 | 1948-12-25 | | Gladys | 8 | 1944-05-28 | +---------+------+------------+ 5 rows in set (0.00 sec)Обратите внимание, что, поскольку в таблице «tourneys» нет записи для Лесли, а в таблице «dinners» нет записи для Бетти, эти записи отсутствуют в этих выходных данных.
Тем не менее, можно вернуть все записи из одной из таблиц, используя предложение outer JOIN . В MySQL предложения JOIN записываются как LEFT JOIN или RIGHT JOIN .
Предложение LEFT JOIN возвращает все записи из« левой »таблицы и только совпадающие записи из правой таблицы. В контексте внешних объединений левая таблица — это таблица, на которую ссылается условие FROM , а правая таблица — любая другая таблица, на которую ссылается после оператора JOIN .
Выполните предыдущий запрос еще раз, но на этот раз используйте предложение LEFT JOIN :
SELECT tourneys.name, tourneys.size, dinners.birthdate FROM tourneys LEFT JOIN dinners ON tourneys.name=dinners.name;Эта команда будет возвращать каждую запись из левой таблицы (в данном случае, «турниры»), даже если в правой таблице нет соответствующей записи. Каждый раз, когда в правой таблице нет подходящей записи, она возвращается как NULL или просто пустое значение, в зависимости от вашей RDBMS:
+---------+------+------------+ | name | size | birthdate | +---------+------+------------+ | Dolly | 8.5 | 1946-01-19 | | Etta | 9 | 1938-01-25 | | Irma | 7 | 1941-02-18 | | Barbara | 7.5 | 1948-12-25 | | Gladys | 8 | 1944-05-28 | | Bettye | 9 | NULL | +---------+------+------------+ 6 rows in set (0.00 sec)Теперь выполните запрос еще раз, на этот раз с предложением RIGHT JOIN :
SELECT tourneys.name, tourneys.size, dinners.birthdate FROM tourneys RIGHT JOIN dinners ON tourneys.name=dinners.name;Это вернет все записи из правой таблицы ( dinners ). Поскольку дата рождения Лесли записана в правой таблице, но для нее нет соответствующей строки в левой таблице, столбцы name и size вернутся как значения NULL в этой строке:
+---------+------+------------+ | name | size | birthdate | +---------+------+------------+ | Dolly | 8.5 | 1946-01-19 | | Etta | 9 | 1938-01-25 | | Irma | 7 | 1941-02-18 | | Barbara | 7.5 | 1948-12-25 | | Gladys | 8 | 1944-05-28 | | NULL | NULL | 1946-05-02 | +---------+------+------------+ 6 rows in set (0.00 sec)Обратите внимание, что левые и правые объединения могут быть записаны как LEFT OUTER JOIN или RIGHT OUTER JOIN , хотя подразумевается часть «OUTER» в предложении. Аналогично, указание INNER JOIN даст тот же результат, что и простое написание JOIN .
В качестве альтернативы использованию JOIN для запроса записей из нескольких таблиц, вы можете использовать предложение UNION .
Оператор UNION работает немного иначе, чем предложение JOIN : вместо того, чтобы печатать результаты из нескольких таблиц в виде уникальных столбцов с использованием одного оператора SELECT , UNION объединяет результаты двух операторов SELECT в один столбец.
Чтобы проиллюстрировать, запустите следующий запрос:
SELECT name FROM tourneys UNION SELECT name FROM dinners;Этот запрос удалит все повторяющиеся записи, что является поведением по умолчанию оператора UNION :
+---------+ | name | +---------+ | Dolly | | Etta | | Irma | | Barbara | | Gladys | | Bettye | | Lesley | +---------+ 7 rows in set (0.00 sec)Чтобы вернуть все записи (включая дубликаты), используйте оператор UNION ALL :
SELECT name FROM tourneys UNION ALL SELECT name FROM dinners; +---------+ | name | +---------+ | Dolly | | Etta | | Irma | | Barbara | | Gladys | | Bettye | | Dolly | | Etta | | Irma | | Barbara | | Gladys | | Lesley | +---------+ 12 rows in set (0.00 sec)Имена и количество столбцов в таблице результатов отражают имя и количество столбцов, запрошенных первым оператором SELECT. Обратите внимание, что при использовании UNION для запроса нескольких столбцов из более чем одной таблицы каждый оператор SELECT должен запрашивать одинаковое количество столбцов, соответствующие столбцы должны иметь одинаковые типы данных, а столбцы в каждом операторе SELECT должны быть в том же порядке. В следующем примере показано, что может произойти, если вы используете предложение UNION для двух операторов SELECT , которые запрашивают разное количество столбцов:
SELECT name FROM dinners UNION SELECT name, wins FROM tourneys; ERROR 1222 (21000): The used SELECT statements have a different number of columnsДругой способ запроса нескольких таблиц — использование subqueries. Подзапросы (также известные как inner или nested query) — это запросы, заключенные в другой запрос. Это полезно в тех случаях, когда вы пытаетесь отфильтровать результаты запроса по сравнению с результатами отдельной агрегатной функции.
Чтобы проиллюстрировать эту идею, скажем, вы хотите знать, кто из ваших друзей выиграл больше матчей, чем Барбара. Вместо того, чтобы узнать, сколько матчей выиграла Барбара, а затем выполнить другой запрос, чтобы узнать, кто выиграл больше игр, вы можете рассчитать обе с помощью одного запроса:
SELECT name, wins FROM tourneys WHERE wins > ( SELECT wins FROM tourneys WHERE name = 'Barbara' ); +--------+------+ | name | wins | +--------+------+ | Dolly | 7 | | Etta | 4 | | Irma | 9 | | Gladys | 13 | +--------+------+ 4 rows in set (0.00 sec)Подзапрос в этом операторе был выполнен только один раз; нужно было только найти значение из столбца wins в той же строке, что и Barbara в столбце name , а данные, возвращаемые подзапросом и внешним запросом, не зависят друг от друга. Однако существуют случаи, когда внешний запрос должен сначала прочитать каждую строку в таблице и сравнить эти значения с данными, возвращенными подзапросом, чтобы получить требуемые данные. В этом случае подзапрос называется коррелированным подзапросом.
Следующее утверждение является примером коррелированного подзапроса. Этот запрос пытается выяснить, кто из ваших друзей выиграл больше игр, чем в среднем для тех, у кого одинаковый размер обуви:
SELECT name, size FROM tourneys AS t WHERE wins > ( SELECT AVG(wins) FROM tourneys WHERE size = t.size );Чтобы запрос завершился, он должен сначала собрать столбцы name и size из внешнего запроса. Затем он сравнивает каждую строку из этого набора результатов с результатами внутреннего запроса, который определяет среднее количество побед для людей с одинаковыми размерами обуви. Поскольку у вас есть только два друга с одинаковым размером обуви, в наборе результатов может быть только одна строка:
+------+------+ | name | size | +------+------+ | Etta | 9 | +------+------+ 1 row in set (0.00 sec)Как упоминалось ранее, подзапросы могут использоваться для запроса результатов из нескольких таблиц. Чтобы проиллюстрировать это одним последним примером, скажем, вы хотели устроить неожиданный ужин для лучшего боулера группы. Вы можете узнать, кто из ваших друзей имеет лучший рекорд в боулинге, и вернуть любимое блюдо по следующему запросу:
SELECT name, entree, side, dessert FROM dinners WHERE name = (SELECT name FROM tourneys WHERE wins = (SELECT MAX(wins) FROM tourneys)); +--------+--------+-------+-----------+ | name | entree | side | dessert | +--------+--------+-------+-----------+ | Gladys | steak | fries | ice cream | +--------+--------+-------+-----------+ 1 row in set (0.00 sec)Обратите внимание, что этот оператор не только включает подзапрос, но также содержит подзапрос в этом подзапросе.
Заключение
Выдача запросов является одной из наиболее часто выполняемых задач в области управления базами данных. Существует ряд инструментов администрирования баз данных, таких как phpMyAdmin или pgAdmin, которые позволяют выполнять запросы и визуализировать результаты, но с помощью операторов SELECT это все еще широко распространенный рабочий процесс, который также может предоставить вам больший контроль.
Рекомендуемые статьи:
- Оптимальные настройки mysql для Битрикс
- PhpMyAdmin — как создать пользователя и базу данных
- Резервное копирование Mysql
- Перенос пользователей в MySQL между серверами
- Как сбросить пароль MariaDB 10 на сервере с Debian
База данных · Построитель запросов
Построитель запросов к базе данных Laravel предлагает удобный и гибкий интерфейс для создания и выполнения запросов к базе данных. Его можно использовать для выполнения большинства операций с базой данных в вашем приложении и он отлично работает со всеми поддерживаемыми Laravel системами баз данных.
Построитель запросов Laravel использует связывание параметров PDO для защиты приложения от SQL-инъекций. Нет необходимости чистить строки, передаваемые как связываемые параметры.
PDO не поддерживает связывание имен столбцов. Поэтому, вы никогда не должны использовать какие-либо входящие от пользователя данные в качестве имен столбцов, используемые вашими запросами, включая столбцы в запросах order by и т.д.
Выполнение запросов к базе данных
Получение всех строк из таблицы
Вы можете использовать метод table фасада DB , чтобы начать запрос. Метод table возвращает текущий экземпляр построителя запросов для данной таблицы, позволяя вам связать больше ограничений к запросу и, наконец, получить результаты, используя метод get :
namespace App\Http\Controllers; use App\Http\Controllers\Controller; use Illuminate\Support\Facades\DB; class UserController extends Controller < /** * Показать список всех пользователей приложения. * * @return \Illuminate\Http\Response */ public function index() < $users = DB::table('users')->get(); return view('user.index', ['users' => $users]); > >Метод get возвращает экземпляр Illuminate\Support\Collection , содержащий результаты запроса, где каждый результат является экземпляром объекта stdClass PHP. Вы можете получить доступ к значению каждого столбца, обратившись к столбцу как к свойству объекта:
use Illuminate\Support\Facades\DB; $users = DB::table('users')->get(); foreach ($users as $user) < echo $user->name; >Коллекции Laravel содержат множество чрезвычайно мощных методов для работы с наборами данных. Для получения дополнительной информации о коллекциях Laravel ознакомьтесь с их документацией.
Получение одной строки / столбца из таблицы
Если вам просто нужно получить одну строку из таблицы базы данных, вы можете использовать метод first фасада DB . Этот метод вернет единственный объект stdClass :
$user = DB::table('users')->where('name', 'John')->first(); return $user->email;Если вам не нужна вся строка, вы можете извлечь одно значение из записи с помощью метода value . Этот метод вернет значение столбца напрямую:
$email = DB::table('users')->where('name', 'John')->value('email');Чтобы получить одну строку по значению столбца id , используйте метод find :
$user = DB::table('users')->find(3);Получение списка значений столбца
Если вы хотите получить экземпляр Illuminate\Support\Collection , содержащий значения одного столбца, вы можете использовать метод pluck . В этом примере мы получим коллекцию из названий пользователей:
use Illuminate\Support\Facades\DB; $titles = DB::table('users')->pluck('title'); foreach ($titles as $title) < echo $title; >Вы можете указать столбец, который результирующая коллекция должна использовать в качестве ключей, указав второй аргумент методу pluck :
$titles = DB::table('users')->pluck('title', 'name'); foreach ($titles as $name => $title) < echo $title; >Разбиение результатов
Если вам нужно работать с тысячами записей базы данных, рассмотрите возможность использования метода chunk фасада DB . Этот метод извлекает за раз небольшой фрагмент результатов и передает каждый фрагмент в функцию-аргумент для обработки. Например, давайте извлечем всю таблицу users фрагментами по 100 записей за раз:
use Illuminate\Support\Facades\DB; DB::table('users')->orderBy('id')->chunk(100, function ($users) < foreach ($users as $user) < // > >);Вы можете остановить обработку последующих фрагментов, вернув из функции обработки false :
DB::table('users')->orderBy('id')->chunk(100, function ($users) < // Обрабатываем записи . return false; >);Если вы обновляете записи базы данных во время фрагментирования результатов, то результаты ваших фрагментов могут измениться неожиданным образом. Если вы планируете обновлять полученные записи при фрагментировании, всегда лучше использовать вместо этого метод chunkById . Этот метод автоматически разбивает результаты на фрагменты на основе первичного ключа записи:
DB::table('users')->where('active', false) ->chunkById(100, function ($users) < foreach ($users as $user) < DB::table('users') ->where('id', $user->id) ->update(['active' => true]); > >);При обновлении или удалении записей внутри функции-аргумента, любые изменения первичного или внешних ключей могут повлиять на запрос очередного фрагмента. Это может потенциально привести к тому, что записи могут не быть включены в последующие результаты выполнения функции.
Отложенная потоковая передача результатов
Метод lazy работает аналогично методу chunk в том смысле, что он выполняет запрос по частям. Однако вместо передачи каждого фрагмента непосредственно в функцию-обработчик, метод lazy() возвращает экземпляр LazyCollection , что позволяет вам взаимодействовать с результатами как с единым потоком:
use Illuminate\Support\Facades\DB; DB::table('users')->lazy()->each(function ($user) < // >);Еще раз, если вы планируете обновлять полученные записи во время их итерации, лучше вместо этого использовать методы lazyById или lazyByIdDesc . Эти методы автоматически разбивают результаты «постранично» на основе первичного ключа записи:
DB::table('users')->where('active', false) ->lazyById()->each(function ($user) < DB::table('users') ->where('id', $user->id) ->update(['active' => true]); >);При обновлении или удалении записей во время их итерации любые изменения первичного ключа или внешних ключей могут повлиять на запрос фрагмента. Это может потенциально привести к тому, что записи не будут включены в результирующий набор.
Агрегатные функции
Построитель запросов также содержит множество методов для получения агрегированных значений, таких как count , max , min , avg , и sum . После создания запроса вы можете вызвать любой из этих методов:
use Illuminate\Support\Facades\DB; $users = DB::table('users')->count(); $price = DB::table('orders')->max('price');Конечно, вы можете комбинировать эти методы с другими выражениями, чтобы уточнить способ вычисления вашего совокупного значения:
$price = DB::table('orders') ->where('finalized', 1) ->avg('price');Определение наличия записей
Вместо использования метода count для определения существования каких-либо записей, соответствующих ограничениям вашего запроса, используйте методы exists и doesntExist :
if (DB::table('orders')->where('finalized', 1)->exists()) < // . > if (DB::table('orders')->where('finalized', 1)->doesntExist()) < // . >Выражения Select
Уточнения выражения Select
Возможно, вам не всегда нужно выбирать все столбцы из таблицы базы данных. Используя метод select , вы можете указать собственное выражение SELECT для запроса:
use Illuminate\Support\Facades\DB; $users = DB::table('users') ->select('name', 'email as user_email') ->get();Метод distinct позволяет вам заставить запрос возвращать уникальные результаты:
$users = DB::table('users')->distinct()->get();Если у вас уже есть экземпляр построителя запросов, и вы хотите добавить столбец к существующему выражению SELECT , то вы можете использовать метод addSelect :
$query = DB::table('users')->select('name'); $users = $query->addSelect('age')->get();Сырые SQL-выражения
Иногда вам может понадобиться вставить в запрос произвольную строку, содержащую часть SQL-запроса. Для этого вы можете использовать метод raw фасада DB :
$users = DB::table('users') ->select(DB::raw('count(*) as user_count, status')) ->where('status', '<>', 1) ->groupBy('status') ->get();Сырые выражения будут вставлены в запрос в виде строк, поэтому следует проявлять особую осторожность, чтобы не создавать уязвимости для SQL-инъекций.
Сырые sql-выражения
Вместо использования метода DB::raw , вы также можете использовать следующие методы для вставки произвольного SQL-выражения в различные части вашего запроса. Помните, Laravel не может гарантировать, что любой запрос, использующий сырые SQL-выражения, защищен от уязвимостей SQL-инъекций.
selectRaw
Метод selectRaw можно использовать вместо addSelect(DB::raw(. )) . Этот метод принимает необязательный массив параметров для подстановки в качестве второго аргумента:
$orders = DB::table('orders') ->selectRaw('price * ? as price_with_tax', [1.0825]) ->get();whereRaw / orWhereRaw
Методы whereRaw и orWhereRaw можно использовать для вставки сырого SQL-выражения WHERE в ваш запрос. Эти методы принимают необязательный массив параметров в качестве второго аргумента:
$orders = DB::table('orders') ->whereRaw('price > IF(state = "TX", ?, 100)', [200]) ->get();havingRaw / orHavingRaw
Методы havingRaw и orHavingRaw могут использоваться для вставки необработанной строки в качестве значения выражения HAVING . Эти методы принимают необязательный массив параметров в качестве второго аргумента:
$orders = DB::table('orders') ->select('department', DB::raw('SUM(price) as total_sales')) ->groupBy('department') ->havingRaw('SUM(price) > ?', [2500]) ->get();orderByRaw
Метод orderByRaw используется для предоставления необработанной строки в качестве значения выражения ORDER BY :
$orders = DB::table('orders') ->orderByRaw('updated_at - created_at DESC') ->get();groupByRaw
Метод groupByRaw используется для предоставления необработанной строки в качестве значения выражения GROUP BY :
$orders = DB::table('orders') ->select('city', 'state') ->groupByRaw('city, state') ->get();Соединения Joins
Inner Join
Построитель запросов также может использоваться для добавления выражений соединения (join) к вашим запросам. Чтобы выполнить базовое «внутреннее соединение» (inner join), вы можете использовать метод join . Первым аргументом, передаваемым методу join , является имя таблицы, к которой вам нужно присоединиться, а остальные аргументы определяют ограничения столбца для соединения. Вы даже можете соединить несколько таблиц в один запрос:
use Illuminate\Support\Facades\DB; $users = DB::table('users') ->join('contacts', 'users.id', '=', 'contacts.user_id') ->join('orders', 'users.id', '=', 'orders.user_id') ->select('users.*', 'contacts.phone', 'orders.price') ->get();Left Join / Right Join
Если вы хотите выполнить «левое соединение» или «правое соединение» вместо «внутреннего соединения», используйте методы leftJoin или rightJoin . Эти методы имеют ту же сигнатуру, что и метод join :
$users = DB::table('users') ->leftJoin('posts', 'users.id', '=', 'posts.user_id') ->get(); $users = DB::table('users') ->rightJoin('posts', 'users.id', '=', 'posts.user_id') ->get();Cross Join
Вы можете использовать метод crossJoin для выполнения «перекрестного соединения». Перекрестные соединения генерируют декартово произведение между первой таблицей и соединяемой таблицей:
$sizes = DB::table('sizes') ->crossJoin('colors') ->get();Расширенные выражения соединения
Вы также можете указать более сложные выражения соединения. Для начала передайте функцию в качестве второго аргумента методу join . Функция получит экземпляр Illuminate\Database\Query\JoinClause , который позволяет вам указать ограничения JOIN :
DB::table('users') ->join('contacts', function ($join) < $join->on('users.id', '=', 'contacts.user_id')->orOn(. ); >) ->get();Если вы хотите использовать выражение WHERE в своих соединениях, вы можете использовать методы where и orWhere экземпляра JoinClause . Вместо сравнения двух столбцов эти методы будут сравнивать столбец со значением:
DB::table('users') ->join('contacts', function ($join) < $join->on('users.id', '=', 'contacts.user_id') ->where('contacts.user_id', '>', 5); >) ->get();Подзапросы соединений
Вы можете использовать методы joinSub , leftJoinSub , и rightJoinSub , чтобы присоединить запрос к подзапросу. Каждый из этих методов получает три аргумента: подзапрос, псевдоним таблицы и функцию, определяющую связанные столбцы. В этом примере мы получим коллекцию пользователей, где каждая запись пользователя также содержит временную метку created_at последнего опубликованного поста пользователя в блоге:
$latestPosts = DB::table('posts') ->select('user_id', DB::raw('MAX(created_at) as last_post_created_at')) ->where('is_published', true) ->groupBy('user_id'); $users = DB::table('users') ->joinSub($latestPosts, 'latest_posts', function ($join) < $join->on('users.id', '=', 'latest_posts.user_id'); >)->get();Объединения результатов Unions
Построитель запросов также содержит удобный метод «объединения» двух или более запросов вместе. Например, вы можете создать первый запрос и использовать метод union для объединения его с другими запросами:
use Illuminate\Support\Facades\DB; $first = DB::table('users') ->whereNull('first_name'); $users = DB::table('users') ->whereNull('last_name') ->union($first) ->get();В дополнение к методу union , построитель запросов содержит метод unionAll . Запросы, объединенные с использованием метода unionAll , не будут удалять повторяющиеся результаты. Метод unionAll имеет ту же сигнатуру, что и метод union .
Основные выражения Where
Выражения Where
Вы можете использовать метод where построителя запросов, чтобы добавить в запрос выражения WHERE . Самый простой вызов метода where требует трех аргументов. Первый аргумент – это имя столбца. Второй аргумент – это оператор, который может быть любым из поддерживаемых базой данных операторов. Третий аргумент – это значение, которое нужно сравнить со значением столбца.
Например, следующий запрос извлекает пользователей, у которых значение столбца votes равно 100 , а значение столбца age больше, чем 35 :
$users = DB::table('users') ->where('votes', '=', 100) ->where('age', '>', 35) ->get();Для удобства, если вы хотите убедиться, что столбец соответствует = переданному значению, то вы можете передать это значение в качестве второго аргумента в метод where . Laravel будет предполагать, что вы хотите использовать оператор = :
$users = DB::table('users')->where('votes', 100)->get();Как упоминалось ранее, вы можете использовать любой оператор, который поддерживается вашей системой баз данных:
$users = DB::table('users') ->where('votes', '>=', 100) ->get(); $users = DB::table('users') ->where('votes', '<>', 100) ->get(); $users = DB::table('users') ->where('name', 'like', 'T%') ->get();Вы также можете передать массив условий методу where . Каждый элемент массива должен быть массивом, содержащим три аргумента, как и обычно передаваемых методу where :
$users = DB::table('users')->where([ ['status', '=', '1'], ['subscribed', '<>', '1'], ])->get();PDO не поддерживает привязку имен столбцов. Поэтому вы никогда не должны брать из пользовательского ввода имена столбцов для совершения запросов, включая столбцы «order by».
Выражения Or Where
При объединении в цепочку вызовов метода where построителя запросов выражения WHERE будут объединены вместе с помощью оператора AND . Однако, вы можете использовать метод orWhere для добавления выражения к запросу с помощью оператора OR . Метод orWhere принимает те же аргументы, что и метод where :
$users = DB::table('users') ->where('votes', '>', 100) ->orWhere('name', 'John') ->get();Если вам нужно сгруппировать условие OR в круглых скобках, вы можете передать функцию в качестве первого аргумента методу orWhere :
$users = DB::table('users') ->where('votes', '>', 100) ->orWhere(function($query) < $query->where('name', 'Abigail') ->where('votes', '>', 50); >) ->get();В приведенном выше примере будет получен следующий SQL:
select * from users where votes > 100 or (name = 'Abigail' and votes > 50)Вы всегда должны группировать вызовы orWhere , чтобы избежать неожиданного поведения при применении глобальных диапазонов.
Выражения Where и JSON
Laravel также поддерживает запросы в базах данных, которые обеспечивают поддержку JSON-типов столбцов. В настоящее время это MySQL 5.7+, PostgreSQL, SQL Server 2016 и SQLite 3.9.0 (с расширением JSON1). Чтобы запросить столбец JSON, используйте оператор -> :
$users = DB::table('users') ->where('preferences->dining->meal', 'salad') ->get();Вы можете использовать whereJsonContains для запроса массивов JSON. Эта функция не поддерживается базой данных SQLite:
$users = DB::table('users') ->whereJsonContains('options->languages', 'en') ->get();Если ваше приложение использует базы данных MySQL или PostgreSQL, вы можете передать массив значений методу whereJsonContains :
$users = DB::table('users') ->whereJsonContains('options->languages', ['en', 'de']) ->get();Вы можете использовать метод whereJsonLength для запроса массивов JSON по их длине:
$users = DB::table('users') ->whereJsonLength('options->languages', 0) ->get(); $users = DB::table('users') ->whereJsonLength('options->languages', '>', 1) ->get();Дополнительные выражения Where
whereBetween / orWhereBetween
Метод whereBetween проверяет, находится ли значение столбца между двумя значениями:
$users = DB::table('users') ->whereBetween('votes', [1, 100]) ->get();whereNotBetween / orWhereNotBetween
Метод whereNotBetween проверяет, что значение столбца находится вне двух значений:
$users = DB::table('users') ->whereNotBetween('votes', [1, 100]) ->get();whereIn / whereNotIn / orWhereIn / orWhereNotIn
Метод whereIn проверяет, что значение переданного столбца содержится в указанном массиве:
$users = DB::table('users') ->whereIn('id', [1, 2, 3]) ->get();Метод whereNotIn проверяет, что значение переданного столбца не содержится в указанном массиве:
$users = DB::table('users') ->whereNotIn('id', [1, 2, 3]) ->get();Если вы добавляете в свой запрос большой массив связываемых целочисленных параметров, то методы whereIntegerInRaw или whereIntegerNotInRaw могут использоваться для значительного сокращения потребляемой памяти.
whereNull / whereNotNull / orWhereNull / orWhereNotNull
Метод whereNull проверяет, что значение переданного столбца равно NULL :
$users = DB::table('users') ->whereNull('updated_at') ->get();Метод whereNotNull проверяет, что значение переданного столбца не равно NULL :
$users = DB::table('users') ->whereNotNull('updated_at') ->get();whereDate / whereMonth / whereDay / whereYear / whereTime
Метод whereDate используется для сравнения значения столбца с датой:
$users = DB::table('users') ->whereDate('created_at', '2016-12-31') ->get();Метод whereMonth используется для сравнения значения столбца с конкретным месяцем:
$users = DB::table('users') ->whereMonth('created_at', '12') ->get();Метод whereDay используется для сравнения значения столбца с определенным днем месяца:
$users = DB::table('users') ->whereDay('created_at', '31') ->get();Метод whereYear используется для сравнения значения столбца с конкретным годом:
$users = DB::table('users') ->whereYear('created_at', '2016') ->get();Метод whereTime используется для сравнения значения столбца с определенным временем:
$users = DB::table('users') ->whereTime('created_at', '=', '11:20:45') ->get();whereColumn / orWhereColumn
Метод whereColumn используется для проверки равенства двух столбцов:
$users = DB::table('users') ->whereColumn('first_name', 'last_name') ->get();Вы также можете передать оператор сравнения методу whereColumn :
$users = DB::table('users') ->whereColumn('updated_at', '>', 'created_at') ->get();Вы также можете передать массив сравнений столбцов методу whereColumn . Эти условия будут объединены с помощью оператора AND :
$users = DB::table('users') ->whereColumn([ ['first_name', '=', 'last_name'], ['updated_at', '>', 'created_at'], ])->get();Логическая группировка
Иногда требуется сгруппировать несколько выражений WHERE в круглых скобках, чтобы добиться желаемой логической группировки вашего запроса. Фактически, вы должны всегда группировать вызовы метода orWhere в круглых скобках, чтобы избежать неожиданного поведения запроса. Для этого вы можете передать функцию методу where :
$users = DB::table('users') ->where('name', '=', 'John') ->where(function ($query) < $query->where('votes', '>', 100) ->orWhere('title', '=', 'Admin'); >) ->get();Как вы можете видеть, передача функции в метод where инструктирует построитель запросов начать группу ограничений. Функция получит экземпляр построителя запросов, который вы можете использовать для задания ограничений, которые должны содержаться в группе скобок. В приведенном выше примере будет получен следующий SQL:
select * from users where name = 'John' and (votes > 100 or title = 'Admin')Вы всегда должны группировать вызовы orWhere , чтобы избежать неожиданного поведения при применении глобальных диапазонов.
Расширенные выражения Where
Выражения Where Exists
Метод whereExists позволяет писать выражения WHERE EXISTS SQL. Метод whereExists принимает функцию, которая получит экземпляр построителя запросов, позволяя вам определить запрос, который должен быть помещен внутри выражения EXISTS :
$users = DB::table('users') ->whereExists(function ($query) < $query->select(DB::raw(1)) ->from('orders') ->whereColumn('orders.user_id', 'users.id'); >) ->get();В приведенном выше примере будет получен следующий SQL:
select * from users where exists ( select 1 from orders where orders.user_id = users.id )Подзапросы выражений Where
Иногда требуется создать выражение WHERE , которое сравнивает результаты подзапроса с переданным значением. Вы можете добиться этого, передав функцию и значение методу where . Например, следующий запрос будет извлекать всех пользователей, недавно имевших «членство» указанного типа:
use App\Models\User; $users = User::where(function ($query) < $query->select('type') ->from('membership') ->whereColumn('membership.user_id', 'users.id') ->orderByDesc('membership.start_date') ->limit(1); >, 'Pro')->get();Или вам может потребоваться создать выражение «where», которое сравнивает столбец с результатами подзапроса. Вы можете сделать это, передав методу where столбец, оператор и функцию. Например, следующий запрос будет извлекать все записи о доходах, где сумма меньше средней:
use App\Models\Income; $incomes = Income::where('amount', ', function ($query) < $query->selectRaw('avg(i.amount)')->from('incomes as i'); >)->get();Сортировка, группировка, ограничение и смещение
Сортировка
Метод orderBy
Метод orderBy позволяет вам сортировать результаты запроса по конкретному столбцу. Первый аргумент, принимаемый методом orderBy , должен быть столбцом, по которому вы хотите выполнить сортировку, а второй аргумент определяет направление сортировки и может быть либо asc , либо desc :
$users = DB::table('users') ->orderBy('name', 'desc') ->get();Для сортировки по нескольким столбцам вы можете просто вызывать orderBy столько раз, сколько необходимо:
$users = DB::table('users') ->orderBy('name', 'desc') ->orderBy('email', 'asc') ->get();Методы latest и oldest
Методы latest и oldest позволяют легко упорядочивать результаты по дате. По умолчанию результат будет упорядочен по столбцу created_at таблицы. Или вы можете передать имя столбца, по которому хотите сортировать:
$user = DB::table('users') ->latest() ->first();Случайный порядок
Метод inRandomOrder используется для случайной сортировки результатов запроса. Например, вы можете использовать этот метод для выборки случайного пользователя:
$randomUser = DB::table('users') ->inRandomOrder() ->first();Удаление существующих сортировок
Метод reorder удаляет все выражения ORDER BY , которые ранее были применены к запросу:
$query = DB::table('users')->orderBy('name'); $unorderedUsers = $query->reorder()->get();Вы можете передать столбец и направление при вызове метода reorder , чтобы удалить все существующие выражения ORDER BY и применить к запросу совершенно новый порядок:
$query = DB::table('users')->orderBy('name'); $usersOrderedByEmail = $query->reorder('email', 'desc')->get();Группировка
Методы groupBy и having
Как и следовало ожидать, для группировки результатов запроса могут использоваться методы groupBy и having . Сигнатура метода having аналогична сигнатуре метода where :
$users = DB::table('users') ->groupBy('account_id') ->having('account_id', '>', 100) ->get();Вы можете использовать метод havingBetween для фильтрации результатов в заданном диапазоне:
$report = DB::table('orders') ->selectRaw('count(id) as number_of_orders, customer_id') ->groupBy('customer_id') ->havingBetween('number_of_orders', [5, 15]) ->get();Вы можете передать несколько аргументов методу groupBy для группировки по нескольким столбцам:
$users = DB::table('users') ->groupBy('first_name', 'status') ->having('account_id', '>', 100) ->get();Чтобы создать более сложные операторы having , см. метод havingRaw .
Ограничение и смещение
Методы skip и take
Вы можете использовать методы skip и take , чтобы ограничить количество результатов, возвращаемых запросом, или пропустить указанное количество результатов из запроса:
$users = DB::table('users')->skip(10)->take(5)->get();Как вариант, вы можете использовать методы limit и offset . Эти методы функционально эквивалентны методам take и skip соответственно:
$users = DB::table('users') ->offset(10) ->limit(5) ->get();Условные выражения
Иногда может потребоваться, чтобы определенные выражения запроса применялись к запросу на основании другого условия. Например, бывает необходимо применить оператор WHERE только в том случае, если переданное входящее значение присутствует в HTTP-запросе. Вы можете сделать это с помощью метода when :
$role = $request->input('role'); $users = DB::table('users') ->when($role, function ($query, $role) < return $query->where('role_id', $role); >) ->get();Метод when выполняет переданную функцию-аргумент только тогда, когда первый аргумент равен true . Если первый аргумент – false , функция не будет выполнена. Итак, в приведенном выше примере функция метода when будет вызываться только в том случае, если поле role присутствует во входящем запросе и оценивается как true .
Вы можете передать другую функцию в качестве третьего аргумента методу when . Это функция будет выполнена только в том случае, если первый аргумент оценивается как false . Чтобы проиллюстрировать этот функционал, определим порядок вывода записей по умолчанию для запроса:
$sortByVotes = $request->input('sort_by_votes'); $users = DB::table('users') ->when($sortByVotes, function ($query, $sortByVotes) < return $query->orderBy('votes'); >, function ($query) < return $query->orderBy('name'); >) ->get();Вставка
Построитель запросов также содержит метод insert , который можно использовать для вставки записей в таблицу базы данных. Метод insert принимает массив имен и значений столбцов:
DB::table('users')->insert([ 'email' => 'kayla@example.com', 'votes' => 0 ]);Вы можете вставить сразу несколько записей, передав массив массивов. Каждый из массивов представляет собой запись, которую нужно вставить в таблицу:
DB::table('users')->insert([ ['email' => 'picard@example.com', 'votes' => 0], ['email' => 'janeway@example.com', 'votes' => 0], ]);Метод insertOrIgnore будет игнорировать ошибки повторяющихся записей при вставке записей в базу данных:
DB::table('users')->insertOrIgnore([ ['id' => 1, 'email' => 'sisko@example.com'], ['id' => 2, 'email' => 'archer@example.com'], ]);insertOrIgnore будет игнорировать дубликаты записей, а также может игнорировать другие типы ошибок в зависимости от движка базы данных. Например, insertOrIgnore будет обходить строгий режим MySQL.
Автоинкрементирование идентификаторов
Если таблица имеет автоинкрементный идентификатор, то используйте метод insertGetId , чтобы вставить запись и затем получить идентификатор этой записи:
$id = DB::table('users')->insertGetId( ['email' => 'john@example.com', 'votes' => 0] );При использовании PostgreSQL метод insertGetId ожидает, что автоинкрементный столбец будет называться id . Если вы хотите получить идентификатор из другой «последовательности», вы можете передать имя столбца в качестве второго параметра методу insertGetId .
Обновления-вставки
Метод upsert вставляет записи, которые не существуют, и обновляет записи, которые уже существуют, новыми значениями, которые вы можете указать. Первый аргумент метода состоит из значений для вставки или обновления, а второй аргумент перечисляет столбцы, которые однозначно идентифицируют записи в связанной таблице. Третий и последний аргумент метода – это массив столбцов, который следует обновить, если соответствующая запись уже существует в базе данных:
DB::table('flights')->upsert([ ['departure' => 'Oakland', 'destination' => 'San Diego', 'price' => 99], ['departure' => 'Chicago', 'destination' => 'New York', 'price' => 150] ], ['departure', 'destination'], ['price']);В приведенном выше примере Laravel попытается вставить две записи. Если запись уже существует с такими же значениями столбцов departure и destination , то Laravel обновит столбец price этой записи.
Все базы данных, кроме SQL Server, требуют, чтобы столбцы во втором аргументе метода upsert имели «первичный» или «уникальный» индекс. Вдобавок, драйвер базы данных MySQL игнорирует второй аргумент метода upsert и всегда использует «первичный» и «уникальный» индексы таблицы для обнаружения существующих записей.
Обновление
Помимо вставки записей в базу данных, построитель запросов также может обновлять существующие записи с помощью метода update . Метод update , как и метод insert , принимает массив пар столбцов и значений, указывающих столбцы, которые нужно обновить. Вы можете ограничить запрос update с помощью выражений WHERE :
$affected = DB::table('users') ->where('id', 1) ->update(['votes' => 1]);Обновление или вставка
Иногда требуется обновить существующую запись в базе данных или создать ее, если соответствующей записи не существует. В этом сценарии может использоваться метод updateOrInsert . Метод updateOrInsert принимает два аргумента: массив условий, по которым нужно найти запись, и массив пар столбцов и значений, указывающих столбцы, которые нужно обновить.
Метод updateOrInsert попытается найти соответствующую запись в базе данных, используя пары столбец и значение первого аргумента. Если запись существует, она будет обновлена значениями второго аргумента. Если запись не может быть найдена, будет вставлена новая запись с объединенными атрибутами обоих аргументов:
DB::table('users') ->updateOrInsert( ['email' => 'john@example.com', 'name' => 'John'], ['votes' => '2'] );Обновление столбцов JSON
При обновлении столбца JSON вы должны использовать синтаксис -> для обновления соответствующего ключа в объекте JSON. Эта операция поддерживается в MySQL 5.7+ и PostgreSQL 9.5+:
$affected = DB::table('users') ->where('id', 1) ->update(['options->enabled' => true]);Увеличение и уменьшение отдельных значений
Конструктор запросов также содержит удобные методы увеличения или уменьшения значения конкретного столбца. Оба метода принимают по крайней мере один аргумент: столбец, который нужно изменить. Может быть указан второй аргумент, определяющий величину, на которую следует увеличить или уменьшить столбец:
DB::table('users')->increment('votes'); DB::table('users')->increment('votes', 5); DB::table('users')->decrement('votes'); DB::table('users')->decrement('votes', 5);Вы также можете указать дополнительные столбцы для обновления во время выполнения запроса:
DB::table('users')->increment('votes', 1, ['name' => 'John']);Удаление
Метод delete может использоваться для удаления записей из таблицы. Он возвращает количество затронутых строк. Вы можете ограничить операторы delete , добавив метод where перед вызовом метода delete :
$deleted = DB::table('users')->delete(); $deleted = DB::table('users')->where('votes', '>', 100)->delete();Если вы хотите очистить всю таблицу, что приведет к удалению всех записей из таблицы и сбросу автоинкрементного идентификатора на ноль, вы можете использовать метод truncate :
DB::table('users')->truncate();Очистка таблицы и PostgreSQL
При очистке базы данных PostgreSQL будет применено поведение CASCADE . Это означает, что все связанные с внешним ключом записи в других таблицах также будут удалены.
Пессимистическая блокировка
Построитель запросов также включает несколько функций, которые помогут вам достичь «пессимистической блокировки» при выполнении ваших операторов SELECT . Чтобы выполнить оператор с «совместной блокировкой», вы можете вызвать метод sharedLock в запросе. Совместная блокировка предотвращает изменение выбранных строк до тех пор, пока ваша транзакция не будет зафиксирована:
DB::table('users') ->where('votes', '>', 100) ->sharedLock() ->get();В качестве альтернативы вы можете использовать метод lockForUpdate . Блокировка «для обновления» предотвращает изменение выбранных записей или их выбор с помощью другой совместной блокировки:
DB::table('users') ->where('votes', '>', 100) ->lockForUpdate() ->get();Отладка
Вы можете использовать методы dd или dump при построении запроса, чтобы отобразить связанные параметры запроса и сам SQL-запрос. Метод dd отобразит отладочную информацию и затем прекратит выполнение запроса. Метод dump отобразит информацию об отладке, но позволит продолжить выполнение запроса:
DB::table('users')->where('votes', '>', 100)->dd(); DB::table('users')->where('votes', '>', 100)->dump();Русскоязычное комьюнити
- Группа в VK
- Телеграм LaravelRUS
- Телеграм Laravel для новичков
- Телеграм LaravelPro
Обучающие ресурсы
- Laracasts
- Codecourse
- Курс Дмитрия Елисеева
- Adam Wathan
Блоги разработчиков
- Laravel News
- Freek Van der Herten
- Brent Roose
- Marcel Pociot
Как сделать запрос в базе данных sql
Видеоурок по созданию MS SQL источника данных в дизайнере отчетов.
Запросы представляют собой текст скриптового вида, который служит для извлечения данных из таблиц и предоставления их в генератор отчетов. Особенность запросов состоит в том, что они получают данные из таблиц базы данных и создают на их основе временную таблицу. Данные во временной таблице будут отфильтрованы, сгруппированы, отсортированы и упорядочены, согласно параметрам запроса. Затем, временная таблица передается в генератор отчетов. Применение запросов предоставляет возможность избежать дублирования данных в таблицах и обеспечивает максимальную гибкость при поиске и отображении данных в базе данных. В основном запросы используются для выборки данных из базы данных и передачи их в генератор отчетов. Не все типы источников данных поддерживают SQL запросы. Если тип источника данных поддерживает SQL запросы, то в окне Новый источник данных (New Data Source) будет отображено поле Текст запроса (Query Text) , в котором указывается запрос.

Имя в источнике указывается в поле Наименование в источнике (Name in Source) . В данном поле можно ввести имя или можно нажать кнопку , для вызова списка имен.
В поле Наименование (Name) указывается имя источника данных, которое отображается в генераторе отчетов;
Псевдоним источника данных следует указывать в поле Псевдоним (Alias) ;
Команды управления запросами. На данной панели расположены основные элементы управления запросами. Для того, чтобы запустить запрос на выполнение следует нажать кнопку Выполнить (Run) .
Поле Текст запроса (Query Text) . В этом поле указывается текст запроса.
Меню выбора типа источника данных. Доступны следующие типы источника данных: Таблица и Хранимая процедура .
Параметр Время ожидания запроса (Query Timeout) предоставляет возможность определить время выполнения запроса, т.е. время в течение которого запрос будет пытаться выполниться. Если время ожидания запроса истекло, а запрос не выполнился, то пользователю будет отображено сообщение об истечении времени. Значение параметра указывается в секундах.
Команды управления данными. На этой панели представлены такие команды как создание новой колонки, новой рассчитываемой колонки, нового параметра. Также присутствует команда удаления выделенной колонки или параметра. Помимо всего прочего, на данной панели располагается команда Получить колонки данных .
Список колонок и параметров в текущем источнике данных.
Панель свойств выделенной колонки данных или параметра.
Конструктор запросов (Query Builder) представляет собой визуальный компонент, с помощью которого можно создавать запросы визуальными средствами. Создание запроса с помощью конструктора запросов, обеспечивает полное управление параметрами запроса и построение сложных условий отбора данных при помощи простейших визуальных действий пользователя.

Панель управления конструктором запросов. Представлена кнопками Сохранить (Save) , сохраняет запрос, и Закрыть (Close) , закрывает конструктор запросов;
На данной панели можно просмотреть дерево запросов.
Панель Конструкция запроса, на которой располагается визуальное представление запроса. В этой области можно определить исходную базу данных объектов и производные источники данных, а также определить связи между источниками данных, настроить свойства источника данных и ссылки.
Панель Базы данных. На этой панели отображается база данных и включенные в неё источники данных;
Панель таблица. На данной панели расположена таблица, в которой строки — это колонки данных, используемые в запросе, а столбцы — операции. В этой таблице можно определить колонки данных, псевдонимы, тип сортировки, порядок сортировки, группировку, критерии.
На данной панели отображается построенный, на панели , запрос в синтаксическом виде.
Также в конструкторе запросов есть вкладка Вид (View) , которая предоставляет возможность отобразить колонки данных, выбранные с помощью запроса. Также будут учтены операции в запросе. На рисунке снизу представлена вкладка Вид (View) конструктора запросов:

Для того, чтобы добавить построенный запрос в поле Текст запроса , следует в конструкторе запроса нажать кнопку Сохранить (Save) .
- Быстродействие, ресурсы сервера
