Пейджинг NHibernate с SQL Server

При использовании методов SetFirstResult(start) и SetMaxResults(count) для реализации подкачки я заметил, что сгенерированный запрос выполняет только select top count * from some_table, и он не принимает параметр start или, по крайней мере, не находится на уровне базы данных, Похоже, что если я поручу NHibernate выполнить следующий запрос:

var users = session.CreateCriteria<User>()
                   .SetFirstResult(100)
                   .SetMaxResults(5)
                   .List<User>();

105 записей будут проходить между сервером базы данных и приложением, которое позаботится о том, чтобы удалить первые 100 записей. С таблицами, содержащими много строк, это может быть проблемой.

Я подтвердил, что с базой данных SQLite NHibernate использует ключевые слова OFFSET и LIMIT для фильтрации результатов на уровень базы данных. Я знаю, что нет эквивалента ключевого слова OFFSET и Oracle ROWNUM в SQL Server 2000, но есть ли какое-либо обходное решение? Как насчет SQL Server 2005/2008?

Ответы

Ответ 1

T-SQL, вариант SQL-языка, который использует Microsoft SQL Server, не имеет предложения limit. Он имеет модификатор select top {...}, который вы видите NHibernate, используя преимущества SQL Server 2000.

В SQL Server 2005 Microsoft представила функцию Row_Number() over (order by {...}), которая может использоваться в качестве замены в предложении limit, и вы можете видеть, что NHibernate использует это в SQL Server 2005/2008.

Запрос для SQLite может выглядеть как

select c.[ID], c.[Name]
from [Codes] c
where c.[Key] = 'abcdef'
order by c.[Order]
limit 20 offset 40

в то время как аналогичный запрос для SQL Server 2005 может выглядеть как

select c.[ID], c.[Name]
from (
    select c.[ID], c.[Name], c.[Order]
        , [!RowNum] = Row_Number() over (order by c.[Order])
    from [Codes] c
    where c.[Key] = 'abcdef'
) c
where c.[!RowNum] > 40 and c.[!RowNum] <= 60
order by c.[Order]

или, используя общие выражения таблицы, это может выглядеть как

with
    [Source] as (
        select c.[ID], c.[Name], c.[Order]
            , [!RowNum] = Row_Number() over (order by c.[Order])
        from [Codes] c
        where c.[Key] = 'abcdef'
    )
select c.[ID], c.[Name]
from [Source] c
where c.[!RowNum] > 40 and c.[!RowNum] <= 60
order by c.[Order]

Существует также способ сделать это и в SQL Server 2000

select c.[ID], c.[Name]
from (
    select top 20 c.[ID], c.[Name], c.[Order]
    from (
        select top 60 c.[ID], c.[Name], c.[Order]
        from [Codes] c
        where c.[Key] = 'abcdef'
        order by c.[Order]
    ) c
    order by c.[Order] desc
) c
order by c.[Order]

Ответ 2

Nhibernate достаточно умен, чтобы оптимизировать запрос. Если вы выберете первые 10 строк, он будет использовать инструкцию TOP. Если вы выберете не первые строки, то он будет использовать RowNum.

В sql 2000 нет функции RowNum, поэтому при обычном запросе невозможно выбрать необходимое количество строк. Для sql 2000, как я знаю, для таких оптимизационных представлений использовались.

В запросе sql 2005/2008 будут выбраны только требуемые строки.