许多应用程序都有网格,一次一页显示数据库表中的数据。其中许多还允许用户选择每页的记录数,按任意列排序,并在结果中来回导航。
在不将整个表格带到客户端然后过滤客户端上的数据的情况下,实现此模式的好算法是什么。如何将您想要显示的记录带给用户?
LINQ是否简化了解决方案?
答案 0 :(得分:11)
在MS SQL Server 2005及更高版本上,ROW_NUMBER()似乎有效:
T-SQL: Paging with 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;
答案 1 :(得分:7)
我建议使用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),与您想要的顺序相同(在本例中,按名称)。
答案 2 :(得分:5)
在数据库中基本上有两种分页方式(我假设您使用的是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中执行分页有一种完全不同的,快得多但却鲜为人知的方法。这通常被称为“寻找方法”,如this blog post here中所述。
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页时)。
例如,这是在网络应用程序中延迟加载更多数据时实现分页的最佳方式。
注意,“搜索方法”也称为keyset paging。
答案 3 :(得分:3)
LINQ结合lambda表达式和.Net 3.5中的匿名类非常简化了这类事情。
查询数据库:
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;
默认情况下,查询的结果是延迟加载的,因此在实际需要数据之前,您不会与数据库通信。 LINQ in .Net还通过保留您所做的任何更改的datacontext,并且只更新您更改的字段,大大简化了更新。
答案 4 :(得分:1)
答案 5 :(得分:1)
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
答案 6 :(得分:1)
我在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
答案 7 :(得分:0)
讨论了这个Here
该技术从78,000行的150,000行数据库中获取页码100,000,
使用优化器知识和SET ROWCOUNT,请求页面中的第一个EmployeeID存储在一个起始点的局部变量中。接下来,将SET ROWCOUNT设置为@maximumRows中请求的最大记录数。这允许以更有效的方式分页结果集。使用此方法还可以利用表上预先存在的索引,因为它直接转到基表而不是本地创建的表。
恐怕我无法判断它是否比当前接受的答案更好。