sql分頁的幾種寫法

一直想整理下關於sql分頁的幾種方法,今天終於有時間整理下了。閒話少說直接上sql,先創建一個測試庫,測試表以及測試數據,sql語句如下:

CREATE DATABASE DBTEST
GO 
USE DBTEST 
GO
--創建測試表
create table pagetest
(
id int identity(1,1) not null,
col01 int null,
col02 nvarchar(50) null,
col03 datetime null
)

--100萬記錄集 耗時 3'46''
declare @i int
set @i=0
while(@i<1000000)
begin
    insert into pagetest select cast(floor(rand()*1000000) as int),left(newid(),10),getdate()
    set @i=@i+1
END

在pagetest表裏面插入了1000000條記錄,共耗時3'46'',當然這跟電腦配置有關。主要說以下幾種方法:

爲了方便定義兩個參數,一個是頁的大小@PageSize,一個是@PageIndex頁的索引。

DECLARE @pageIndex INT, -----頁索引   

                  @pageSize INT  -----頁大小

我們設置每頁大小爲500條,取@pageIndex=198 即199頁數據,

SET @pageSize=500

SET @pageIndex=198

下面直接上幾種常用的sql分頁的方法:爲了測試每種方法的執行時間,定義一個開始時間和結束時間

DECLARE @begin_date DATETIME;
DECLARE @end_date DATETIME;
SELECT @begin_date = GETDATE();

DECLARE @pageIndex INT, -----頁索引
        @pageSize INT   -----頁大小

SET @pageSize = 500;
SET @pageIndex = 198;



----寫法一 NOT IN /Top 
SET @begin_date = GETDATE();

SELECT TOP (@pageSize)
    *
FROM dbo.pagetest
WHERE id NOT IN
      (
          SELECT TOP (@pageSize * @pageIndex) id FROM dbo.pagetest ORDER BY id ASC
      )
ORDER BY id ASC;

SELECT @end_date = GETDATE();
SELECT 'NOT IN /Top' AS 'Type',
    DATEDIFF(ms, @begin_date, @end_date) AS '毫秒';

----寫法二 NOT EXIST
SET @begin_date = GETDATE();

SELECT TOP (@pageSize)
    *
FROM dbo.pagetest p
WHERE NOT EXISTS
(
    SELECT 1
    FROM
    (
        SELECT TOP (@pageSize * @pageIndex)
            *
        FROM dbo.pagetest p
        ORDER BY p.id ASC
    ) a
    WHERE p.id = a.id
)
ORDER BY id ASC;

SELECT @end_date = GETDATE();
SELECT 'NOT EXIST' AS 'Type',
    DATEDIFF(ms, @begin_date, @end_date) AS '毫秒';

----寫法三 MAX/TOP
SET @begin_date = GETDATE();

SELECT TOP (@pageSize)
    *
FROM dbo.pagetest
WHERE id >
      (
          SELECT MAX(id)
          FROM
          (
              SELECT TOP (@pageSize * @pageIndex)
                  *
              FROM dbo.pagetest
              ORDER BY id ASC
          ) a
      )
ORDER BY id ASC;

SELECT @end_date = GETDATE();
SELECT 'MAX/TOP' AS 'Type',
    DATEDIFF(ms, @begin_date, @end_date) AS '毫秒';

----寫法四 ROW NUMBER()
SET @begin_date = GETDATE();

SELECT TOP (@pageSize)
    a.*
FROM
(
    SELECT ROW_NUMBER() OVER (ORDER BY id ASC) rowNum,
        *
    FROM dbo.pagetest
) a
WHERE a.rowNum > (@pageSize * @pageIndex)
ORDER BY a.id;

SELECT @end_date = GETDATE();
SELECT 'ROW NUMBER()一' AS 'Type',
    DATEDIFF(ms, @begin_date, @end_date) AS '毫秒';

SET @begin_date = GETDATE();

SELECT a.*
FROM
(
    SELECT ROW_NUMBER() OVER (ORDER BY id ASC) rowNum,
        *
    FROM dbo.pagetest
) a
WHERE a.rowNum > (@pageSize * @pageIndex)
      AND a.rowNum < (@pageSize * @pageIndex + @pageSize + 1)
ORDER BY a.id;

SELECT @end_date = GETDATE();
SELECT 'ROW NUMBER()二' AS 'Type',
    DATEDIFF(ms, @begin_date, @end_date) AS '毫秒';

SET @begin_date = GETDATE();

SELECT a.*
FROM
(
    SELECT ROW_NUMBER() OVER (ORDER BY id ASC) rowNum,
        *
    FROM dbo.pagetest
) a
WHERE a.rowNum
BETWEEN (@pageSize * @pageIndex + 1) AND (@pageSize * @pageIndex + @pageSize)
ORDER BY a.id;

SELECT @end_date = GETDATE();
SELECT 'ROW NUMBER()三' AS 'Type',
    DATEDIFF(ms, @begin_date, @end_date) AS '毫秒';

----寫法五 ROWNUMBER()變體
SET @begin_date = GETDATE();

SELECT *
FROM
(
    SELECT ROW_NUMBER() OVER (ORDER BY tempColumn) AS rowNum,
        *
    FROM
    (
        SELECT TOP (@pageIndex * @pageSize + @pageSize)
            tempColumn = 0,
            *
        FROM pagetest
        WHERE 1 = 1
        ORDER BY id ASC
    ) a
) b
WHERE b.rowNum > (@pageIndex * @pageSize);

