【问题标题】:SQL query filtering by list of parameters按参数列表过滤 SQL 查询
【发布时间】:2012-07-19 22:35:43
【问题描述】:

我有一个查询,我想返回与值列表关联的所有行。你可以很简单地写成:

select * from TableA where ColumnB in (1, 2, 3, 5)

我可以在 C# 中生成这个查询并执行它。然而,这显然不太理想,因为它不使用参数,在尝试缓存查询计划时会受到影响,并且显然容易受到 SQL 注入攻击。

另一种方法是将其写为:

select * from TableA where ColumnB = @value

这可以由 C# 执行多次,但是这将导致 N 个 DB 命中。

我能看到的唯一另一种选择是创建一个临时表并以这种方式加入它,但是我不认为这一点,因为它会更复杂,并且会受到与第一个选项相同的限制。

我正在使用 SQL 服务器和 OLDB,创建查询不是问题。我正在尝试创建最有效的流程。

这三种方法中哪一种更有效?我错过了替代方案吗?

【问题讨论】:

  • 你想如何执行查询? EF、LINQ、ADO、OLEDB?
  • 那台服务器呢? MySql,MsSql,其他?
  • OLDB 和 MsSQL,问题已更新
  • 你使用的是 sql2008 吗?我可以使用表值参数提供另一种方法
  • 询问 SQL Server 问题时请指定 SQL Server 版本。这将防止人们花时间开发您无法使用的解决方案。

标签: c# sql sql-server performance sql-server-2008


【解决方案1】:

假设 SQL Server 2008 或更高版本,在 SQL Server 中创建表类型一次:

CREATE TYPE dbo.ColumnBValues AS TABLE
(
  ColumnB INT
);

然后是一个以这种类型为输入的存储过程:

CREATE PROCEDURE dbo.whatever
  @ColumnBValues dbo.ColumnBValues READONLY
AS
BEGIN
  SET NOCOUNT ON;

  SELECT A.* FROM dbo.TableA AS A
    INNER JOIN @ColumnBValues AS c
    ON A.ColumnB = c.ColumnB;
END
GO

现在在 C# 中,创建一个 DataTable 并将其作为参数传递给存储过程:

DataTable cbv = new DataTable();
cbv.Columns.Add(new DataColumn("ColumnB"));

// in a loop from a collection, presumably:
cbv.Rows.Add(someThing.someValue);

using (connectionObject)
{
    SqlCommand cmd        = new SqlCommand("dbo.whatever", connectionObject);
    cmd.CommandType       = CommandType.StoredProcedure;
    SqlParameter cbvParam = cmd.Parameters.AddWithValue("@ColumnBValues", cbv);
    cbvParam.SqlDbType    = SqlDbType.Structured;
    //cmd.Execute...;
}

(您可能希望使该类型更通用,我专门命名它以明确它在做什么。)

【讨论】:

    【解决方案2】:

    您也可以使用multiple resultsets 并像这样发送一堆查询:

    select * from TableA where ColumnB = @value0
    select * from TableA where ColumnB = @value1
    select * from TableA where ColumnB = @value2
    ...
    select * from TableA where ColumnB = @valuen
    

    单个通话中。 即使明显违反直觉,它也会利用执行计划并且在参数化方面是安全的。

    【讨论】:

    • 我很好奇收到的-1
    • 这不正是 OP 说他们不想做的吗?
    • @AaronBertrand 不完全是:他想使用执行计划,这将。请注意,它们不是分开的往返,而是所有查询都在到数据库的一次往返中执行。另外,查询已正确参数化,以避免注入。
    • 如果 ColumnB 没有被索引,这将需要对整个表进行 N 次扫描。即使 OP 的列被索引,下一个读者的列也可能没有。往返并不是将其拆分为多个查询使其效率降低的唯一原因。
    • 如果它没有被索引,扫描也会发生在 In 上
    【解决方案3】:

    你可以很容易地这样写:

    String csvString = "1, 2, 3, 5"; // Built the list somehow, don't forget escaping
    String query = "select * from TableA where ColumnB in (" + csvString + ")";
    

    通过这种方式,性能不会降低,并且您可以防止 Sql Injection 在创建 csvString 时简单地转义输入值。

    顺便说一句,如果你使用 MS SQL 而不是标准 SQL,你可以findalternativeways

    【讨论】:

    • 是的,这就是我目前的做法。我的问题是,每次执行此查询时都会创建一个新的执行计划,因为命令不同,从而大大降低了性能......
    • @Liath 您可以通过使用optimize for ad hoc workloads 设置来避免其中的一些情况。这种方式在特定查询执行两次之前不会缓存计划。您的查询仍然会产生扫描,但它不会占用计划缓存中的空间,除非它应该(例如,它确实被重复使用)。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-11-14
    • 1970-01-01
    • 1970-01-01
    • 2014-02-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多