我有一个复杂的存储过程。我想让用户选择要排序的列。他们应该能够根据需要选择尽可能多的列。
有没有办法在存储过程中实现这个?如何将列名传递给过程,然后在order by子句中反映这些?注意到将有可变数量的列。
我理解如何传递参数,只是不知道是否可以在存储过程中动态构建order by子句
答案 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