欢迎光临
我们一直在努力

数据库查询结果的动态排序-数据库专栏,SQL Server

建站超值云服务器,限时71元/月

在公共新闻组中,一个经常出现的问题是“怎样才能根据传递给存储过程的参数返回一个排序的输出?”。在一些高水平专家的帮助之下,我整理出了这个问题的几种解决方案。

一、用if…else执行预先编写好的查询

  对于大多数人来说,首先想到的做法也许是:通过if…else语句,执行几个预先编写好的查询中的一个。例如,假设要从northwind数据库查询得到一个货主(shipper)的排序列表,发出调用的代码以存储过程参数的形式指定一个列,存储过程根据这个列排序输出结果。listing 1显示了这种存储过程的一个可能的实现(getsortedshippers存储过程)。

【listing 1: 用if…else执行多个预先编写好的查询中的一个】

create proc getsortedshippers

@ordseq as int

as

if @ordseq = 1

select * from shippers order by shipperid

else if @ordseq = 2

select * from shippers order by companyname

else if @ordseq = 3

select * from shippers order by phone

  这种方法的优点是代码很简单、很容易理解,sql server的查询优化器能够为每一个select查询创建一个查询优化计划,确保代码具有最优的性能。这种方法最主要的缺点是,如果查询的要求发生了改变,你必须修改多个独立的select查询——在这里是三个。

二、用列名字作为参数

  另外一个选择是让查询以参数的形式接收一个列名字。listing 2显示了修改后的getsortedshippers存储过程。case表达式根据接收到的参数,确定sql server在order by子句中使用哪一个列值。注意,order by子句中的表达式并未在select清单中出现。在ansi sql-92标准中,order by子句中不允许出现没有在select清单中指定的表达式,但ansi sql-99标准允许。sql server一直允许这种用法。

【listing 2:用列名字作为参数,第一次尝试】

create proc getsortedshippers

@colname as sysname

as

select *

from shippers

order by

case @colname

when shipperid then shipperid

when companyname then companyname

when phone then phone

else null

end

  现在,我们来试一下新的存储过程,以参数的形式指定shipperid列:

exec getsortedshippers shipperid

  此时一切正常。但是,当我们视图把companyname列作为参数调用存储过程时,它不再有效:

exec getsortedshippers companyname

  仔细看一下错误信息:

server: msg 245, level 16, state 1, procedure getsortedshippers, line 5

syntax error converting the nvarchar value speedy

express to a column of data type int.

  它显示出,sql server试图把“speedy express”(nvarchar数据类型)转换成一个整数值——当然,这个操作是不可能成功的。出现错误的原因在于,按照“数据类型优先级”规则,case表示式中最高优先级的数据类型决定了表达式返回值的数据类型。“数据类型优先级”规则可以在sql server books online(bol)找到,它规定了int数据类型的优先级要比nvarchar数据类型高。前面的代码要求sql server按照companyname排序输出,companyname是nvarchar数据类型。这个case表达式的返回值可能是shipperid(int类型),可能是companyname(nvarchar类型),或phone(nvarchar类型)。由于int类型具有较高的优先级,因此case表达式返回值的数据类型应该是int。

为了避免出现这种转换错误,我们可以尝试把shipperid转换成varchar数据类型。采用这种方法之后,nvarchar将作为最高优先级的数据类型被返回。listing 3显示了修改后的getsortedshippers存储过程。

【listing 3:用列名字作为参数,第二次尝试】

alter proc getsortedshippers

@colname as sysname

as

select *

from shippers

order by

case @colname

when shipperid

then cast(shipperid as varchar(11))

when companyname

then companyname

when phone

then phone

else null

end

  现在,假设我们再把三个列名字中的任意一个作为参数调用存储过程,输出结果看起来正确。看起来就象指定的列正确地为查询输出提供了排序标准。但这个表只有三个货主,它们的id分别是1、2、3。假设我们把更多的货主加入到表,如listing 4所示(shipperid列有identity属性,sql server自动为该列生成值)。

【listing 4:向shippers表插入一些记录】

insert into shippers values(shipper4, (111) 222-9999)

insert into shippers values(shipper5, (111) 222-8888)

insert into shippers values(shipper6, (111) 222-7777)

