Выберите COUNT (*) подзапроса, не выполняя его дважды - PullRequest
19 голосов
/ 24 февраля 2009

У меня есть процедура для возврата набора результатов, который ограничен номером страницы и некоторыми другими вещами. В качестве параметра OUTPUT мне нужно вернуть общее количество выбранных строк в соответствии с параметрами, кроме номера страницы. Итак, у меня есть что-то подобное:

WITH SelectedItems AS
(SELECT Id, Row1, Row2, ROW_NUMBER() OVER (ORDER BY Row1) AS Position
FROM Items
WHERE Row2 = @Row2)
SELECT Id, Row1, Row2
FROM SelectedItems
WHERE Position BETWEEN @From AND @To

И тогда мне нужно установить для параметра OUTPUT количество строк во внутреннем запросе. Я могу просто скопировать запрос и посчитать его, но этот запрос может вернуть тысячи строк (и будет больше в будущем), поэтому я ищу способ сделать это с хорошей производительностью. Я думал о табличных переменных, это хорошая идея? Или какие-либо другие предложения?

Точнее, это Microsoft SQL Server 2008.

Спасибо, Ян

Ответы [ 5 ]

19 голосов
/ 24 февраля 2009

Вы можете подсчитать общее количество строк в виде отдельного столбца в основном запросе, используя COUNT (*). Как это:

WITH SelectedItems AS
(SELECT Id, Row1, Row2, ROW_NUMBER() OVER (ORDER BY Row1) AS Position, 
COUNT(*) OVER () AS TotalRows
FROM Items
WHERE Row2 = @Row2)
SELECT Id, Row1, Row2
FROM SelectedItems
WHERE Position BETWEEN @From AND @To

Это вернет счет в вашем наборе результатов, а не в выходном параметре, но это должно соответствовать вашим требованиям. В противном случае объедините с временной таблицей:

DECLARE @tmp TABLE (Id int, RowNum int, TotalRows int);

WITH SelectedItems AS
(SELECT Id, Row1, Row2, ROW_NUMBER() OVER (ORDER BY Row1) AS Position, 
COUNT(*) OVER () AS TotalRows
FROM Items
WHERE Row2 = @Row2)
INSERT @tmp
SELECT Id, Row1, Row2
FROM SelectedItems
WHERE Position BETWEEN @From AND @To

SELECT TOP 1 @TotalRows = TotalRows FROM @tmp
SELECT * FROM @tmp

Вы обнаружите, что использование временной таблицы только для вашего постраничного результата не будет занимать много памяти (конечно, в зависимости от размера страницы), и вы сохраняете ее в течение короткого периода времени. Выбор полного набора результатов из временной таблицы и выбор TotalRows займет немного больше времени.

Это будет намного быстрее, чем выполнение совершенно отдельного запроса, который в моем тесте (повторяющий WITH) удвоил время выполнения.

3 голосов
/ 24 февраля 2009

Я думаю, вы должны сделать это в отдельном запросе. Хотя эти два запроса могут выглядеть примерно одинаково, но способ работы с ними оптимизатора запросов будет значительно отличаться.

Теоретически, SQL Server может даже не пройти все строки в подзапросе, чтобы иметь возможность его подсчитать.

2 голосов
/ 17 мая 2010

Вы ДОЛЖНЫ выполнить весь запрос, не ограничивая диапазон, хотя бы один раз, чтобы получить полное количество строк. Так как вы все равно собираетесь это сделать, вам следует выбрать @@ RowCount, чтобы вывести общее количество найденных строк, а не перегружать ваше устройство чтения данных столбцом избыточного количества (*) в каждой строке.

1. При первом запуске НОВОГО запроса:

select YOUR_COLUMNS 
from YOUR_TABLE 
where YOUR_SEARCH_CONDITION 
order by YOUR_COLUMN_ORDERING_LIST;
select @@rowcount;

2. ЧИТАЙТЕ только первые X строк

Приведенный выше запрос позволяет избежать заполнения вашего SqlDataReader столбцом избыточного COUNT (*), который в противном случае отправлялся бы при каждом вызове SqlDataReader.Read (). Так как вы запускаете запрос в первый раз ... вместо выбора диапазона, просто ПРОЧИТАЙТЕ только первые строки X. Это дает именно то, что вы хотите ... полный счетчик результатов, первый X записей и эффективная потоковая передача результирующего набора без столбца избыточного числа.

3. Для последующих запусков ТОГО ЖЕ запроса, чтобы получить подмножество результатов

select YOUR_COLUMNS 
from (select YOUR_COLUMNS, ROW_NUMBER() 
over(order by BY YOUR_COLUMN_ORDERING_LIST) as RowNum) Results 
where Results.RowNum between @From and @To;

В любом случае, @@rowcount - это самый прямой способ доступа к счетчику при первом запуске запроса без ограничения набора результатов (в любом случае вам понадобятся первые результаты X), без выполнения отдельного запроса count (), без использования временной таблицы и без включения избыточного столбца count ().

2 голосов
/ 24 февраля 2009

У меня сейчас нет доступа к моей кодовой базе, но я считаю, что вы можете использовать COUNT () OVER (или аналогичную команду), чтобы вернуть общее количество строк как часть подзапроса. Затем вы можете вернуть его как часть окончательного набора результатов. Он дублируется в каждой строке, но, на мой взгляд, это незначительное снижение производительности для приложения, использующего подкачку страниц, и в любом случае конечные результаты должны быть ограничены.

Через пару часов я выложу точный код.

РЕДАКТИРОВАТЬ: Вот строка, которую я использовал для генерации счетчика. В конце концов, наши разработчики хотели получить отдельный метод для подсчета, поэтому теперь я поддерживаю критерии поиска в двух местах одной и той же хранимой процедуры.

COUNT(*) OVER (PARTITION BY '') AS TotalCount

Добавьте это к вашему CTE, и затем вы можете выбрать TotalCount, и он будет столбцом в каждой из ваших строк.

1 голос
/ 25 февраля 2009

Не могли бы вы просто установить выходную переменную @@ RowCount? Это получит строки, затронутые последним выполненным оператором:

SELECT stuff FROM mytable

SET @output = @@ROWCOUNT

Это должно дать вам то, что вам нужно, и не требует повторного выполнения запроса.

...