许多应用程序都有网格,一次显示一页数据库表中的数据。其中许多还允许用户选择每页的记录数,按任何列排序,并在结果中来回导航。
什么是实现这种模式的好算法,而不是将整个表带到客户端,然后在客户端过滤数据。您如何只将要显示的记录提供给用户?
LINQ 是否简化了解决方案?
许多应用程序都有网格,一次显示一页数据库表中的数据。其中许多还允许用户选择每页的记录数,按任何列排序,并在结果中来回导航。
什么是实现这种模式的好算法,而不是将整个表带到客户端,然后在客户端过滤数据。您如何只将要显示的记录提供给用户?
LINQ 是否简化了解决方案?
在 MS SQL Server 2005 及更高版本上,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 会更优化地执行它。
在 SQL 中执行分页有一种完全不同的、更快但鲜为人知的方法。这通常被称为“seek 方法”,如本博文中所述。
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
值是上一页最后一条记录的相应值。这允许您获取“下一页”。如果ORDER BY
方向是ASC
,只需使用即可>
。
使用上述方法,您不能在没有先获取前 40 条记录的情况下立即跳转到第 4 页。但通常,无论如何你都不想跳那么远。相反,您将获得更快的查询,该查询可能能够在恒定时间内获取数据,具体取决于您的索引。另外,您的页面保持“稳定”,无论基础数据是否发生变化(例如,在第 1 页,而您在第 4 页)。
例如,当在 Web 应用程序中延迟加载更多数据时,这是实现分页的最佳方式。
注意,“seek 方法”也称为keyset paging。
LINQ 与 .Net 3.5 中的 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
这里有一个关于这个的讨论
该技术在 78 毫秒内从 150,000 行数据库中获取页码 100,000
使用优化器知识和 SET ROWCOUNT,请求的页面中的第一个 EmployeeID 存储在本地变量中作为起点。接下来,将 ROWCOUNT 设置为 @maximumRows 中请求的最大记录数。这允许以更有效的方式对结果集进行分页。使用此方法还可以利用表上预先存在的索引,因为它直接进入基表而不是本地创建的表。
恐怕我无法判断它是否比当前接受的答案更好。