动态构建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 中。
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) 在一定程度上解决这个问题,这可以确保您每次都生成一个新计划,但现在您每次都需要支付编译成本,即使总是或几乎总是使用相同的 order by。
当您使用动态 SQL 时,每个版本的查询都会单独优化。计划缓存膨胀是一个被optimize for ad hoc workloads 服务器设置所抵消的问题。这可以防止 SQL Server 缓存查询的特定变体的整个计划,直到该特定变体被使用两次。