如何在 table 值函数中使用动态 'order by'

How to use dynamic 'order by' in a table-valued function

我使用 table 值函数作为参数化视图。几乎一切正常,除了 'order by' part.I 将原始函数更改为这个短函数。

函数:

ALTER FUNCTION [dbo].[fn_Get_NormalOrders]
(   
    @minimalLevel int,
    @recordStart int,
    @recordsEnd int,
    @orderBy varchar(30)
)
RETURNS TABLE 
AS
RETURN 
(
    select * 
        from
        (
            select 
                ROW_NUMBER() over (order by @orderBy) as row
                ,d.nameModel
                ,t.idToner
            from toner t
                inner join vwWebDevice d on d.idDevice = t.idDevice 
                    and d.statusDevice not like '%stale%'
                    and isnull(d.deleted,0) = 0
                inner join groups g on g.idGroup = d.idGroup
            where 
                t.currentLevel <= @minimalLevel
        ) as x
    where x.row between @recordStart and @recordsEnd
)

我通过将此查询设为 varchar 并执行它找到了解决方案,但我 return 如何获得结果?当我现在使用它时,结果总是一样的。

我很确定你不能轻易做到这一点。一种方法是 case:

. . .
order by (case when @OrderBy = 'col1' then col1 end),
         (case when @OrderBy = 'col2' then col2 end),
         . . .

(请注意,每种可能性都有一个单独的 case。这可以防止用于排序的值发生类型冲突。)

遗憾的是,参数不能替换列名或表达式。那将是最简单的解决方案。

另一种不起作用的可能性是动态 SQL(您提到的 "varchar" 方法)。不幸的是,SQL 服务器不允许动态 SQL。

有一个非常复杂的方法,其中函数使用 xp_cmdshell 来绕过这些限制。这可能不值得为此付出努力。

exec 的限制是 phrased by specifying that EXECUTE can only be used for extended stored procedures:

EXECUTE statements calling extended stored procedures.

您必须覆盖所有硬编码的列名:

ALTER FUNCTION [dbo].[fn_Get_NormalOrders]
(   
    @minimalLevel int,
    @recordStart int,
    @recordsEnd int,
    @orderBy varchar(30)
)
RETURNS TABLE 
AS
RETURN 
(
    select * 
        from
        (
            select case @orderBy
                    when 'Column1' then ROW_NUMBER() over (order by Column1) 
                    when 'Column2' then ROW_NUMBER() over (order by Column2) 
                    when 'Column3' then ROW_NUMBER() over (order by Column3) 
                    when 'Column4' then ROW_NUMBER() over (order by Column4)
                end as row
                ,d.nameModel
                ,t.idToner
            from toner t
                inner join vwWebDevice d on d.idDevice = t.idDevice 
                    and d.statusDevice not like '%stale%'
                    and isnull(d.deleted,0) = 0
                inner join groups g on g.idGroup = d.idGroup
            where 
                t.currentLevel <= @minimalLevel
        ) as x
    where x.row between @recordStart and @recordsEnd
)