Недавно, реализуя некоторый код доступа к данным, я столкнулся с задачей выбора последних 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