insert into shippers values(shipper7, (111) 222-6666)

insert into shippers values(shipper8, (111) 222-5555)

insert into shippers values(shipper9, (111) 222-4444)

insert into shippers values(shipper10, (111) 222-3333)

  现在调用存储过程,指定shipperid作为排序列:

exec getsortedshippers shipperid

  表一显示了存储过程的输出。shipperid等于10的记录位置错误,因为这个存储过程的排序输出是字符排序,而不是整数排序。按照字符排序时,10排列在2的前面,因为10的开始字符是1。

表一:记录排序错误的查询结果

shipperid companyname phone

1 speedy express (503) 555-9831

10 shipper10 (111) 222-3333

2 united package (503) 555-3199

3 federal shipping (503) 555-9931

4 shipper4 (111) 222-9999

5 shipper5 (111) 222-8888

6 shipper6 (111) 222-7777

7 shipper7 (111) 222-6666

8 shipper8 (111) 222-5555

9 shipper9 (111) 222-4444

为了解决这个问题,我们可以用前置的0补足shipperid值,使得shipperid值都有同样的长度。按照这种方法,基于字符的排序具有和整数排序同样的输出结果。修改后的存储过程如listing 5所示。十个0被置于shipperid的绝对值之前,而在结果中,代码只是使用最右边的10个字符。sign函数确定在正数的前面加上加号(+)前缀,还是在负数的前面加上负号(-)前缀。按照这种方法,输出结果总是有11个字符,包含一个“+”或“-”字符、前导的字符0以及shipperid的绝对值。

【listing 5:用列名字作为参数,第三次尝试】

alter proc getsortedshippers

@colname as sysname

as

select *

from shippers

order by

case @colname

when shipperid then case sign(shipperid)

when -1 then –

when 0 then +

when 1 then +

else null

end +

right(replicate(0, 10) +

cast(abs(shipperid) as varchar(10)), 10)

when companyname then companyname

when phone then phone

else null

end

  如果shipperid的值都是正数,加上符号前缀就没有必要,但为了让方案适用于尽可能多的范围,本例加上了符号前缀。排序时“-”在“+”的前面,所以它可以用于正、负数混杂排序的情况。

  现在,如果我们用任意三个列名字之一作为参数调用存储过程,存储过程都能够正确地返回结果。richard romley提出了一种巧妙的处理方法,如listing 6所示。它不再要求我们搞清楚可能涉及的列数据类型。这种方法把order by子句分成三个独立的case表达式,每一个表达式处理一个不同的列,避免了由于case只返回一种特定数据类型的能力而导致的问题。

【listing 6:用列名字作为参数,romley提出的方法】

alter proc getsortedshippers

@colname as sysname

as

select *

from shippers

order by

case @colname when shipperid

then shipperid else null end,

case @colname when companyname

then companyname else null end,

case @colname when phone

then phone else null end

  按照这种方法编写代码,sql server能够为每一个case表达式返回恰当的数据类型,而且无需进行数据类型转换。但应该注意的是,只有当指定的列不需要进行计算时,索引才能够优化排序操作。

三、用列号作为参数

  就象第一个方案所显示地那样,你也许更喜欢用列的编号作为参数,而不是使用列的名字(列的编号即一个代表你想要作为排序依据的列的数字)。这种方法的基本思想与使用列名字作为参数的思想一样:case表达式根据指定的列号确定使用哪一个列进行排序。listing 7显示了修改后的getsortedshippers存储过程。

【listing 7:用列号作为参数】

alter proc getsortedshippers

@colnumber as int

as

select *

from shippers

order by

case @colnumber

when 1 then case sign(shipperid)

when -1 then –

when 0 then +

when 1 then +

else null

end +

right(replicate(0, 10) +

cast(abs(shipperid) as varchar(10)), 10)

when 2 then companyname

when 3 then phone

else null

end

  当然,在这里你也可以使用richard的方法,避免order by子句中列数据类型带来的问题。如果要根据shipperid排序输出,你可以按照下面的方式调用修改后的getsortedshippers存储过程:

exec getsortedshippers 1

