Pergunta

Muitos aplicativos possuem grades que exibem dados de uma tabela de banco de dados, uma página por vez.Muitos deles também permitem que o usuário escolha o número de registros por página, classifique por qualquer coluna e navegue pelos resultados.

Qual é um bom algoritmo para implementar esse padrão sem trazer a tabela inteira para o cliente e depois filtrar os dados no cliente.Como você traz apenas os registros que deseja exibir para o usuário?

O LINQ simplifica a solução?

Foi útil?

Solução

No MS SQL Server 2005 e superior, ROW_NUMBER() parece funcionar:

T-SQL:Paginação com ROW_NUMBER()

DECLARE @PageNum AS INT;
DECLARE @PageSize AS INT;
SET @PageNum = 2;
SET @PageSize = 10;

WITH OrdersRN AS
(
    SELECT ROW_NUMBER() OVER(ORDER BY OrderDate, OrderID) AS RowNum
          ,OrderID
          ,OrderDate
          ,CustomerID
          ,EmployeeID
      FROM dbo.Orders
)

SELECT * 
  FROM OrdersRN
 WHERE RowNum BETWEEN (@PageNum - 1) * @PageSize + 1 
                  AND @PageNum * @PageSize
 ORDER BY OrderDate
         ,OrderID;

Outras dicas

Eu recomendo usar o LINQ ou tentar copiar o que ele faz.Eu tenho um aplicativo onde uso os métodos LINQ Take e Skip para recuperar dados paginados.O código é mais ou menos assim:

MyDataContext db = new MyDataContext();
var results = db.Products
    .Skip((pageNumber - 1) * pageSize)
    .Take(pageSize);

A execução do SQL Server Profiler revela que o LINQ está convertendo esta consulta em SQL semelhante a:

SELECT [ProductId], [Name], [Cost], and so on...
FROM (
    SELECT [ProductId], [Name], [Cost], [ROW_NUMBER]
    FROM (
       SELECT ROW_NUMBER() OVER (ORDER BY [Name]) AS [ROW_NUMBER], 
           [ProductId], [Name], [Cost]
       FROM [Products]
    )
    WHERE [ROW_NUMBER] BETWEEN 10 AND 20
)
ORDER BY [ROW_NUMBER]

Em inglês simples:
1.Filtre suas linhas e use a função ROW_NUMBER para adicionar números de linhas na ordem desejada.
2.Filtre (1) para retornar apenas os números das linhas que você deseja em sua página.
3.Classifique (2) pelo número da linha, que é a mesma ordem desejada (neste caso, por Nome).

Existem essencialmente duas maneiras de fazer paginação no banco de dados (presumo que você esteja usando o SQL Server):

Usando DESLOCAMENTO

Outros explicaram como ROW_NUMBER() OVER() função de classificação pode ser usada para executar páginas.Vale ressaltar que o SQL Server 2012 finalmente incluiu suporte ao padrão SQL OFFSET .. FETCH cláusula:

SELECT first_name, last_name, score
FROM players
ORDER BY score DESC
OFFSET 40 ROWS FETCH NEXT 10 ROWS ONLY

Se você estiver usando o SQL Server 2012 e a compatibilidade com versões anteriores não for um problema, você provavelmente deverá preferir esta cláusula, pois ela será executada de maneira mais otimizada pelo SQL Server em casos extremos.

Usando o método SEEK

Existe uma maneira totalmente diferente, muito mais rápida, mas menos conhecida de realizar paginação em SQL.Isso geralmente é chamado de "método de busca", conforme descrito em esta postagem do blog aqui.

SELECT TOP 10 first_name, last_name, score
FROM players
WHERE (score < @previousScore)
   OR (score = @previousScore AND player_id < @previousPlayerId)
ORDER BY score DESC, player_id DESC

O @previousScore e @previousPlayerId valores são os respectivos valores do último registro da página anterior.Isso permite que você busque a “próxima” página.Se o ORDER BY direção é ASC, basta usar > em vez de.

Com o método acima, você não pode pular imediatamente para a página 4 sem primeiro buscar os 40 registros anteriores.Mas muitas vezes você não quer ir tão longe de qualquer maneira.Em vez disso, você obtém uma consulta muito mais rápida que pode buscar dados em tempo constante, dependendo da sua indexação.Além disso, suas páginas permanecem "estáveis", independentemente de os dados subjacentes serem alterados (por exemplo,na página 1, enquanto você está na página 4).

