Недавно, реализуя некоторый код доступа к данным, я столкнулся с задачей выбора последних N записей для каждой сущности. Пользователь kuda78 подсказал вместо многоэтажной выборки использовать метод SelectMany.
Исследуя, какой SQL код создает LinqToSQL, я натолкнулся на интересный SQL оператор APPLY.
Как гласит MSDN эта команда выполняет следующее:
http://technet.microsoft.com/en-us/library/ms175156.aspx
Оператор APPLY позволяет вызывать возвращающую табличное значение функцию для каждой строки, возвращаемой внешним табличным выражением запроса. Возвращающая табличное значение функция выступает в роли правого входа, а внешнее табличное выражение — в роли левого входа. Правый вход оценивается для каждой строки из левого входа, а созданные строки объединяются для конечного вывода. Список столбцов, созданных оператором APPLY, является набором столбцов в левом входе, за которым следует список столбцов, возвращенный правым входом.
Как оказалось APPLY очень хорошо подходит к решению поставленной задачи.
Давайте рассмотрим на примере:
Задача: Выбрать 10 последних заказов для каждого заказчика.
Пускай имеем следующую простую структуру БД:
Теперь нам надо выбрать 10 последних заказов для каждого заказчика. Раньше мы пользовались следующим подходом: сначала для каждого заказчика выбирал дату, начиная с которой у него было 10 заказов, а потом выбирал все заказы с этой даты.
* Для простоты я специально сделал допущение, что 2 заказа в один и тот же момент времени быть не могут.
С использованием APPLY SQL код приобрел большую читаемость:
План и время выполнения запроса при наличии всех необходимых индексов также вселяют уверенность, что эта функция еще неоднократно нам пригодится.
Файл создания бд с индексами: CreateDB.txt
Файл с SQL запросами: Queries.txt
Исследуя, какой SQL код создает LinqToSQL, я натолкнулся на интересный SQL оператор APPLY.
Как гласит MSDN эта команда выполняет следующее:
http://technet.microsoft.com/en-us/library/ms175156.aspx
Оператор APPLY позволяет вызывать возвращающую табличное значение функцию для каждой строки, возвращаемой внешним табличным выражением запроса. Возвращающая табличное значение функция выступает в роли правого входа, а внешнее табличное выражение — в роли левого входа. Правый вход оценивается для каждой строки из левого входа, а созданные строки объединяются для конечного вывода. Список столбцов, созданных оператором APPLY, является набором столбцов в левом входе, за которым следует список столбцов, возвращенный правым входом.
Как оказалось APPLY очень хорошо подходит к решению поставленной задачи.
Давайте рассмотрим на примере:
Задача: Выбрать 10 последних заказов для каждого заказчика.
Пускай имеем следующую простую структуру БД:
CREATE TABLE Customer ( CustomerID INT PRIMARY KEY, CustomerName NVARCHAR(30) NOT NULL ) CREATE TABLE Nomenclature ( NomenclatureID INT PRIMARY KEY, NomenclatureName NVARCHAR(30) NOT NULL, Price MONEY NOT NULL ) CREATE TABLE Deal ( DealID INT IDENTITY(1, 1) PRIMARY KEY, CustomerID INT NOT NULL, NomenclatureID INT NOT NULL, [Count] DECIMAL(8,2) NOT NULL, DealDate DATETIME NOT NULL )
Теперь нам надо выбрать 10 последних заказов для каждого заказчика. Раньше мы пользовались следующим подходом: сначала для каждого заказчика выбирал дату, начиная с которой у него было 10 заказов, а потом выбирал все заказы с этой даты.
SELECT d.DealDate, c.CustomerName, n.NomenclatureName, n.Price, d.Count FROM Customer c JOIN Deal d ON d.CustomerID = c.CustomerID JOIN (SELECT c.CustomerID, (SELECT MIN(lastDeals.DealDate) FROM (SELECT TOP 10 d1.DealDate FROM Deal d1 WHERE d1.CustomerID = c.CustomerID ORDER BY d1.DealDate DESC) LastDeals) LastDealDate FROM Customer c) ld ON ld.CustomerID = c.CustomerID JOIN Nomenclature n ON n.NomenclatureID = d.NomenclatureID WHERE d.DealDate >= ld.LastDealDate ORDER BY c.CustomerName, d.DealDate DESC
* Для простоты я специально сделал допущение, что 2 заказа в один и тот же момент времени быть не могут.
С использованием APPLY SQL код приобрел большую читаемость:
SELECT d.DealDate, c.CustomerName, n.NomenclatureName, n.Price, d.Count FROM Customer c OUTER APPLY (SELECT TOP 10 d1.* FROM Deal d1 Where d1.CustomerID = c.CustomerID ORDER BY d1.DealDate DESC) d INNER JOIN Nomenclature n ON n.NomenclatureID = d.NomenclatureID ORDER BY c.CustomerName, d.DealDate DESC
План и время выполнения запроса при наличии всех необходимых индексов также вселяют уверенность, что эта функция еще неоднократно нам пригодится.
Файл создания бд с индексами: CreateDB.txt
Файл с SQL запросами: Queries.txt
