可选订单在存储过程中?

时间:2014-01-22 18:14:10

标签: sql-server stored-procedures sql-order-by

我有一个复杂的存储过程。我想让用户选择要排序的列。他们应该能够根据需要选择尽可能多的列。

有没有办法在存储过程中实现这个?如何将列名传递给过程,然后在order by子句中反映这些?注意到将有可变数量的列。

我理解如何传递参数,只是不知道是否可以在存储过程中动态构建order by子句

2 个答案:

答案 0 :(得分:4)

动态构建ORDER BY非常简单。我假设您传递的参数如下:

@OrderByCol1 NVARCHAR(255),
@OrderByCol2 NVARCHAR(255),
...etc...

这些可能也可能不包括方向,例如N'MyColumn DESC'。那么你可以按如下方式构建它:

DECLARE @sql NVARCHAR(MAX);

SELECT @sql = N'SELECT ... 
  FROM ...  
  WHERE ... 
  ORDER BY NULL'
  + COALESCE(',' + @OrderByCol1, '')
  + COALESCE(',' + @OrderByCol2, '')
  ...etc...;

PRINT @sql;
--EXEC sp_executesql @sql;

由于每次答案甚至提到动态SQL时我们显然需要回顾整个SQL注入对话,我将添加一些示例。

如果它们只能按升序排序,那么只需将参数值包装在QUOTENAME()中即可阻止SQL注入。

  + COALESCE(',' + QUOTENAME(@OrderByCol1), '')
  + COALESCE(',' + QUOTENAME(@OrderByCol2), '')

否则,您还可以将参数拆分为空格(假设您的列名称不包含空格,它们不应该!),并验证左侧是否始终存在于sys.columns中。 / p>

IF @OrderByCol1 IS NOT NULL AND EXISTS
(
   SELECT 1 FROM sys.columns 
     WHERE [object_id] = OBJECT_ID('dbo.MyTable')
     AND name = LTRIM(LEFT(@OrderByCol1, CHARINDEX(' ', @OrderByCol1)))
)
BEGIN
  SET @sql += ',' + @OrderByCol1;
END

您可能还希望在那里进行检查,以防它们没有将任何参数传递给任何参数,或者只将值传递给参数#4等。上面就是这样。

使用TVP传递这些内容可能更好,然后您不必对他们可以选择的列数进行任意和人为的限制。以下是三列TVP的一个示例,它允许您按列传递一组顺序,指示它们应用的顺序,并指示每个顺序。这也可以让我更容易检查每一列是否真的是一个列(嘿,如果你命名一个列[1;truncate table dbo.something],你得到你得到的......)。

首先,在数据库中创建以下用户定义的表类型:

CREATE TYPE dbo.OrderByColumns AS TABLE
(
  [Sequence] TINYINT PRIMARY KEY,
  ColumnName SYSNAME NOT NULL,
  Direction VARCHAR(4) NOT NULL DEFAULT 'ASC'
);

然后:

DECLARE @x dbo.OrderByColumns;

INSERT @x SELECT 1, N'name', 'ASC';
INSERT @x SELECT 2, N'ID', 'DESC';
INSERT @x SELECT 3, N'1;truncate table dbo.whatever', 'DESC';

-- the above could be a parameter to your stored procedure
-- and could be populated in a DataTable in your application

DECLARE @sql NVARCHAR(MAX) = N'SELECT ... FROM ...
  WHERE ... ORDER BY NULL';

SELECT @sql += ',' + QUOTENAME(x.ColumnName) + ' ' + x.Direction
FROM sys.columns AS c
INNER JOIN @x AS x
ON c.name = x.ColumnName
AND c.[object_id] = OBJECT_ID('dbo.MyTable')
ORDER BY x.[Sequence] OPTION (MAXDOP 1);

PRINT @sql;

虽然您可以使用CASE执行此操作,但动态生成ORDER BY - 尤其是当它影响计划选择时 - 实际上可以更好地提高性能。使用静态查询,您可以获得最先@order_column的计划,然后即使不同的排序列可能导致另一个更有效的计划,它也会被重用。不同的计划可能有不同的ORDER BY子句,因为这些子句需要不同的SORT运算符。您可以使用OPTION (RECOMPILE)稍微解决这个问题,这可以确保您每次都生成一个新计划,但现在您每次都要支付编译成本,即使相同的顺序总是或几乎总是如此,使用

使用动态SQL时,每个版本的查询都会单独进行优化。计划缓存膨胀是一个问题,有点被optimize for ad hoc workloads服务器设置所抵消。这可以防止SQL Server缓存查询的特定变体的整个计划,直到该特定变体已被使用两次。

答案 1 :(得分:3)

您可以动态组装SQL并使用sp_executesql执行它。但是,使用非动态的参数化存储过程会使您失去一些性能和安全性。

如果可能的ORDER BY列是有限列表,则可以在ORDER BY子句中使用CASE WHEN来根据传入的参数更改排序。例如。如果您传入了一个名为@order_column的参数,则可以执行

ORDER BY
CASE WHEN @order_column='ColumnA'
THEN ColumnA END
CASE WHEN @order_column='ColumnB'
THEN ColumnB END
相关问题