Esta é a melhor maneira de implementar a paginação ao carregar lentamente mais dados em aplicativos da web, por exemplo.

Observe que o "método de busca" também é chamado paginação de conjunto de chaves.

LINQ combinado com expressões lambda e classes anônimas em .Net 3.5 imensamente simplifica esse tipo de coisa.

Consultando o banco de dados:

var customers = from c in db.customers
                join p in db.purchases on c.CustomerID equals p.CustomerID
                where p.purchases > 5
                select c;

Número de registros por página:

customers = customers.Skip(pageNum * pageSize).Take(pageSize);

Classificando por qualquer coluna:

customers = customers.OrderBy(c => c.LastName);

Obtendo apenas os campos selecionados do servidor:

var customers = from c in db.customers
                join p in db.purchases on c.CustomerID equals p.CustomerID
                where p.purchases > 5
                select new
                {
                    CustomerID = c.CustomerID,
                    FirstName = c.FirstName,
                    LastName = c.LastName
                };

Isso cria uma classe anônima de tipo estático na qual você pode acessar suas propriedades:

var firstCustomer = customer.First();
int id = firstCustomer.CustomerID;

Os resultados das consultas são carregados lentamente por padrão, portanto, você não conversa com o banco de dados até realmente precisar dos dados.O LINQ em .Net também simplifica bastante as atualizações, mantendo um contexto de dados de todas as alterações feitas e atualizando apenas os campos que você altera.

Solução Oracle:

select * from (
    select a.*, rownum rnum from (
        YOUR_QUERY_GOES_HERE -- including the order by
    ) a
    where rownum <= MAX_ROW
 ) where rnum >= MIN_ROW

Existem algumas soluções que utilizo com o MS SQL 2005.

Um deles é ROW_NUMBER().Mas, pessoalmente, não gosto de ROW_NUMBER() porque não funciona para grandes resultados (o banco de dados no qual trabalho é muito grande - mais de 1 TB de dados executando milhares de consultas em segundo - você sabe - grandes redes sociais site).

Aqui está minha solução favorita.

Usarei uma espécie de pseudocódigo do T-SQL.

Vamos encontrar a 2ª página de usuários classificados por nome próprio, sobrenome, onde cada página possui 10 registros.

@page = 2 -- input parameter
@size = 10 -- can be optional input parameter

if @page < 1 then begin
    @page = 1 -- check page number
end
@start = (@page-1) * @size + 1 -- @page starts at record no @start

-- find the beginning of page @page
SELECT TOP (@start)
    @forename = forename,
    @surname = surname
    @id = id
FROM
    users
ORDER BY
    forename,
    surname,
    id -- to keep correct order in case of have two John Smith.

-- select @size records starting from @start
SELECT TOP (@size)
    id,
    forename,
    surname
FROM
    users
WHERE
    (forename = @forename and surname = @surname and id >= @id) -- the same name and surname, but bigger id
    OR (forename = @forename and surname > @surname) -- the same name, but bigger surname, id doesn't matter
    OR (forename > @forename) -- bigger forename, the rest doesn't matter
ORDER BY
    forename,
    surname,
    id

Na verdade, o LINQ possui métodos Skip e Take que podem ser combinados para escolher quais registros serão buscados.

Confira isso.

Para banco de dados: Paginação no SQL Server 2005

Há uma discussão sobre isso Aqui

A técnica obtém o número de página 100.000 de um banco de dados de 150.000 linhas em 78ms

Usando o conhecimento do otimizador e SET ROWCOUNT, o primeiro EmployeeID na página solicitada é armazenado em uma variável local como ponto de partida.A seguir, SET ROWCOUNT para o número máximo de registros solicitados em @maximumRows.Isso permite paginar o conjunto de resultados de uma maneira muito mais eficiente.O uso desse método também aproveita os índices pré-existentes na tabela, pois vai diretamente para a tabela base e não para uma tabela criada localmente.

Receio não ser capaz de julgar se é melhor do que a resposta atualmente aceita.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top