SELECT @end_date = GETDATE();
SELECT 'ROW NUMBER()變體' AS 'Type',
    DATEDIFF(ms, @begin_date, @end_date) AS '毫秒';

----寫法六 OFFSET mssql2012後支持
SET @begin_date = GETDATE();
SELECT *
FROM dbo.pagetest
ORDER BY id ASC 
OFFSET @pageIndex * @pageSize ROWS FETCH NEXT @pageSize ROWS ONLY;

SELECT @end_date = GETDATE();
SELECT 'OFFSET' AS 'Type',
    DATEDIFF(ms, @begin_date, @end_date) AS '毫秒';
大體看一下幾種方法的執行速度,









執行多次後大體可以看出Max Top,RowNum()以及offset的執行效率比較高,RowNum()應該是在sql2005後就支持了,而offset是從sql2012開始支持。將以上兩種常用的方法封裝成存儲過程:一個是RowNum() 一個是MaxTop

USE [DBTEST]
GO

/****** Object:  StoredProcedure [dbo].[Proc_SqlPageByRownumber]    Script Date: 2017/4/21 16:24:52 ******/
SET ANSI_NULLS ON
GO

SET QUOTED_IDENTIFIER ON
GO

-- =============================================
-- Author:		Allen
-- Create date: 2017-04-19
-- Description:	根據RowNumber()分頁
-- =============================================
CREATE PROCEDURE [dbo].[Proc_SqlPageByRownumber]
(
    @tbName VARCHAR(255),             --表名
    @tbGetFields VARCHAR(1000) = '*', --返回字段
    @OrderfldName VARCHAR(255),       --排序的字段名
    @PageSize INT = 20,               --頁尺寸
    @PageIndex INT = 1,               --頁碼
    @OrderType BIT = 0,               --0升序,非0降序
    @strWhere VARCHAR(1000) = '',      --查詢條件
    @TotalCount INT OUTPUT            --返回總記錄數
) 
AS
    DECLARE @strSql VARCHAR(5000); --主語句
    DECLARE @strSqlCount NVARCHAR(500); --查詢記錄總數主語句
    DECLARE @strOrder VARCHAR(300); -- 排序類型
    BEGIN
        --------------總記錄數---------------
        IF ISNULL(@strWhere, '') <> ''
            SET @strSqlCount = 'Select @TotalCout=count(1) from  ' + @tbName + ' where 1=1 ' + @strWhere;
        ELSE
            SET @strSqlCount = 'Select @TotalCout=count(1) from  ' + @tbName;

        exec sp_executesql @strSqlCount,N'@TotalCout int output',@TotalCount output
        --------------分頁------------
        IF @PageIndex <= 0
            SET @PageIndex = 1;

        IF (@OrderType <> 0)
            SET @strOrder = ' ORDER BY ' + @OrderfldName + ' DESC ';
        ELSE
            SET @strOrder = ' ORDER BY ' + @OrderfldName + ' ASC ';

        SET @strSql
            = 'SELECT * FROM 
    (SELECT ROW_NUMBER() OVER(' + @strOrder + ') RowNo,' + @tbGetFields + ' FROM ' + @tbName + ' WHERE 1=1 '
              + @strWhere + ' ) tb 
    WHERE tb.RowNo BETWEEN ' + STR((@PageIndex - 1) * @PageSize + 1) + ' AND ' + STR(@PageIndex * @PageSize);

        EXEC (@strSql);

    SELECT @TotalCount


    END;

GO



USE [DBTEST]
GO

/****** Object:  StoredProcedure [dbo].[ProcSqlPageByMaxTop]    Script Date: 2017/4/21 16:27:11 ******/
SET ANSI_NULLS ON
GO

SET QUOTED_IDENTIFIER ON
GO

-- =============================================
-- Author:		Allen
-- Create date: 2017-04-19
-- Description:	MaxTop
-- =============================================
CREATE PROCEDURE [dbo].[ProcSqlPageByMaxTop]
    @tbName VARCHAR(255),    --表名
    @tbFields VARCHAR(1000), --返回字段
    @PageSize INT,           --頁尺寸
    @PageIndex INT,          --頁碼
    @strWhere VARCHAR(1000), --查詢條件
    @StrOrder VARCHAR(255),  --排序條件
    @Total INT OUTPUT        --返回總記錄數
AS
    DECLARE @strSql VARCHAR(5000); --主語句
    DECLARE @strSqlCount NVARCHAR(500); --查詢記錄總數主語句
    BEGIN
        --------------總記錄數---------------
        IF @strWhere != ''
        BEGIN
            SET @strSqlCount = 'Select @TotalCout=count(*) from  ' + @tbName + ' where ' + @strWhere;
        END;
        ELSE
        BEGIN
            SET @strSqlCount = 'Select @TotalCout=count(*) from  ' + @tbName;
        END;
        --------------分頁------------
        IF @PageIndex <= 0
        BEGIN
            SET @PageIndex = 1;
        END;

        SET @strSql
            = 'select top ' + STR(@PageSize) + ' * from ' + @tbName + '
where id>(select max(id) from (select top ' + STR((@PageIndex - 1) * @PageSize) + ' id from ' + @tbName + ''
              + @StrOrder + ')a)
'                     + @StrOrder + '';

        EXEC sp_executesql @strSqlCount, N'@TotalCout int output', @Total OUTPUT;
        EXEC (@strSql);
    END;

GO
ish
以上也只是對單表的查詢,注意在sql優化時要加上主鍵和索引,查詢效率會提高,關於如何創建索引,在哪些字段時創建索引在下一篇博客中說明。



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章