哪个是在SQL Server上执行分页的最佳方法?

时间:2013-01-15 14:00:19

标签: c# sql visual-studio-2010 sql-server-2008 pagination

我有一个包含超过 200万记录的数据库,我需要在我的网络应用程序上执行分页,该应用程序必须在DataGrid中每页有10条记录。< / p>

我已经尝试使用ROW_NUMBER(),但这种方式会选择所有200万条记录,然后只获得10条记录。我也尝试使用TOP 10,但我必须保存第一个和最后一个id来控制页面。我已经读过,使用DataAdapter.Fill()将选择所有内容,然后获取我需要的10条记录。

哪种方式最好?我应该使用DataAdapter.Fill()吗?或者使用SQL Server的函数ROW_NUMBER()?或者尝试使用TOP 10

3 个答案:

答案 0 :(得分:5)

ALTER PROCEDURE [dbo].[SP_tblTest_SelectSpecificRecordsWithCTE]
    @FromRow int = 1000000,
    @PgSize int = 10
AS
BEGIN
    ;WITH RecordsRN AS
    (
        select ID, colValue, ROW_NUMBER() over(order by colvalue) as Num from tblTest
    )
    SELECT ID Value, colValue Text FROM RecordsRN WHERE Num between @FromRow AND (@FromRow+@PgSize-1)
END

这是我用于分页的查询。使用它,你将在4-5秒内得到你想要的10条记录。我在3秒钟内获得10条记录,我的数据库中的总记录数为1000万,不使用前10名,每次只会带来相同的10条记录。在我的情况下,我在会话中维护页面大小和起始行号(@FromRow),我将这两个值传递给下面给定的存储过程并获得结果。 如果你使用的是SQL 2012,你可能还想使用OFFSET和Fetch接下来的10行。在谷歌上搜索有关OFFSET关键字的内容,您将在顶部看到所需的结果。

感谢

答案 1 :(得分:2)

使用ROW_NUMBER()并实现一个静态实用程序函数(在我的代码中为GetPaginatedSQL),它会自动将原始SQL查询包装到有限/分页中。

这是我使用的那个:

namespace Persistence.Utils
{
    public class SQLUtils
    {
        /// <summary>
        /// Builds a paginated/limited query from a SELECT SQL.
        /// </summary>
        /// <param name="startRow">Start row</param>
        /// <param name="numberOfRows">Number/quatity of rows to be expected</param>
        /// <param name="sql">Original SQL (without its ordering clause)</param>
        /// <param name="orderingClause">MANDATORY: ordering clause (including ORDER BY keywords)</param>
        /// <returns>Paginated SQL ready to be executed.</returns>
        /// <remarks>SELECT keyword of original SQL must be placed exactly at the beginning of the SQL.</remarks>
        public static string GetPaginatedSQL(int startRow, int numberOfRows, string sql, string orderingClause)
        {
            // Ordering clause is mandatory!
            if (String.IsNullOrEmpty(orderingClause))
                throw new ArgumentNullException("orderingClause");

            // numberOfRows here is checked of disable building paginated/limited query
            // in case is not greater than 0. In this case we simply return the
            // query with its ordering clause appended to it. 
            // If ordering is not spe
            if (numberOfRows <= 0)
            {
                return String.Format("{0} {1}", sql, orderingClause);
            }
            // Extract the SELECT from the beginning.
            String partialSQL = sql.Remove(0, "SELECT ".Length);

            // Build the limited query...
            return String.Format(
                "SELECT * FROM ( SELECT ROW_NUMBER() OVER ({0}) AS rn, {1} ) AS SUB WHERE rn > {2} AND rn <= {3}",
                orderingClause,
                partialSQL,
                startRow.ToString(),
                (startRow + numberOfRows).ToString()
            );
        }
    }
}

上面的功能可能会有所改进,但是是初步实现。

然后,在你的DAO中,你应该只是做这样的事情:

using (var conn = new SqlConnection(CONNECTION_STRING))
{
    using (var cmd = conn.CreateCommand())
    {
        String SQL = "SELECT * FROM MILLIONS_RECORDS_TABLE";
        String SQLOrderBy = "ORDER BY DATE ASC "; //GetOrderByClause(Object someInputParams);
        String limitedSQL = GetPaginatedSQL(0, 50, SQL, SQLOrderBy);

        DataSet ds = new DataSet();
        SqlDataAdapter adapter = new SqlDataAdapter();

        cmd.CommandText = limitedSQL;

        // Add named parameters here to the command if needed...

        adapter.SelectCommand = cmd;
        adapter.Fill(ds);

        // Process the dataset...
    }
    conn.Close();
}

希望它有所帮助。

答案 2 :(得分:1)

我使用以下模式(自动)生成分页子查询:

select top (@takeN) <your-column-list>
from (
    select qSub2.*, _row=row_number() over (order by SomeColumn Asc, SomethingElse Desc)
    from (
        select top (@takeN + @skipN) <your-column-list> 
        from ( 
            select <your-subquery-here>
        ) as qSub1 
        order by SomeColumn Asc, SomethingElse Desc
    ) as qSub2
) qSub3
where _row > @skipN
order by _row

关于这种模式的说明:

  • 从概念上提交查询跳过@skipN行,然后获取下一行@takeN行。
  • 如果您不关心结果中的额外列_row,则可以将<your-column-list>替换为*;我使用显式列列表,因为它允许我在运行时对列集进行子集化,这可能是有用的,例如仅查找主要关键列等。
  • 您的order by条款应该相同; sql server的optmizer通常足够聪明,可以理解。复制是用于截断结果的top子句的副作用;对于未排序的子查询,top不合法。 top可以帮助查询优化器理解这个查询可能返回几行。
  • 使用@takeN@skipN而不是页码+基于大小的参数的原因相当轻微。首先,它在查询中更灵活,更简单一些,其次,它更好地发挥了sql server的优势:数据库首先在优化这类查询方面并不是特别出色,希望是这样的外部简单顶部子句使优化器理解可能的最大行数变得微不足道。一般来说,我试图避免在sql中进行计算我可以在代码中做得同样好,因为它往往会混淆优化器(尽管在@ pagecount * @ pagesize实验的特定情况下已经表明它不是一个大问题)

请注意,sql server 2012正好支持一个新的offset...fetch clause,这种情况要简单得多。