讨论:Sql server 中快速的分页查询语句
有千万条记录的表
每次返回的记录数也在几十万,上万页
假定就在一张表中,使用一个语句(select)查询出某页
SELECT TOP 50 xxx from
(select top total-(page-1)*50 xxx from theTable where ... order by xxx desc)tmp order by xxx
如上为一种查询方法,请赐教各种查询语句
[解决办法]
SELECT * FROM (
SELECT TOP 50* FROM (
SELECT TOP 50*(Page+1) * FROM Table
ORDER BY PrimaryKey ASC
) TableA ORDER BY PrimaryKey DESC
) TableB ORDER BY PrimaryKey ASC
[解决办法]
SELECT TOP 50* FROM Table
WHERE PrimaryKey NOT IN (
SELECT TOP 50*Page PrimaryKey FROM Table
ORDER BY PrimaryKey ASC
) ORDER BY PrimaryKey ASC
[解决办法]
Page是变量?
需要用动态SQL语句。
[解决办法]
动态sql语句基本语法
1 :普通SQL语句可以用Exec执行
eg: Select * from tableName
Exec( 'select * from tableName ')
Exec sp_executesql N 'select * from tableName ' -- 请注意字符串前一定要加N
2:字段名,表名,数据库名之类作为变量时,必须用动态SQL
eg:
declare @fname varchar(20)
set @fname = 'FiledName '
Select @fname from tableName -- 错误,不会提示错误,但结果为固定值FiledName,并非所要。
Exec( 'select ' + @fname + ' from tableName ') -- 请注意 加号前后的 单引号的边上加空格
当然将字符串改成变量的形式也可
declare @fname varchar(20)
set @fname = 'FiledName ' --设置字段名
declare @s varchar(1000)
set @s = 'select ' + @fname + ' from tableName '
Exec(@s) -- 成功
exec sp_executesql @s -- 此句会报错
declare @s Nvarchar(1000) -- 注意此处改为nvarchar(1000)
set @s = 'select ' + @fname + ' from tableName '
Exec(@s) -- 成功
exec sp_executesql @s -- 此句正确
3. 输出参数
declare @num int,
@sqls nvarchar(4000)
set @sqls= 'select count(*) from tableName '
exec(@sqls)
--如何将exec执行结果放入变量中?
declare @num int,
@sqls nvarchar(4000)
set @sqls= 'select @a=count(*) from tableName '
exec sp_executesql @sqls,N '@a int output ',@num output
select @num
[解决办法]
用邹老大的效率不错:
首先判断唯一标识\主键,没有时通过临时表实现:
/*--用存储过程实现的分页程序
显示指定表、视图、查询结果的第X页
对于表中主键或标识列的情况,直接从原表取数查询,其它情况使用临时表的方法
如果视图或查询结果中有主键,不推荐此方法
--邹建 2003.09(引用请保留此信息)--*/
/*--调用示例
exec p_show '地区资料 '
exec p_show '地区资料 ',5,3, '地区编号,地区名称,助记码 ', '地区编号 '
--*/
if exists (select * from dbo.sysobjects where id = object_id(N '[dbo].[p_show] ') and OBJECTPROPERTY(id, N 'IsProcedure ') = 1)
drop procedure [dbo].[p_show]
GO
CREATE Proc p_show
@QueryStr nvarchar(4000), --表名、视图名、查询语句
@PageSize int=10, --每页的大小(行数)
@PageCurrent int=1, --要显示的页
@FdShow nvarchar (4000)= ' ', --要显示的字段列表,如果查询结果有标识字段,需要指定此值,且不包含标识字段
@FdOrder nvarchar (1000)= ' ' --排序字段列表
as
declare @FdName nvarchar(250) --表中的主键或表、临时表中的标识列名
,@Id1 varchar(20),@Id2 varchar(20) --开始和结束的记录号
,@Obj_ID int --对象ID
--表中有复合主键的处理
declare @strfd nvarchar(2000) --复合主键列表
,@strjoin nvarchar(4000) --连接字段
,@strwhere nvarchar(2000) --查询条件
select @Obj_ID=object_id(@QueryStr)
,@FdShow=case isnull(@FdShow, ' ') when ' ' then ' * ' else ' '+@FdShow end
,@FdOrder=case isnull(@FdOrder, ' ') when ' ' then ' ' else ' order by '+@FdOrder end
,@QueryStr=case when @Obj_ID is not null then ' '+@QueryStr else ' ( '+@QueryStr+ ') a ' end
--如果显示第一页,可以直接用top来完成
if @PageCurrent=1
begin
select @Id1=cast(@PageSize as varchar(20))
exec( 'select top '+@Id1+@FdShow+ ' from '+@QueryStr+@FdOrder)
return
end
--如果是表,则检查表中是否有标识更或主键
if @Obj_ID is not null and objectproperty(@Obj_ID, 'IsTable ')=1
begin
select @Id1=cast(@PageSize as varchar(20))
,@Id2=cast((@PageCurrent-1)*@PageSize as varchar(20))
select @FdName=name from syscolumns where id=@Obj_ID and status=0x80
if @@rowcount=0 --如果表中无标识列,则检查表中是否有主键
begin
if not exists(select 1 from sysobjects where parent_obj=@Obj_ID and xtype= 'PK ')
goto lbusetemp --如果表中无主键,则用临时表处理
select @FdName=name from syscolumns where id=@Obj_ID and colid in(
select colid from sysindexkeys where @Obj_ID=id and indid in(
select indid from sysindexes where @Obj_ID=id and name in(
select name from sysobjects where xtype= 'PK ' and parent_obj=@Obj_ID
)))
if @@rowcount> 1 --检查表中的主键是否为复合主键
begin
select @strfd= ' ',@strjoin= ' ',@strwhere= ' '
select @strfd=@strfd+ ',[ '+name+ '] '
,@strjoin=@strjoin+ ' and a.[ '+name+ ']=b.[ '+name+ '] '
,@strwhere=@strwhere+ ' and b.[ '+name+ '] is null '
from syscolumns where id=@Obj_ID and colid in(
select colid from sysindexkeys where @Obj_ID=id and indid in(
select indid from sysindexes where @Obj_ID=id and name in(
select name from sysobjects where xtype= 'PK ' and parent_obj=@Obj_ID
)))
select @strfd=substring(@strfd,2,2000)
,@strjoin=substring(@strjoin,5,4000)
,@strwhere=substring(@strwhere,5,4000)
goto lbusepk
end
end
end
else
goto lbusetemp
/*--使用标识列或主键为单一字段的处理方法--*/
lbuseidentity:
exec( 'select top '+@Id1+@FdShow+ ' from '+@QueryStr
+ ' where '+@FdName+ ' not in(select top '
+@Id2+ ' '+@FdName+ ' from '+@QueryStr+@FdOrder
+ ') '+@FdOrder
)
return
/*--表中有复合主键的处理方法--*/
lbusepk:
exec( 'select '+@FdShow+ ' from(select top '+@Id1+ ' a.* from
(select top 100 percent * from '+@QueryStr+@FdOrder+ ') a
left join (select top '+@Id2+ ' '+@strfd+ '
from '+@QueryStr+@FdOrder+ ') b on '+@strjoin+ '
where '+@strwhere+ ') a '
)
return
/*--用临时表处理的方法--*/
lbusetemp:
select @FdName= '[ID_ '+cast(newid() as varchar(40))+ '] '
,@Id1=cast(@PageSize*(@PageCurrent-1) as varchar(20))
,@Id2=cast(@PageSize*@PageCurrent-1 as varchar(20))
exec( 'select '+@FdName+ '=identity(int,0,1), '+@FdShow+ '
into #tb from '+@QueryStr+@FdOrder+ '
select '+@FdShow+ ' from #tb where '+@FdName+ ' between '
+@Id1+ ' and '+@Id2
)
GO
[解决办法]
CREATE PROC sp_PageView
@tbname sysname, --要分页显示的表名
@FieldKey nvarchar(1000), --用于定位记录的主键(惟一键)字段,可以是逗号分隔的多个字段
@PageCurrent int=1, --要显示的页码
@PageSize int=10, --每页的大小(记录数)
@FieldShow nvarchar(1000)= ' ', --以逗号分隔的要显示的字段列表,如果不指定,则显示所有字段
@FieldOrder nvarchar(1000)= ' ', --以逗号分隔的排序字段列表,可以指定在字段后面指定DESC/ASC
用于指定排序顺序
@Where nvarchar(1000)= ' ', --查询条件
@PageCount int OUTPUT --总页数
AS
SET NOCOUNT ON
--检查对象是否有效
IF OBJECT_ID(@tbname) IS NULL
BEGIN
RAISERROR(N '对象 "%s "不存在 ',1,16,@tbname)
RETURN
END
IF OBJECTPROPERTY(OBJECT_ID(@tbname),N 'IsTable ')=0
AND OBJECTPROPERTY(OBJECT_ID(@tbname),N 'IsView ')=0
AND OBJECTPROPERTY(OBJECT_ID(@tbname),N 'IsTableFunction ')=0
BEGIN
RAISERROR(N ' "%s "不是表、视图或者表值函数 ',1,16,@tbname)
RETURN
END
--分页字段检查
IF ISNULL(@FieldKey,N ' ')= ' '
BEGIN
RAISERROR(N '分页处理需要主键(或者惟一键) ',1,16)
RETURN
END
--其他参数检查及规范
IF ISNULL(@PageCurrent,0) <1 SET @PageCurrent=1
IF ISNULL(@PageSize,0) <1 SET @PageSize=10
IF ISNULL(@FieldShow,N ' ')=N ' ' SET @FieldShow=N '* '
IF ISNULL(@FieldOrder,N ' ')=N ' '
SET @FieldOrder=N ' '
ELSE
SET @FieldOrder=N 'ORDER BY '+LTRIM(@FieldOrder)
IF ISNULL(@Where,N ' ')=N ' '
SET @Where=N ' '
ELSE
SET @Where=N 'WHERE ( '+@Where+N ') '
--如果@PageCount为NULL值,则计算总页数(这样设计可以只在第一次计算总页数,以后调用时,把总页数传回给存储过程,避免再次计算总页数,对于不想计算总页数的处理而言,可以给@PageCount赋值)
IF @PageCount IS NULL
BEGIN
DECLARE @sql nvarchar(4000)
SET @sql=N 'SELECT @PageCount=COUNT(*) '
+N ' FROM '+@tbname
+N ' '+@Where
EXEC sp_executesql @sql,N '@PageCount int OUTPUT ',@PageCount OUTPUT
SET @PageCount=(@PageCount+@PageSize-1)/@PageSize
END
--计算分页显示的TOPN值
DECLARE @TopN varchar(20),@TopN1 varchar(20)
SELECT @TopN=@PageSize,
@TopN1=(@PageCurrent-1)*@PageSize
--第一页直接显示
IF @PageCurrent=1
EXEC(N 'SELECT TOP '+@TopN
+N ' '+@FieldShow
+N ' FROM '+@tbname
+N ' '+@Where
+N ' '+@FieldOrder)
ELSE
BEGIN
--处理别名
IF @FieldShow=N '* '
SET @FieldShow=N 'a.* '
--生成主键(惟一键)处理条件
DECLARE @Where1 nvarchar(4000),@Where2 nvarchar(4000),
@s nvarchar(1000),@Field sysname
SELECT @Where1=N ' ',@Where2=N ' ',@s=@FieldKey
WHILE CHARINDEX(N ', ',@s)> 0
SELECT @Field=LEFT(@s,CHARINDEX(N ', ',@s)-1),
@s=STUFF(@s,1,CHARINDEX(N ', ',@s),N ' '),
@Where1=@Where1+N ' AND a. '+@Field+N '=b. '+@Field,
@Where2=@Where2+N ' AND b. '+@Field+N ' IS NULL ',
@Where=REPLACE(@Where,@Field,N 'a. '+@Field),
@FieldOrder=REPLACE(@FieldOrder,@Field,N 'a. '+@Field),
@FieldShow=REPLACE(@FieldShow,@Field,N 'a. '+@Field)
SELECT @Where=REPLACE(@Where,@s,N 'a. '+@s),
@FieldOrder=REPLACE(@FieldOrder,@s,N 'a. '+@s),
@FieldShow=REPLACE(@FieldShow,@s,N 'a. '+@s),
@Where1=STUFF(@Where1+N ' AND a. '+@s+N '=b. '+@s,1,5,N ' '),
@Where2=CASE
WHEN @Where= ' ' THEN N 'WHERE ( '
ELSE @Where+N ' AND ( '
END+N 'b. '+@s+N ' IS NULL '+@Where2+N ') '
--执行查询
EXEC(N 'SELECT TOP '+@TopN
+N ' '+@FieldShow
+N ' FROM '+@tbname
+N ' a LEFT JOIN(SELECT TOP '+@TopN1
+N ' '+@FieldKey
+N ' FROM '+@tbname
+N ' a '+@Where
+N ' '+@FieldOrder
+N ')b ON '+@Where1
+N ' '+@Where2
+N ' '+@FieldOrder)
END
[解决办法]
--舉例SQL2005中的
--表結構
CREATE TABLE [dbo].[T_ECS_Location](
[LocationNo] [varchar](20) COLLATE Chinese_Taiwan_Stroke_CI_AS NOT NULL,
[LocationName] [varchar](100) COLLATE Chinese_Taiwan_Stroke_CI_AS NOT NULL CONSTRAINT [DF_T_ECS_Location_LocationName] DEFAULT ( ' '),
[SiteID] [char](10) COLLATE Chinese_Taiwan_Stroke_CI_AS NOT NULL CONSTRAINT [DF_T_ECS_Location_SiteID] DEFAULT ( 'LH '),
[Enabled] [bit] NOT NULL CONSTRAINT [DF_T_ECS_Location_Enabled] DEFAULT ((1)),
[UpdatedBy] [char](8) COLLATE Chinese_Taiwan_Stroke_CI_AS NOT NULL CONSTRAINT [DF_Table_1_UpdatedBy] DEFAULT ( ' '),
[UpdateTime] [datetime] NOT NULL CONSTRAINT [DF_T_ECS_Location_UpdateTime] DEFAULT (getdate()),
CONSTRAINT [PK_T_ECS_Location] PRIMARY KEY CLUSTERED
(
[LocationNo] ASC
)WITH (PAD_INDEX = OFF, IGNORE_DUP_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]
--分頁
set ANSI_NULLS ON
set QUOTED_IDENTIFIER ON
GO
create Procedure [dbo].[LocationSel_sp]
@pLang char(10) = ' ',
@pTranType varchar(30) = null,
@pParam1 varchar(200) = ' ',
@pParam2 varchar(200) = ' '
AS
/*Declare*/
DECLARE @ErrStr nvarchar(200)
DECLARE @sqlstr nvarchar(4000)
IF @pTranType = 'PAGE '
BEGIN
DECLARE @pageindex int,@pagesize int
SET @pageindex=convert(int,@pParam1)
SET @pagesize=convert(int,@pParam2)
SET @SqlStr= '
SELECT TOP ' + str(@pagesize) + '
LocationNo,
LocationName,
SiteID,
Enabled,
UpdatedBy,
UpdateTime
FROM T_ECS_Location(nolock) '
IF @pageindex <> 1 --Not the first page
BEGIN
SET @SqlStr=@SqlStr + 'WHERE LocationNo> (SELECT MAX(LocationNo) FROM (SELECT TOP ' + str((@pageindex-1)*@pagesize)+
' LocationNo FROM T_ECS_Location ORDER BY LocationNo ASC) AS tmptable) '
END
SET @SqlStr=@SqlStr+ ' ORDER BY LocationNo ASC '
EXECUTE sp_executesql @sqlstr,N '@pageindex int,@pagesize int ',@pageindex,@pagesize
RETURN 0
END