Безопасность систем баз данных. Учебное пособие
.pdfПрежде всего обращаем внимание на ключевую фразу
INNER JOIN между gruppirovka, prestupnik и prestuplenie, исполь-
зующую столбцы первичного ключа(идентификатора). Забудем на некоторое время об агрегации— выходные данные этого запроса представляют по одной строке на каждое преступление.
Выражение GROUP BY помещает строки в кластеры. В этом запросе нам нужно сгруппировать преступления каждого преступника по преступным группировкам. Таким образом, выражение GROUP BY использует для этого столбцы gruppirovka_id
и prestupnik_id. Можно использовать gruppirovka_id из трех таблиц.
Значения gruppirovka. gruppirovka_id, prestupnik. gruppirovka_id и prestuplenie.gruppirovka_id в данном случае равнознач-
ны. Выбор не играет роли, поскольку выражение INNER JOIN в любом случае сделает их одинаковыми. То же самое относится к prestupnik_id.
Как и в предыдущем примере запроса, используем функцию COUNT(), чтобы знать, сколько строк присутствует в каждой группе. Например, функция COUNT(*) указывает, что преступ-
ник «Andreev Sergey Ruslanovich» входящий в Солнцевскую
(Solncevskaya) преступную группировку совершил 2 преступления. Опять же, не играет роли, какой столбец или столбцы подсчитывается в запросе. Например, функция COUNT(*) выдает те же результаты, как и функцияCOUNT(gruppirovka. gruppirovka_id) или COUNT (gruppirovka_name).
В предыдущих главах рассматривались примеры использования функцию COUNT() для указания количества строк в группе. Приведем еще несколько функций, которые используются для определения свойств агрегированных строк.
AVG() — возвращает среднее значение всех строк в группе для указанного столбца.
MAX() — возвращает максимальное значение для строк в группе. С помощью данной функции можно, например, определить самый жаркий день месяца после группировки строк по месяцам.
MIN() — возвращает минимальное значение для строк в группе. Используя эту функцию, несложно найти самых юных студентов в классе, когда строки сгруппированы по классам.
91
STD() или STDDEV() — возвращает стандартное отклонение значений для строк в группе. Можно применять данную функцию, к примеру, когда нужно определить разброс между контрольными отсчетами, если строки сгруппированы, например, по курсам университета.
SUM() — возвращает сумму значений для строк в группе. Вместе с выражениемGROUP BY можно использовать и
другие редко используемые функции.
Выражение GROUP BY позволяет сортировать и создавать кластеры данных и определять количество средних, минимальных и максимальных значений. Теперь опишем выражение HAVING, используемое для добавления дополнительного контроля агрегации строк в операции GROUP BY.
Предположим, что необходимо выяснить, кто из преступников является рецидивистом. Определяем рецидивиста как преступника, совершившего больше одного преступления. Только один альбом соответствует нашему критерию. Запрос будет выглядеть следующим образом (рис. 7.14):
mysql> SELECT prestupnik_name, COUNT(*) FROM -> prestupnik INNER JOIN prestuplenie
-> USING (gruppirovka_id, prestupnik_id) -> GROUP BY prestupnik_name
-> HAVING COUNT(*) > 1;
Рис.7.14. Определение наибольшего количества преступлений
92
Выражение HAVING должно содержать функцию или столбец из инструкцииSELECT. В данном примере используем HAVING COUNT(*)>1, где функция COUNT(*) является частью инструкции SELECT. Обычно выражение HAVING использует такую агрегатную функцию, как COUNT(), SUM() или МАХ(). Если вы попытаетесь написать выражение HAVING с использованием столбца или функции, которых нет в инструкции SELECT, то, скорее всего, придется заменить его выражениемWHERE. Выражение HAVING предназначено лишь для формирования каждой группы или кластера, а не для выбора строк в выходных данных. Далее приведем пример, демонстрирующий, в каком случае не следует использовать выражение HAVING.
Допустим, что необходимо выяснить, сколько преступников входит в Солнцевскую(Solncevskaya) преступную группировку. Следующий запрос не рекомендуется использовать— это как раз и есть тот случай, когда не следует использовать выраже-
ние HAVING (рис. 7.15):
mysql> SELECT gruppirovka_name, -> COUNT(gruppirovka_name) FROM
-> gruppirovka INNER JOIN prestupnik -> USING (gruppirovka_id)
-> GROUP BY gruppirovka_name
-> HAVING gruppirovka_name=’Solncevskaya’;
Рис. 7.15. Использование выражения HAVING
Запрос выдает ответ, хотя и правильный, но выполняется очень медленно. Так не следует писать запросы, поскольку выражение HAVING используется для формирования групп из строк, а для фильтрации отображаемых результатов применяется некорректно. В этом запросе лучше использовать выражение
WHERE (рис. 7.16):
93
mysql> SELECT gruppirovka_name, -> COUNT(gruppirovka_name) FROM
-> gruppirovka INNER JOIN prestupnik -> USING (gruppirovka_id)
-> WHERE gruppirovka_name=’Solncevskaya’ -> GROUP BY gruppirovka_name;
Рис. 7.16. Использование выражения WHERE
Это корректный запрос. Он формирует группы и на основе выражения WHERE определяет, какие группы следует отобразить.
Контрольные вопросы
1.Назначение псевдонимов.
2.К каким структурам применяются псевдонимы?
3.Как дать псевдоним столбцу?
4.Назначение группировки строк.
5.С помощью какого выражения языкаSQL производится группировка строк?
6.Назначение функции COUNT.
7.С помощью какого выражения языкаSQL производится сортировка сгруппированных данных?
8.Какова очередность выполнения операцийWHERE, GROUP BY и HAVING?
94
8. ВЛОЖЕННЫЕ ЗАПРОСЫ
(ПК-33, ПК-35)
8.1. Простые вложенные запросы
Вложенные запросы поддерживаютсяMySQL, начиная с версии 4.1. Вложенные запросы обеспечивают мощный, удобный и лаконичный способ выражения комплексных данных, требуемых в коротких инструкциях SQL. Начнем с простых примеров, а затем перейдем к более сложным.
Принцип работы вложенных запросов
С помощью выражения INNER JOIN можно найти название преступной группировки, в которую входит конкретный преступник (рис.8.1):
mysql> SELECT gruppirovka_name FROM -> gruppirovka INNER JOIN prestupnik -> USING (gruppirovka_id)
-> WHERE prestupnik_name =
-> "Belov Aleksandr Petrovich";
Рис. 8.1. Поиск группировки по фамилии
Однако есть еще один способ с использованием вложенного запроса (рис. 8.2):
mysql> SELECT gruppirovka_name FROM gruppirovka
-> WHERE gruppirovka_id = (SELECT gruppirovka_id
-> FROM prestupnik WHERE prestupnik_name ="Belov
-> Aleksandr Petrovich");
95
Рис. 8.2. Использование вложенного запроса
Запрос называется вложенным в том случае, когда один запрос включает в себя другой. Внутренний запрос, или подзапрос, является вложенным и заключен в круглые скобки. Он определяет значение gruppirovka_id для преступника с именем «Belov Aleksandr Petrovich». Для внутренних запросов всегда следует использовать круглые скобки. Внешний запрос перечисляется первым и в данном случае не заключается в скобки. Он выполняет поиск значенияgruppirovka_name для преступной группировки со значением gruppirovka_id, соответствующим результату подзапроса. Таким образом, внутренний запрос выполняет поиск значения gruppirovka_id, а внешний — использует его для поиска названия преступной группировки.
Какой метод более предпочтителен? Этот вопрос следует проработать. С точки зрения производительности, вложенные запросы сложнее оптимизировать, и они практически всегда работают медленней, чем невложенные. Но в некоторых ситуациях они представляют собой единственный способ написанияот дельного запроса. Кроме того, иногда вложенные запросы могут выдавать информацию, которую сложно извлечь другими методами. А еще вложенные запросы более четко выражены. После усваивания основной идеи такие запросы легко понимаются. Многие проектировщики SQL считают, что вложенные запросы следует изучать перед альтернативами, связанными с объединениями.
Впредыдущем примере был использован оператор оценки
—знак равенства (=). Можно использовать в своих кодах все ти-
пы операторов сравнения: меньше чем (<), меньше или равно (<=), больше чем (>), больше или равно (>=), а также не равно (!=
или <>).
96
Использование ANY и IN.
Предположим, нужно выбрать преступников входящих в солнцевскую преступную группировку, которые старше преступников, входящих в состав матвеевской преступной группировки. Чтобы не усложнять запрос использованием объединение таблиц, будем использовать идентификатор преступной группировки. У солнцевской преступной группировки gruppirovka_id =1, а у матвеевской gruppirovka_id =4 Подобный запрос можно выразить так, как показано ниже (рис.8.3):
mysql> SELECT prestupnik_name, -> prestupnik_god_rozhdeniya
-> FROM prestupnik WHERE gruppirovka_id=1 -> AND prestupnik_god_rozhdeniya < ANY
-> (SELECT prestupnik_god_rozhdeniya -> FROM prestupnik
-> WHERE gruppirovka_id=4);
Рис. 8.3. Использование идентификатора преступной группировки
Подзапрос выполняет поиск года рождения преступников входящих в преступную группировку с идентификаторомравным 4 (матвеевская, рис. 8.4):
mysql> SELECT prestupnik_god_rozhdeniya -> FROM prestupnik
-> WHERE gruppirovka_id=4;
Рис. 8.4. Подзапрос поиска года рождения
97
Внешний запрос проверяет каждого преступника входящего в состав преступной группировки с идентификатором равным 1 (солнцевская) и возвращает данные о нем, если его год рождения меньше какого-нибудь значения из набора, возвращаемого подзапросом. Таким образом, например, выведен Andreev Sergey Ruslanovich, поскольку значение 1959 меньше наибольшего значения из набора (1969, 1977). Ключевое слово ANY имеет псевдоним SOME, введенный для удобочитаемости некоторых запросов в виде выражений на английском языке. Он выполняет те же операции и используется довольно редко.
Ключевое слово ANY хорошо подходит для выражения вложенных запросов. Предыдущий запрос является первым вложенным запросом с подзапросом столбца. Это означает, что результаты, возвращаемые подзапросом, представляют одно или несколько значений из столбца, а не отдельное скалярное значение, как в предыдущем разделе. Таким образом, можно сравнивать значение столбца из внешнего запроса с набором значений, возвращаемых подзапросом.
Рассмотрим еще один пример использования ANY. Предположим, что требуется выяснить, совершались ли одинаковые преступления членами солнцевской и матвеевской преступной группировки. Для этого можно использовать следующий вложенный запрос (рис. 8.5):
mysql> SELECT prestuplenie_name -> FROM prestuplenie INNER JOIN
-> gruppirovka USING(gruppirovka_id)
-> WHERE gruppirovka_name=’Solncevskaya’ -> AND prestuplenie_name = ANY
-> (SELECT prestuplenie_name
-> FROM prestuplenie INNER JOIN
-> gruppirovka USING(gruppirovka_id)
-> WHERE gruppirovka_name=’Matveevskaya’);
98
Рис. 8.5. Определение одинаковых преступлений
Оператор = ANY указывает внешнему запросу возвращать данные о названии преступлен, еслия значение prestuplenie_name соответствует любому названии. преступления, возвращаемого подзапросом. Ключевая фраза = ANY имеет псевдоним IN, который часто используется во вложенных запросах. Подобным образом, фразы <> ANY или != ANY имеют псевдоним NOT IN. Предыдущий пример можно переписать с применением ключевого слова IN (рис. 8.6):
mysql> SELECT prestuplenie_name -> FROM prestuplenie INNER JOIN
-> gruppirovka USING(gruppirovka_id)
-> WHERE gruppirovka_name=’Solncevskaya’ -> AND prestuplenie_name IN
-> (SELECT prestuplenie_name
-> FROM prestuplenie INNER JOIN
-> gruppirovka USING(gruppirovka_id)
-> WHERE gruppirovka_name=’Matveevskaya’);
Рис. 8.6. Применение ключевого слова IN
Рассмотрим пример с использованиемNOT IN. Выполним поиск всех преступлений совершенных преступниками, входящими в солнцевскую преступную группировку, которые не совершали преступники, входящие в матвеевскую преступную группировку (рис. 8.7):
99
mysql> SELECT prestuplenie_name -> FROM prestuplenie INNER JOIN
-> gruppirovka USING(gruppirovka_id)
-> WHERE gruppirovka_name=’Solncevskaya’ -> AND prestuplenie_name NOT IN
-> (SELECT prestuplenie_name
-> FROM prestuplenie INNER JOIN
-> gruppirovka USING(gruppirovka_id)
-> WHERE gruppirovka_name=’Matveevskaya’);
Рис. 8.7. Поиск всех преступников одной группировки
Использование ALL.
Перефразируем запрос выполненный ранее. Предположим, нужно найти преступников входящих в состав солнцевской преступной группировки, которые старше всех преступников входящих в состав матвеевской преступной группировки. Для этого вы можете использовать вместо ANY ключевое слово ALL (рис. 8.8):
mysql> SELECT prestupnik_name, -> prestupnik_god_rozhdeniya
-> FROM prestupnik INNER JOIN gruppirovka -> USING(gruppirovka_id)
-> WHERE gruppirovka_name=’Solncevskaya’ -> AND prestupnik_god_rozhdeniya<ALL
-> (SELECT prestupnik_god_rozhdeniya
-> FROM prestupnik INNER JOIN gruppirovka -> USING(gruppirovka_id)
-> WHERE gruppirovka_name=’Matveevskaya’);
100
