Урок 4. Сортировка результатов
В SQL часто кроме фильтрации данных, также часто требуется отсортировать их по одному из столцов.
В первом уроке мы написали SQL-запрос для получения данных о совершенолетних пользователях. Посмотрим на него еще раз:
SELECT last_name, first_name, birthday FROM users WHERE age >= 18
| id | last_name | first_name | birthday |
|---|---|---|---|
| 1 | Иванов | Дмитрий | 1996-12-11 |
| 3 | Шевченко | Тимур | 1998-04-27 |
| 4 | Иванова | Светлана | 1993-08-06 |
| 6 | Иванов | Алексей | 1993-08-05 |
| 7 | Процук | Алена | 1997-02-28 |
Обратите внимание, что сейчас данные никак не упорядочены. Ни по фамилии, ни по имени, ни по дате рождения. Но давайте добавим после конструции WHERE: ORDER BY last_name:.
SELECT last_name, first_name, birthday FROM users WHERE age >= 18 ORDER BY last_name
После выполнение данного SQL запроса мы получим таблицу отсортированную по столбцу last_name (фамилия). Так как last_name хранит строки, то сортировка происходит в алфавитном порядке:
| id | last_name | first_name | birthday |
|---|---|---|---|
| 1 | Иванов | Дмитрий | 1996-12-11 |
| 6 | Иванов | Алексей | 1993-08-05 |
| 4 | Иванова | Светлана | 1993-08-06 |
| 7 | Процук | Алена | 1997-02-28 |
| 3 | Шевченко | Тимур | 1998-04-27 |
Обратите внимание, что данные отсортированы только по фамилии, но не по другим полям. Однако давайте добавим через запятую еще один столбец, например, first_name:
SELECT last_name, first_name, birthday FROM users WHERE age >= 18 ORDER BY last_name, first_name
Теперь таблица отсортирована сразу по двум поля, сперва по фамилии, а уже затем по имени:
| id | last_name | first_name | birthday |
|---|---|---|---|
| 6 | Иванов | Алексей | 1993-08-05 |
| 1 | Иванов | Дмитрий | 1996-12-11 |
| 4 | Иванова | Светлана | 1993-08-06 |
| 7 | Процук | Алена | 1997-02-28 |
| 3 | Шевченко | Тимур | 1998-04-27 |
Разумеется, сортировать можно не только по текстовым полям. Например, можно написать ORDER BY birthday:
SELECT last_name, first_name, birthday FROM users WHERE age >= 18 ORDER BY birthday
И получить данные упорядоченные по дате рождения в хронологическом порядке:
| id | last_name | first_name | birthday |
|---|---|---|---|
| 6 | Иванов | Алексей | 1993-08-05 |
| 4 | Иванова | Светлана | 1993-08-06 |
| 1 | Иванов | Дмитрий | 1996-12-11 |
| 7 | Процук | Алена | 1997-02-28 |
| 3 | Шевченко | Тимур | 1998-04-27 |
А если в конструкции ORDER BY после имени столца написать DESC, то данные будут отсортированы в обратном порядке — свежие даты выше:
| id | last_name | first_name | birthday |
|---|---|---|---|
| 3 | Шевченко | Тимур | 1998-04-27 |
| 7 | Процук | Алена | 1997-02-28 |
| 1 | Иванов | Дмитрий | 1996-12-11 |
| 4 | Иванова | Светлана | 1993-08-06 |
| 6 | Иванов | Алексей | 1993-08-05 |
Более того при сортировке данных по нескольким столцам, мы можем для разных столцов указывать разные направления сортировки. Например ORDER BY last_name, birthday DESC:
SELECT last_name, first_name, birthday FROM users WHERE age >= 18 ORDER BY last_name, birthday DESC
Тогда данные будут сперва отсортированы в алфавитном порядке по фамилии, а уже внутри каждой фамилии по дате рождения в обратном порядке:
| id | last_name | first_name | birthday |
|---|---|---|---|
| 6 | Иванов | Алексей | 1993-08-05 |
| 1 | Иванов | Дмитрий | 1996-12-11 |
| 4 | Иванова | Светлана | 1993-08-06 |
| 7 | Процук | Алена | 1997-02-28 |
| 3 | Шевченко | Тимур | 1998-04-27 |
Следующий урок
Урок 5. Ограничение и смещение
В этом уроке вы научитесь ограничивать итоговые результаты с помощью конструкции LIMIT и делать смещение с помощью OFFSET
Полный курс с практикой
- 57 уроков
- 261 задание
- Сертификат
- Поддержка преподавателя
- Доступ к курсу навсегда
- Можно в рассрочку
Выбрать несколько записей из каждой группы.
Судя по сообщениям на форуме SQLinfo, выбор более одной строки из группы — часто встречающаяся задача (например, несколько популярных/новых товаров из каждой категории, последние новости для каждой рубрики, и т.д.), которая вызывает сложности при попытке решить её средствами SQL. В статье объясняется несколько способов как одним запросом получить N первых, последних или случайных строк из группы и дана оценка их эффективности с точки зрения производительности.
Разберем решения на примере таблицы сообщений, имеющей поля (post_id, user_id, date_added, post_text), в которой хранится id сообщения, id пользователя, дата добавления и текст поста. Предполагается, что комбинация (user_id, date_added) уникальна, т.е. пользователь не может разместить 2 сообщения в один момент времени.
Код создания и заполнения таблицы
CREATE TABLE `posts` (
`post_id` int ( 11 ) NOT NULL AUTO_INCREMENT ,
`user_id` int ( 11 ) NOT NULL ,
`date_added` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ,
`post_text` text NOT NULL ,
PRIMARY KEY ( `post_id` ) ,
UNIQUE KEY `user_id` ( `user_id`,`date_added` )
) ENGINE = InnoDB DEFAULT CHARSET =utf8;
INSERT INTO `posts` VALUES ( 1 , 1 , ‘2018-04-17 05:37:15’ , ‘Есть таблица товаров. Каждый товар относится к какой-то категории. Как вывести по 3 товара из каждой категории?’ ) ;
INSERT INTO `posts` VALUES ( 2 , 1 , ‘2018-04-17 05:43:26’ , ‘В идеале хочется получить последние N товаров из каждой категории одним запросом. Может есть какой-нибудь элегантный способ?’ ) ;
INSERT INTO `posts` VALUES ( 3 , 2 , ‘2018-04-17 11:13:22’ , ‘Выборка по N последних товаров каждой категории. Просто не хочется делать запрос на каждую категорию.’ ) ;
INSERT INTO `posts` VALUES ( 4 , 1 , ‘2018-04-17 15:38:04’ , ‘Как вывести по N строк из каждой группы? Например, последние новости для каждой рубрики или несколько популярных товаров из каждой категории’ ) ;
INSERT INTO `posts` VALUES ( 5 , 1 , ‘2018-04-17 15:52:18’ , ‘Помогите написать запрос. Выбор по несколько строк из каждой группы в запросе’ ) ;
INSERT INTO `posts` VALUES ( 6 , 4 , ‘2018-04-18 12:17:05’ , ‘MySQL не поддерживает LIMIT внутри IN подзапросов. Как написать запрос с лимитом строк внутри каждой группы?’ ) ;
INSERT INTO `posts` VALUES ( 7 , 4 , ‘2018-04-18 14:55:36’ , ‘К сожалению, в MySQL нет windows function. Используйте user variables для их эмуляции.’ ) ;
INSERT INTO `posts` VALUES ( 8 , 1 , ‘2018-04-23 02:17:14’ , ‘Помогите ускорить выборку случайных строк из группы.’ ) ;
INSERT INTO `posts` VALUES ( 9 , 4 , ‘2018-04-25 17:22:02’ , ‘TOP в подзапросе выдает ошибку.’ ) ;
INSERT INTO `posts` VALUES ( 10 , 4 , ‘2018-04-26 22:28:45’ , ‘Нужно выбрать из каждой группы по 2 последних элемента и 2 случайных.’ ) ;
Пусть нам требуется выбрать 3 последних сообщения каждого пользователя. Некоторые из предложенных ниже вариантов после незначительных изменений могут быть использованы для выборки 3 случайных сообщений каждого пользователя (о чем будут даны соответствующие пояснения).
1. зависимый подзапрос
Для каждой строки таблицы считаем количество строк с тем же идентификатором пользователя (user_id) и большей датой добавления (date_added). Если количество таких строк меньше 3, значит рассматриваемая строка и есть нужная нам строка, т.е. входит в группу трёх последних сообщений пользователя.
select t1.* from posts t1
where ( select count ( * ) from posts t2 where t1.user_id=t2.user_id and t2.date_added > t1.date_added ) < 3 ;
Эффективность запроса ухудшается по мере роста числа сообщений у пользователя. Нельзя ограничится рассмотрением только нескольких записей каждого пользователя, необходимо проверить все сообщения и для каждого из них подсчитать точное кол-во более поздних. Кроме того метод неприменим для выборки нескольких случайных строк из группы.
2. join + group by
Та же идея, что и в предыдущем случае, только реализована через самообъединение таблицы и группировку. Каждой строке сопоставляется набор строк с тем же user_id и большей или равной date_added, после группировки мы получаем для каждой строки (количество сообщений того же пользователя с большей датой добавления) + 1. Иными словами, если мы пронумеруем сообщения пользователя по убыванию date_added, то полученное число будет порядковым номером строки в этой нумерации.
select t1.* from
posts t1 join posts t2 on t1.user_id=t2.user_id and t2.date_added >= t1.date_added
group by t1.post_id
having count ( * )
Этот способ часто рекомендуют в интернете в качестве решения задачи (встречаются вариации с left join). Однако его производительность не самая оптимальная в сравнении с другими методами, рассмотренными в этой статье. Вероятно, причина популярности этого решения в том, что join многим интуитивно представляется более простым решением.
Обратите внимание: в режиме ONLY_FULL_GROUP_BY придется усложнять запрос: сначала выбрать нужные post_id, затем по ним дополнительным join извлечь остальные поля (подробнее см статью Группировка в MySQL). Простое перечисление всех полей в части group by в разы увеличивает время выполнения запроса.
- в старых версиях они действительно не применимы 🙂
- в новых их производительность будет существенно хуже по сравнению с иными доступными вариантами решений (см способы 4 и 6)
3. group_concat()
Для каждого пользователя с помощью group_concat() составляется список идентификаторов его сообщений, отсортированный по убыванию даты. Используя substring_index(), вырезаем первые 3 значения post_id, и по ним извлекается вся строка.
select t1.* from posts t1 join
( select user_id, substring_index ( group_concat ( post_id order by date_added desc ) , ‘,’ , 3 ) x from posts t2 group by user_id ) t
on t.user_id = t1.user_id and find_in_set ( t1.post_id,x ) ;
К сожалению, MySQL не умеет решать уравнения, поэтому для поиска по условию с find_in_set будет просканирована вся таблица сообщений. Есть обходной путь: используя строковые функции и union all, вырезать id сообщений из списка и объединить их в один столбец. Тогда оптимизатор сможет использовать их для поиска нужных строк в таблице сообщений, а не наоборот.
with cte as (
select substring_index ( group_concat ( post_id order by date_added desc ) , ‘,’ , 3 ) x from posts group by user_id
)
select posts.* from (
select substring_index ( x, ‘,’ , 1 ) post_id from cte
union all
select substring_index ( substring_index ( x, ‘,’ , 2 ) , ‘,’ , -1 ) from cte
union all
select substring_index ( x, ‘,’ , -1 ) from cte ) t
join posts on t.post_id = posts.post_id;
- сколько строк из группы нужно выбрать
- есть ли возможность использовать with (доступны с MariaDB 10.2 / MySQL 8). Если в явном виде дублировать from-подзапрос, то каждый из них будет материализован в отдельную временную таблицу.
Этот способ можно применять для выборки 3 случайных сообщений каждого пользователя. Для этого достаточно указать иной вид сортировки внутри group_concat: order by rand ( ) вместо order by date_added desc .
4. оконные функции
Начиная с MariaDB 10.2 / MySQL 8 добавлена поддержка оконных функций. С помощью row_number() можно для каждого пользователя сделать отдельную нумерацию сообщений в порядке убывания даты. После чего выбрать те записи, у которых № меньше или равен 3.
select post_id, user_id, date_added, post_text from
( select posts.*,
row_number ( ) over ( partition by user_id order by date_added desc ) i
from posts ) t where i
Производительность — двойное сканирование таблицы: сначала для нумерации (нет возможности ограничиться нумерацией только нескольких строк из группы), потом отбросить не удовлетворяющие условию where i
Для случайных сообщений пользователя достаточно заменить сортировку по убыванию даты order by date_added desc на случайную — order by rand ( ) .
5. пользовательские переменные
Та же идея, что и в предыдущем варианте, только реализована с помощью пользовательских переменных (user variables). Актуально для версий, в которых нет оконных функций.
select post_id, user_id, date_added, post_text from
( select posts.*, if ( @gr=user_id, @i:=@i +1 , @i:= 1 + least ( @gr:=user_id, 0 ) ) x
from posts, ( select @i:= 0 , @gr:= 0 ) t order by user_id, date_added desc ) t1 where x
Как и в примере с row_number(), мы нумеруем сообщения каждого пользователя в порядке убывания даты добавления (только делаем это с помощью пользовательских переменных), затем оставляем только те строки, у которых № меньше или равен 3.
Способ можно применять и для выборки нескольких случайных сообщений юзера. Однако простая замена сортировки по убыванию даты на случайную не даст нужного эффекта.
select post_id, user_id, date_added, post_text from
( select t2.*, if ( @gr=user_id, @i:=@i +1 , @i:= 1 + least ( @gr:=user_id, 0 ) ) x
from ( select posts.*, rand ( ) q, @z:= 1 from posts ) t2, ( select @i:= 0 , @gr:= 0 ) t order by user_id, q ) t1 where x
Обратите внимание на добавление ещё одной переменной @z:=1, которая более нигде не применяется. С некоторых пор оптимизатор научился упрощать тривиальные с его точки зрения from-подзапросы, перенося условия из них во внешний запрос. Однако, если в подзапросе используются переменные, то пока оптимизатор материализует такие подзапросы.
В общем, пользовательские переменные — мощный инструмент написания и оптимизации запросов, но нужно быть очень внимательными при работе с ними, понимать на каком эффекте основан, используемый вами трюк, и проверять работоспособность в новых версиях. Подробнее см Оптимизация запросов MySQL с использованием пользовательских переменных
6. подзапросы lateral
В MySQL 8.0.14 добавлена поддержка зависимых подзапросов в части FROM, с помощью которых наша задача решается оптимальным образом. Сначала формируется список идентификаторов пользователей (производная таблица t1) и для каждого из выбираются нужные строки (коррелированный from-подзапрос t2).
select t2.* from ( select user_id from posts group by user_id ) as t1,
lateral ( select * from posts where t1.user_id=posts.user_id order by date_added desc limit 3 ) as t2;
К удивлению, при выборе строк в подзапросе t2 сервер читает все строки группы и делает файловую сортировку вместо нахождения 3 нужных строк по уникальному индексу (user_id, date_added). Возможно в будущих версиях это поведение будет исправлено.
На сегодняшний день можно применить следующий трюк, благодаря которому MySQL будет использовать индекс — расширить выражение сортировки следующим образом:
.. order by user_id desc , date_added desc limit 3
Спасибо за совет Гильяму Бишоту.
Для выборки случайных строк из группы достаточно в lateral подзапросе заменить сортировку на случайную — order by rand().
Заключение
Сводная таблица, показывающая среднее время выполнения изложенных выше способов для нахождения трёх последних и трёх случайных сообщений каждого пользователя на тестовых данных в 16000 строк, равномерно распределенных среди count(distinct user_id) = 20.
| время, с | ||
| 3 последних | 3 случайных | |
| 1. зависимый подзапрос | 10.8 | — |
| 2. join + group by | 11 | — |
| 3. group_concat() | 0.06 | 0.03 |
| 3. модифицированный вариант + WITH | 0.03 | 0.016 |
| 3. модифицированный вариант без WITH | 0.08 | — |
| 4. row_number() | 0.15 | 0.17 |
| 5. пользовательские переменные | 0.13 | 0.14 |
| 6. LATERAL | 0.005 | 0.03 |
Если ваша СУБД поддерживает подзапросы lateral, то используйте их. Вообще, каждый раз, когда есть необходимость «для каждого значения выбрать . » — возможно вы сможете эффективно решить задачу, используя LATERAL производные таблицы. Подробнее об этой функциональности можно прочитать в статье В MySQL 8.0.14 добавлена поддержка производных таблиц типа LATERAL.
Неожиданно высокую эффективность показал третий способ, особенно для выборки случайных строк из группы. Неожиданно, потому что как правило рекомендуют использовать второй и четвертый (для MySQL до недавнего времени его реализацию через переменные, т.е. пятый) способы.
Также не забывайте про вариант реализации lateral во внешнем приложении: сначала выбираем список идентификаторов групп, потом в цикле отдельными запросами находим нужные строки для каждой группы. Порой встречается ошибочное мнение, что это ламерский подход и правильно решать задачу в один запрос к базе. По эффективности множество «простых» запросов, выбирающих по индексу нужные строки, лучше одного «сложного», который многократно сканирует всю таблицу. Разумеется это справедливо, когда в группах много элементов, и нужно вернуть лишь малую часть, иначе накладные расходы могут превысить выигрыш от снижения количества прочитанных строк.
P.S.При выборе подходящего варианта проводите тестирование в своем окружении.
Если после прочтения статьи ваш вопрос остался нерешенным, задавайте его на форуме SQLinfo.
Дата публикации: 15.09.2019
© Все права на данную статью принадлежат порталу SQLInfo.ru. Перепечатка в интернет-изданиях разрешается только с указанием автора и прямой ссылки на оригинальную статью. Перепечатка в бумажных изданиях допускается только с разрешения редакции.
Примеры SELECT (Transact-SQL)
В этой статье приведены примеры использования инструкции SELECT .
В этой статье требуется AdventureWorks2022 пример базы данных, которую можно скачать на домашней странице примеров и проектов сообщества Microsoft SQL Server.
А. Использование SELECT для получения строк и столбцов
В следующем примере приведены три примера кода. В ходе выполнения первого примера кода возвращаются все строки (предложение WHERE не указано), а также все столбцы (используется звездочка, * ) таблицы Product базы данных AdventureWorks2022 .
USE AdventureWorks2022; GO SELECT * FROM Production.Product ORDER BY Name ASC; -- Alternate way. USE AdventureWorks2022; GO SELECT p.* FROM Production.Product AS p ORDER BY Name ASC; GO
В ходе выполнения данного примера кода происходит выдача всех строк (предложение WHERE не задано) и подмножества столбцов ( Name , ProductNumber , ListPrice ) таблицы Product базы данных AdventureWorks2022 . Дополнительно выведено название столбца.
USE AdventureWorks2022; GO SELECT Name, ProductNumber, ListPrice AS Price FROM Production.Product ORDER BY Name ASC; GO
В ходе выполнения данного примера кода происходит выдача всех строк таблицы Product , для которых линейки продуктов начинаются символом R и для которых длительность изготовления не превышает 4 дней.
USE AdventureWorks2022; GO SELECT Name, ProductNumber, ListPrice AS Price FROM Production.Product WHERE ProductLine = 'R' AND DaysToManufacture < 4 ORDER BY Name ASC; GO
B. Использование SELECT с заголовками столбцов и вычислениями
В ходе выполнения следующего примера возвращаются все строки таблицы Product . В результате выполнения первого примера выдаются все объемы продаж и скидки по всем продуктам. Во втором примере вычисляется годовой доход от продажи каждого вида продукции.
USE AdventureWorks2022; GO SELECT p.Name AS ProductName, NonDiscountSales = (OrderQty * UnitPrice), Discounts = ((OrderQty * UnitPrice) * UnitPriceDiscount) FROM Production.Product AS p INNER JOIN Sales.SalesOrderDetail AS sod ON p.ProductID = sod.ProductID ORDER BY ProductName DESC; GO
Данный запрос вычисляет доход от продажи по каждому виду продукции для каждого заказа.
USE AdventureWorks2022; GO SELECT 'Total income is', ((OrderQty * UnitPrice) * (1.0 - UnitPriceDiscount)), ' for ', p.Name AS ProductName FROM Production.Product AS p INNER JOIN Sales.SalesOrderDetail AS sod ON p.ProductID = sod.ProductID ORDER BY ProductName ASC; GO
C. Использование DISTINCT с SELECT
В приведенном ниже примере для предотвращения получения повторяющихся заголовков используется оператор DISTINCT .
USE AdventureWorks2022; GO SELECT DISTINCT JobTitle FROM HumanResources.Employee ORDER BY JobTitle; GO
D. Создание таблиц с помощью SELECT INTO
В следующем примере в базе данных #Bicycles создается временная таблица tempdb .
USE tempdb; GO IF OBJECT_ID(N'#Bicycles', N'U') IS NOT NULL DROP TABLE #Bicycles; GO SELECT * INTO #Bicycles FROM AdventureWorks2022.Production.Product WHERE ProductNumber LIKE 'BK%'; GO
В данном примере создается постоянная таблица NewProducts .
USE AdventureWorks2022; GO IF OBJECT_ID('dbo.NewProducts', 'U') IS NOT NULL DROP TABLE dbo.NewProducts; GO ALTER DATABASE AdventureWorks2022 SET RECOVERY BULK_LOGGED; GO SELECT * INTO dbo.NewProducts FROM Production.Product WHERE ListPrice > $25 AND ListPrice < $100; GO ALTER DATABASE AdventureWorks2022 SET RECOVERY FULL; GO
Д. Использование сопоставленных вложенных запросов
Коррелированный запрос — это запрос, зависящий от результатов выполнения другого запроса. Этот запрос можно выполнять многократно, один раз для каждой строки, которая может быть выбрана внешним запросом.
В первом примере представлены семантически эквивалентные запросы для демонстрации различий в использовании ключевых слов EXISTS и IN . В обоих примерах приведены допустимые вложенные запросы, извлекающие по одному экземпляру продукции каждого наименования, для которых модель продукта — «long sleeve logo jersey» (кофта с длинными рукавами, с эмблемой), а значения столбцов ProductModelID таблиц Product и ProductModel совпадают.
USE AdventureWorks2022; GO SELECT DISTINCT Name FROM Production.Product AS p WHERE EXISTS ( SELECT * FROM Production.ProductModel AS pm WHERE p.ProductModelID = pm.ProductModelID AND pm.Name LIKE 'Long-Sleeve Logo Jersey%' ); GO -- OR USE AdventureWorks2022; GO SELECT DISTINCT Name FROM Production.Product WHERE ProductModelID IN ( SELECT ProductModelID FROM Production.ProductModel AS pm WHERE p.ProductModelID = pm.ProductModelID AND Name LIKE 'Long-Sleeve Logo Jersey%' ); GO
В следующем примере используется и извлекается IN один экземпляр первого имени и имени семьи каждого сотрудника, для которого указан 5000.00 бонус в SalesPerson таблице, и для которого идентификаторы сотрудников совпадают в Employee таблицах и SalesPerson таблицах.
USE AdventureWorks2022; GO SELECT DISTINCT p.LastName, p.FirstName FROM Person.Person AS p INNER JOIN HumanResources.Employee AS e ON e.BusinessEntityID = p.BusinessEntityID WHERE 5000.00 IN ( SELECT Bonus FROM Sales.SalesPerson AS sp WHERE e.BusinessEntityID = sp.BusinessEntityID ); GO
Предыдущий вложенный запрос в этом операторе нельзя оценивать независимо от внешнего запроса. Он требует значения параметра Employee.EmployeeID , однако это значение меняется, когда ядро СУБД SQL Server обрабатывает строки в Employee .
Коррелированный вложенный запрос также может использоваться в предложении HAVING внешнего запроса. В данном примере осуществляется поиск моделей продуктов, для которых максимальная цена в каталоге в два раза превышает среднюю цену по нему.
USE AdventureWorks2022; GO SELECT p1.ProductModelID FROM Production.Product AS p1 GROUP BY p1.ProductModelID HAVING MAX(p1.ListPrice) >= ( SELECT AVG(p2.ListPrice) * 2 FROM Production.Product AS p2 WHERE p1.ProductModelID = p2.ProductModelID ); GO
В этом примере используются два сопоставленных вложенных запроса для поиска имен сотрудников, которые продали определенный продукт.
USE AdventureWorks2022; GO SELECT DISTINCT pp.LastName, pp.FirstName FROM Person.Person pp INNER JOIN HumanResources.Employee e ON e.BusinessEntityID = pp.BusinessEntityID WHERE pp.BusinessEntityID IN ( SELECT SalesPersonID FROM Sales.SalesOrderHeader WHERE SalesOrderID IN ( SELECT SalesOrderID FROM Sales.SalesOrderDetail WHERE ProductID IN ( SELECT ProductID FROM Production.Product p WHERE ProductNumber = 'BK-M68B-42' ) ) ); GO
F. Использование GROUP BY
В следующем примере находится общий объем продаж для каждого заказа в базе данных.
USE AdventureWorks2022; GO SELECT SalesOrderID, SUM(LineTotal) AS SubTotal FROM Sales.SalesOrderDetail GROUP BY SalesOrderID ORDER BY SalesOrderID; GO
Так как в запросе используется предложение GROUP BY , то для каждого заказа выводится только одна строка, содержащая общий объем продаж.
G. Использование GROUP BY с несколькими группами
В данном примере вычисляются средние цены и объемы продаж за последний год, сгруппированные по коду продукта и идентификатору специального предложения.
USE AdventureWorks2022; GO SELECT ProductID, SpecialOfferID, AVG(UnitPrice) AS [Average Price], SUM(LineTotal) AS SubTotal FROM Sales.SalesOrderDetail GROUP BY ProductID, SpecialOfferID ORDER BY ProductID; GO
H. Использование GROUP BY и WHERE
В следующем примере после извлечения строк, содержащих цены каталога, превышающие $1000 , происходит их разделение на группы.
USE AdventureWorks2022; GO SELECT ProductModelID, AVG(ListPrice) AS [Average List Price] FROM Production.Product WHERE ListPrice > $1000 GROUP BY ProductModelID ORDER BY ProductModelID; GO
I. Использование GROUP BY с выражением
В следующем примере производится группировка с помощью выражения. Можно сгруппировать по выражению, если выражение не включает агрегатные функции.
USE AdventureWorks2022; GO SELECT AVG(OrderQty) AS [Average Quantity], NonDiscountSales = (OrderQty * UnitPrice) FROM Sales.SalesOrderDetail GROUP BY (OrderQty * UnitPrice) ORDER BY (OrderQty * UnitPrice) DESC; GO
J. Использование GROUP BY с ORDER BY
В следующем примере для каждого типа продуктов вычисляется средняя цена, а также осуществляется сортировка полученных результатов по возрастанию.
USE AdventureWorks2022; GO SELECT ProductID, AVG(UnitPrice) AS [Average Price] FROM Sales.SalesOrderDetail WHERE OrderQty > 10 GROUP BY ProductID ORDER BY AVG(UnitPrice); GO
K. Использование предложения HAVING
В первом из приведенных ниже примеров показывается использование предложения HAVING с агрегатной функцией. В нем производится группировка строк таблицы SalesOrderDetail по коду продукта, а также удаляются строки, соответствующие продуктам, для которых средний объем заказа не превышает пяти. Во втором примере показывается использование предложения HAVING без агрегатной функции.
USE AdventureWorks2022; GO SELECT ProductID FROM Sales.SalesOrderDetail GROUP BY ProductID HAVING AVG(OrderQty) > 5 ORDER BY ProductID; GO
В данном запросе внутри предложения LIKE используется предложение HAVING .
USE AdventureWorks2022; GO SELECT SalesOrderID, CarrierTrackingNumber FROM Sales.SalesOrderDetail GROUP BY SalesOrderID, CarrierTrackingNumber HAVING CarrierTrackingNumber LIKE '4BD%' ORDER BY SalesOrderID ; GO
L. Использование HAVING и GROUP BY
В следующем примере показано использование предложений GROUP BY , HAVING , WHERE и ORDER BY в одной инструкции SELECT . В результате его выполнения в группах и сводных значениях не учитываются строки, соответствующие продуктам с ценами выше $25 и средним объемом заказов ниже 5. Также осуществляется сортировка результатов по ProductID .
USE AdventureWorks2022; GO SELECT ProductID FROM Sales.SalesOrderDetail WHERE UnitPrice < 25.00 GROUP BY ProductID HAVING AVG(OrderQty) >5 ORDER BY ProductID; GO
M. Использование HAVING с СУММ и AVG
В следующем примере производится группировка строк таблицы SalesOrderDetail по коду продукта, а затем выводятся только те группы, для которых общий объем продаж составляет более $1000000.00 , а средний объем заказа не превышает 3 .
USE AdventureWorks2022; GO SELECT ProductID, AVG(OrderQty) AS AverageQuantity, SUM(LineTotal) AS Total FROM Sales.SalesOrderDetail GROUP BY ProductID HAVING SUM(LineTotal) > $1000000.00 AND AVG(OrderQty) < 3; GO
Чтобы просмотреть продукты с общим объемом продаж, превышающих $2000000.00 , используйте следующий запрос:
USE AdventureWorks2022; GO SELECT ProductID, Total = SUM(LineTotal) FROM Sales.SalesOrderDetail GROUP BY ProductID HAVING SUM(LineTotal) > $2000000.00; GO
Если вы хотите убедиться в наличии не менее 1500 элементов, участвующих в вычислениях для каждого продукта, используйте HAVING COUNT(*) > 1500 для устранения продуктов, возвращающих итоги для меньшего количества 1500 проданных элементов. Этот запрос выглядит следующим образом.
USE AdventureWorks2022; GO SELECT ProductID, SUM(LineTotal) AS Total FROM Sales.SalesOrderDetail GROUP BY ProductID HAVING COUNT(*) > 1500; GO
О. Использование указания оптимизатора INDEX
В следующем примере показаны два способа использования указания оптимизатора INDEX . В первом примере показано, как принудительно принудить оптимизатора использовать некластеризованный индекс для получения строк из таблицы. Во втором примере выполняется проверка таблицы с помощью индекса 0.
USE AdventureWorks2022; GO SELECT pp.FirstName, pp.LastName, e.NationalIDNumber FROM HumanResources.Employee AS e WITH (INDEX (AK_Employee_NationalIDNumber)) INNER JOIN Person.Person AS pp ON e.BusinessEntityID = pp.BusinessEntityID WHERE LastName = 'Johnson'; GO -- Force a table scan by using INDEX = 0. USE AdventureWorks2022; GO SELECT pp.LastName, pp.FirstName, e.JobTitle FROM HumanResources.Employee AS e WITH (INDEX = 0) INNER JOIN Person.Person AS pp ON e.BusinessEntityID = pp.BusinessEntityID WHERE LastName = 'Johnson'; GO
M. Использование OPTION и подсказок GROUP
В следующем примере демонстрируется совместное использование предложений OPTION (GROUP) и GROUP BY .
USE AdventureWorks2022; GO SELECT ProductID, OrderQty, SUM(LineTotal) AS Total FROM Sales.SalesOrderDetail WHERE UnitPrice < $5.00 GROUP BY ProductID, OrderQty ORDER BY ProductID, OrderQty OPTION (HASH GROUP, FAST 10); GO
O. Использование указания запроса UNION
В следующем примере используется указание запроса MERGE UNION .
USE AdventureWorks2022; GO SELECT BusinessEntityID, JobTitle, HireDate, VacationHours, SickLeaveHours FROM HumanResources.Employee AS e1 UNION SELECT BusinessEntityID, JobTitle, HireDate, VacationHours, SickLeaveHours FROM HumanResources.Employee AS e2 OPTION (MERGE UNION); GO
P. Использование UNION
При выполнении следующего примера в результирующий набор включается содержимое столбцов ProductModelID и Name таблиц ProductModel и Gloves .
USE AdventureWorks2022; GO IF OBJECT_ID('dbo.Gloves', 'U') IS NOT NULL DROP TABLE dbo.Gloves; GO -- Create Gloves table. SELECT ProductModelID, Name INTO dbo.Gloves FROM Production.ProductModel WHERE ProductModelID IN (3, 4); GO -- Here is the simple union. USE AdventureWorks2022; GO SELECT ProductModelID, Name FROM Production.ProductModel WHERE ProductModelID NOT IN (3, 4) UNION SELECT ProductModelID, Name FROM dbo.Gloves ORDER BY Name; GO
В. Использование SELECT INTO с UNION
При выполнении следующего примера предложение INTO во второй инструкции SELECT указывает, что в таблице с именем ProductResults содержится итоговый результирующий набор объединения заданных столбцов таблиц ProductModel и Gloves . Таблица Gloves была создана в результате выполнения первой инструкции SELECT .
USE AdventureWorks2022; GO IF OBJECT_ID('dbo.ProductResults', 'U') IS NOT NULL DROP TABLE dbo.ProductResults; GO IF OBJECT_ID('dbo.Gloves', 'U') IS NOT NULL DROP TABLE dbo.Gloves; GO -- Create Gloves table. SELECT ProductModelID, Name INTO dbo.Gloves FROM Production.ProductModel WHERE ProductModelID IN (3, 4); GO USE AdventureWorks2022; GO SELECT ProductModelID, Name INTO dbo.ProductResults FROM Production.ProductModel WHERE ProductModelID NOT IN (3, 4) UNION SELECT ProductModelID, Name FROM dbo.Gloves; GO SELECT ProductModelID, Name FROM dbo.ProductResults;
R. Использование UNION двух операторов SELECT с ORDER BY
При использовании предложения UNION необходимо соблюдать порядок следования определенных параметров. В следующем примере показаны случаи правильного и неверного использования UNION в двух инструкциях SELECT , в которых необходимо переименовать столбцы на выходе.
USE AdventureWorks2022; GO IF OBJECT_ID('dbo.Gloves', 'U') IS NOT NULL DROP TABLE dbo.Gloves; GO -- Create Gloves table. SELECT ProductModelID, Name INTO dbo.Gloves FROM Production.ProductModel WHERE ProductModelID IN (3, 4); GO /* INCORRECT */ USE AdventureWorks2022; GO SELECT ProductModelID, Name FROM Production.ProductModel WHERE ProductModelID NOT IN (3, 4) ORDER BY Name UNION SELECT ProductModelID, Name FROM dbo.Gloves; GO /* CORRECT */ USE AdventureWorks2022; GO SELECT ProductModelID, Name FROM Production.ProductModel WHERE ProductModelID NOT IN (3, 4) UNION SELECT ProductModelID, Name FROM dbo.Gloves ORDER BY Name; GO
S. Использование UNION трех инструкций SELECT для отображения эффектов ALL и круглых скобок
В следующих примерах используются UNION для объединения результатов трех таблиц, которые имеют одинаковые пять строк данных. В первом примере используется предложение UNION ALL , в результате чего выдаются все 15 строк. Второй пример используется без ALL исключения повторяющихся UNION строк из объединенных результатов трех SELECT операторов и возвращает пять строк.
В третьем примере с первым предложением ALL используется ключевое слово UNION , а во втором предложении UNION вместо ключевого слова ALL используются скобки. Второй UNION обрабатывается сначала, так как он находится в скобках, и возвращает пять строк, так как ALL параметр не используется и дубликаты удаляются. Эти пять строк объединяются с результатами первого SELECT с помощью UNION ALL ключевое слово. В данном случае повторяющиеся строки двух множеств, состоящих из пяти строк, не удаляются. Окончательный результат состоит из 10 строк.
USE AdventureWorks2022; GO IF OBJECT_ID('dbo.EmployeeOne', 'U') IS NOT NULL DROP TABLE dbo.EmployeeOne; GO IF OBJECT_ID('dbo.EmployeeTwo', 'U') IS NOT NULL DROP TABLE dbo.EmployeeTwo; GO IF OBJECT_ID('dbo.EmployeeThree', 'U') IS NOT NULL DROP TABLE dbo.EmployeeThree; GO SELECT pp.LastName, pp.FirstName, e.JobTitle INTO dbo.EmployeeOne FROM Person.Person AS pp INNER JOIN HumanResources.Employee AS e ON e.BusinessEntityID = pp.BusinessEntityID WHERE LastName = 'Johnson'; GO SELECT pp.LastName, pp.FirstName, e.JobTitle INTO dbo.EmployeeTwo FROM Person.Person AS pp INNER JOIN HumanResources.Employee AS e ON e.BusinessEntityID = pp.BusinessEntityID WHERE LastName = 'Johnson'; GO SELECT pp.LastName, pp.FirstName, e.JobTitle INTO dbo.EmployeeThree FROM Person.Person AS pp INNER JOIN HumanResources.Employee AS e ON e.BusinessEntityID = pp.BusinessEntityID WHERE LastName = 'Johnson'; GO -- Union ALL SELECT LastName, FirstName, JobTitle FROM dbo.EmployeeOne UNION ALL SELECT LastName, FirstName, JobTitle FROM dbo.EmployeeTwo UNION ALL SELECT LastName, FirstName, JobTitle FROM dbo.EmployeeThree; GO SELECT LastName, FirstName, JobTitle FROM dbo.EmployeeOne UNION SELECT LastName, FirstName, JobTitle FROM dbo.EmployeeTwo UNION SELECT LastName, FirstName, JobTitle FROM dbo.EmployeeThree; GO SELECT LastName, FirstName, JobTitle FROM dbo.EmployeeOne UNION ALL ( SELECT LastName, FirstName, JobTitle FROM dbo.EmployeeTwo UNION SELECT LastName, FirstName, JobTitle FROM dbo.EmployeeThree ); GO
Связанный контент
- CREATE TRIGGER (Transact-SQL)
- CREATE VIEW (Transact-SQL)
- DELETE (Transact-SQL)
- EXECUTE (Transact-SQL)
- Выражения (Transact-SQL)
- INSERT (Transact-SQL)
- LIKE (Transact-SQL)
- Операторы set — UNION (Transact-SQL)
- Операторы set — EXCEPT и INTERSECT (Transact-SQL)
- UPDATE (Transact-SQL)
- WHERE (Transact-SQL)
- PathName (Transact-SQL)
- SELECT — предложение INTO (Transact-SQL)
Обратная связь
Были ли сведения на этой странице полезными?
Как вывести первые 10 строк sql
Самым лучшим способом это сделать будет использование оператора LIMIT .
SELECT * FROM users LIMIT 10;
Такой запрос вернёт первые 10 записей из таблицы users.
