许多应用程序都有网格,一次一页地显示数据库表中的数据。其中许多还允许用户选择每页的记录数、按任何列排序以及在结果中来回导航。

有什么好的算法可以实现这种模式,而不需要将整个表带到客户端,然后在客户端过滤数据。如何只将想要显示的记录提供给用户?

LINQ 是否简化了解决方案?

有帮助吗?

解决方案

在 MS SQL Server 2005 及更高版本上, ROW_NUMBER() 似乎有效:

T-SQL:使用 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;

其他提示

我建议使用 LINQ,或者尝试复制它的功能。我有一个应用程序,我在其中使用 LINQ Take 和 Skip 方法来检索分页数据。代码看起来像这样:

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

运行 SQL Server Profiler 显示 LINQ 正在将此查询转换为 SQL,类似于:

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]

用简单的英语来说:
1.筛选行并使用 ROW_NUMBER 函数按您想要的顺序添加行号。
2.过滤器 (1) 仅返回您想要在页面上显示的行号。
3.按行号对 (2) 进行排序,这与您想要的顺序相同(在本例中为按名称)。

本质上有两种在数据库中进行分页的方法(我假设您使用的是 SQL Server):

使用偏移量

其他人已经解释了如何 ROW_NUMBER() OVER() 可以使用排名功能来执行页面。值得一提的是,SQL Server 2012终于包含了对SQL标准的支持 OFFSET .. FETCH 条款:

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

如果您使用的是 SQL Server 2012 并且向后兼容性不是问题,您可能应该更喜欢此子句,因为在极端情况下 SQL Server 可以更优化地执行它。

使用SEEK方法

有一种完全不同、更快但鲜为人知的方法来在 SQL 中执行分页。这通常称为“查找方法”,如中所述 这篇博文在这里.

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

@previousScore@previousPlayerId value 是上一页最后一条记录的相应值。这允许您获取“下一页”。如果 ORDER BY 方向是 ASC, ,只需使用 > 反而。

使用上述方法,如果没有先获取前 40 条记录,则无法立即跳转到第 4 页。但通常情况下,您无论如何都不想跳那么远。相反,您可以获得更快的查询,可能能够在恒定时间内获取数据,具体取决于您的索引。另外,无论底层数据是否发生变化(例如,在第 1 页上,而您在第 4 页上)。

例如,当在 Web 应用程序中延迟加载更多数据时,这是实现分页的最佳方法。

注意,“seek 方法”也称为 按键组寻呼.

.Net 3.5中LINQ与lambda表达式和匿名类相结合 极大地 简化了这类事情。

查询数据库:

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

每页记录数:

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

按任意列排序:

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

仅从服务器获取选定的字段:

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
                };

这将创建一个静态类型的匿名类,您可以在其中访问其属性:

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

默认情况下,查询结果是延迟加载的,因此在实际需要数据之前,您不会与数据库对话。.Net 中的 LINQ 还通过保留您所做的任何更改的数据上下文并仅更新您更改的字段,极大地简化了更新。

甲骨文解决方案:

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

我在 MS SQL 2005 中使用了一些解决方案。

其中之一是 ROW_NUMBER()。但是,就我个人而言,我不喜欢 ROW_NUMBER() 因为它不适用于大结果(我工作的数据库非常大 - 超过 1TB 的数据在一秒钟内运行数千个查询 - 你知道 - 大型社交网络地点)。

这是我最喜欢的解决方案。

我将使用 T-SQL 的伪代码。

让我们找到按名字、姓氏排序的用户的第二页,其中每页有 10 条记录。

@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

实际上,LINQ 有 Skip 和 Take 方法,可以组合使用它们来选择提取哪些记录。

检查一下。

对于数据库: SQL Server 2005 中的分页

有一个关于这个的讨论 这里

该技术在 78 毫秒内从 150,000 行数据库中获取页码 100,000

使用优化器知识和 SET ROWCOUNT,请求的页面中的第一个 EmployeeID 存储在本地变量中作为起点。接下来,将 ROWCOUNT 设置为 @maximumRows 中请求的最大记录数。这允许以更有效的方式对结果集进行分页。使用此方法还可以利用表上预先存在的索引,因为它直接访问基表而不是本地创建的表。

恐怕我无法判断它是否比当前接受的答案更好。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top