如何从SQL返回结果页面?

时间:2008-08-13 18:38:03

标签: .net sql linq pagination

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

在不将整个表格带到客户端然后过滤客户端上的数据的情况下,实现此模式的好算法是什么。如何将您想要显示的记录带给用户?

LINQ是否简化了解决方案?

8 个答案:

答案 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):

使用OFFSET

其他人已经解释了如何使用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中执行分页有一种完全不同的,快得多但却鲜为人知的方法。这通常被称为“寻找方法”,如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)

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

检查出来。

对于DB:Pagination In SQL Server 2005

答案 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中请求的最大记录数。这允许以更有效的方式分页结果集。使用此方法还可以利用表上预先存在的索引,因为它直接转到基表而不是本地创建的表。

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