四、动态执行

  使用动态执行技术,我们能够更轻松地编写出getsortedshippers存储过程。使用这种方法时,我们只需动态地构造出select语句,然后用exec()命令执行这个select语句。假设传递给存储过程的参数是列的名字,存储过程可以大大缩短:

alter proc getsortedshippers

@colname as sysname

as

exec(select * from shippers order by +

@colname)

  在sql server 2000和7.0中,你可以用系统存储过程sp_executesql替代exec()命令。bol说明了使用sp_executesql比使用exec()命令更有利的地方。一般地,如果满足以下三个条件,你能够在不授予存储过程所涉及对象权限的情况下,授予执行存储过程的权限:首先,只使用data manipulation language(dml)语言(即select,insert,update,delete);其次,所有被引用的对象都有与存储过程同样的所有者;第三,没有使用动态命令。

  上面的存储过程不能满足第三个条件。在这种情况下,你必须为所有需要使用存储过程的用户和组显式地授予shippers表的select权限。如果这一点可以接受的话,一切不存在问题。类似地,你可以修改存储过程,使它接受一个列号参数,如listing 8所示。

【listing 8:用列号作为参数,动态执行(代码较长的方法)】

alter proc getsortedshippers

@colnumber as int

as

declare @cmd as varchar(8000)

set @cmd = select * from shippers order by +

case @colnumber

when 1 then shipperid

when 2 then companyname

when 3 then phone

else null

end

exec(@cmd)

  注意,当你使用了函数时,你应该在一个变量而不是exec()命令内构造select语句。此时,case表达式动态地确定使用哪一个列。还有一种更简短的格式,t-sql允许在order by子句中指定select清单中列的位置,如listing 9所示。这种格式遵从了sql-92标准,但ansi sql-99标准不支持这种格式,所以最好不要使用这种格式。

【listing 9:列号作为参数,动态执行(代码较短的方法)】

alter proc getsortedshippers

@colnumber as int

as

declare @cmd as varchar(8000)

set @cmd = select * from shippers order by + cast(@colnumber as varchar(4))

exec(@cmd)

五、用户定义函数

  如果你使用的是sql server 2000,想要编写一个用户定义的函数(udf),这个用户定义函数接受列的名字或编号为参数、返回排序的结果集,listing 10显示了大多数程序员当成第一选择的方法。

【listing 10:列名字作为参数,使用udf】

create function ufn_getsortedshippers

(

@colname as sysname

)

returns table

as

return

select *

from shippers

order by

case @colname

when shipperid then case sign(shipperid)

when -1 then –

when 0 then +

when 1 then +

else null

end +

right(replicate(0, 10) +

cast(abs(shipperid) as

varchar(10)), 10)

when companyname then companyname

when phone then phone

else null

end

  但是,sql server不接受这个函数,它将返回如下错误信息:

server: msg 1033, level 15, state 1, procedure ufn_getsortedshippers,

line 24

the order by clause is invalid in views, inline functions, and

subqueries, unless top is also specified.

  注意错误信息中的“unless”。sql server 2000不允许在视图、嵌入式udf、子查询中出现order by子句,因为它们都应该返回一个表,表不能指定行的次序。然而,如果使用了top关键词,order by子句将帮助确定查询所返回的行。因此,如果指定了top,你还可以同时指定order by。由于在带有top的udf中允许使用order by子句,你可以使用一个技巧:把“select *”替换成“select top 100 percent *”。这样,你就能够成功地构造出一个接受列名字或编号为参数、返回排序结果的函数。

  新构造的函数可以按照如下方式调用:

select * from ufn_getsortedshippers(shipperid)

  现在,你已经了解了几种用参数确定查询输出中记录次序的方法。在编写那些允许用户指定查询结果排序标准的列的应用程序时,你可以使用本文介绍的各种技术,用列名字或编号作为参数,构造出使用case表达式和动态执行能力的各种方案。

赞(0)
版权申明:本站文章部分自网络,如有侵权,请联系:west999com@outlook.com 特别注意:本站所有转载文章言论不代表本站观点! 本站所提供的图片等素材,版权归原作者所有,如需使用,请与原作者联系。未经允许不得转载:IDC资讯中心 » 数据库查询结果的动态排序-数据库专栏,SQL Server
分享到: 更多 (0)

相关推荐

  • 暂无文章