你有两个选择。
条件连接
如果你不知道具体的列名,但你知道列名可能是什么,你可以像这样进行条件连接:
CREATE PROCEDURE ExampleDynamicJoin(@JoinColumn AS VarChar(40))
AS
BEGIN
SELECT *
FROM TableA
JOIN TableB ON (@JoinColumn = 'ColumnA' AND TableA.ColumnA = TableB.ColumnA)
OR (@JoinColumn = 'ColumnB' AND TableA.ColumnB = TableB.ColumnB)
OR (@JoinColumn = 'ColumnC' AND TableA.ColumnC = TableB.ColumnC)
END
您可能无法从中获得最佳性能(条件连接会使查询引擎感到困惑,并且它可能不会选择最佳索引,如果它完全选择了一个)。如果桌子很大,你也可以这样做。看起来有点痛苦,但会获得更好的性能:
CREATE PROCEDURE ExampleDynamicJoin(@JoinColumn AS VarChar(40))
AS
BEGIN
IF (@JoinColumn = 'ColumnA') BEGIN
SELECT *
FROM TableA
JOIN TableB ON TableA.ColumnA = TableB.ColumnA
END
IF (@JoinColumn = 'ColumnB') BEGIN
SELECT *
FROM TableA
JOIN TableB ON TableA.ColumnB = TableB.ColumnB
END
IF (@JoinColumn = 'ColumnC') BEGIN
SELECT *
FROM TableA
JOIN TableB ON TableA.ColumnC = TableB.ColumnC
END
END
如果 TableA 或 TableA 是更大查询的一部分,并且您最终会复制大量 SQL,则始终可以将仅 TableA 和 TableB 的结果集提取到临时表中,然后在更大的查询中使用临时表。
动态 SQL
如果您对列名没有最模糊的了解,并且有很多可能性,您可以将 SQL 构造为字符串并以这种方式连接。您应该验证传入的列名;这不仅会确保该列确实存在,而且当@JoinColumn 包含注入攻击时,它会阻止动态SQL 的构建,因为合法的列名不包含SQL 语句。示例:
CREATE PROCEDURE ExampleDynamicJoin(@JoinColumn AS VarChar(40))
AS
BEGIN
DECLARE @Sql AS VarChar(MAX)
IF NOT EXISTS
(
SELECT 0
FROM syscolumns c
JOIN sysobjects o ON o.id = c.id
WHERE o.Name = 'TableA'
AND c.Name = @JoinColumn
)
RAISERROR (15600,-1,-1, 'ExampleDynamicJoin'); //Throw error if column doesn't exist
SET @Sql =
'SELECT *
FROM TableA
JOIN TableB ON TableA.' + @JoinColumn + ' = TableB.' + @JoinColumn
sp_ExecuteSql @Sql
END
或者,如果你不使用存储过程,
DataTable ExampleDynamicJoin(string joinColumn)
{
if (!ValidateColumn(joinColumn)) throw new ArgumentException();
var sql = String.Format(
@"SELECT *
FROM TableA
JOIN TableB ON TableA.{0} = TableB.{0}",
joinColumn
);
using (var connection = GetConnectionFromSomewhere())
{
using (var cmd = new SqlCommand
{
CommandText = sql,
CommandType = CommandType.Text,
Connection = connection
})
{
var reader = cmd.ExecuteReader();
var table = new DataTable();
table.Load(reader);
return table;
}
}
}
使用动态 SQL 时,应尽可能使用参数。但是您不能将参数用作列名,因此在这种情况下您必须进行连接。连接时,始终将输入列入白名单。这就是为什么我包含一个名为 ValidateColumn 的函数,它看起来像这样:
bool ValidateColumn(string columnName)
{
switch columnName.ToUpper()
{
case "COLUMNA":
case "COLUMNB":
case "COLUMNC":
return true;
default:
return false;
}
}