C#/ ODP.NET:大型IN子句解决方法

我们有一个C#组件,它处理将任意大小的元素列表附加到用于半任意SQL SELECT查询的IN子句中。 从本质上讲,这归结为接收如下内容:

 SELECT COUNT(*) FROM a WHERE b IN (...) 

…其中“…”是允许组件修改的查询的唯一部分。

目前该组件将插入一组以逗号分隔的命名绑定参数,然后将相应的IDbDataParameter对象附加到命令并执行; 组件知道它必须绑定的参数的类型。 这很有效,直到调用代码提供的参数集大于数据库愿意接受的参数集。 这里的目标是让这些大型集合通过ODP.NET处理针对Oracle 11gR2的查询。

由于以下方法被设定要求的人认为是不可接受的,因此这项任务有些复杂:

  • 全球临时表
  • 存储过程
  • 任何需要CREATE TYPE东西都已被执行

解决方案不需要只执行一个查询。

我试图通过使用来自其他地方的代码将子句绑定为数组来完成此工作:

 IList values; //... OracleParameter parameter = new OracleParameter(); parameter.ParameterName = "parm"; parameter.DbType = DbType.String; parameter.Value = values.ToArray(); int[] sizes = new int[values.Count]; for (int index = 0; index < values.Count; index++) { sizes[index] = values[index].Length; } parameter.ArrayBindSize = sizes; //... 

该命令随后执行而不抛出exception,但为COUNT返回的值为零(与预期值相比,在SQLDeveloper中运行查询时,嵌套的SELECT返回相同的参数集)。 到目前为止,通过ODP.NET文档并未带来任何乐趣。

对此的问题是:

  • 有没有办法使上述参数附件按预期工作?
  • 是否有另一种可行的方法可以在不使用其中一种否决方法的情况下实现这一目标?

(我知道这与这个(未答复的)问题类似,但是这种情况没有提到对方法有相同的限制。)

那么,既然不允许使用全局临时表,那么您是否至少可以创建普通表? 如果是这样,这是一种方式:

使用以下命令文本创建OracleCommand对象:

 @"BEGIN CREATE TABLE {inListTableName} ( inValue {dbDataType} ) INSERT INTO {inListTableName}(inValue) VALUES(:inValue); END" 

将命令对象上的ArrayBindCount设置为您在列表中所需的项目数。

{inListTableName}替换为Guid.NewGuid().ToString()

{dbDataType}替换为要在in子句中使用的值列表的正确oracle数据类型。

将OracleParameter添加到名为“inValue”的OracleCommand中,并将参数值设置为包含in子句中所需值的数组。 如果你有一个Hashset(我建议使用它来避免发送不必要的重复项),使用它.ToArray()来获取一个数组。

执行此命令。 这是你的准备命令。

然后使用以下sql代码段作为select sql语句中in子句的值部分: (SELECT {inListTableName}.inValue FROM {inListTableName})

例如:

 SELECT FirstName, LastName FROM Users WHERE UserId IN (SELECT {inListTableName}.inValue FROM {inListTableName}); 

执行此命令以获取读者。

最后,再使用以下命令文本执行以下命令:

 DROP TABLE {inListTableName}; 

这是你的清理命令。 执行此命令。

您可能希望创建备用架构/用户来创建inListTable以便您可以向用户授予适当的权限,以便仅在该架构中创建表。

所有这些都可以使用以下接口封装在可重用的类中:

 public interface IInListOperation { void TransmitValueList(OracleConnection connection); string GetInListSQLSnippet(); void RemoveValueList(); } 

TransmitValueList将创建您的prep命令,添加参数并执行prep命令。

GetInListSQLSnippet将简单地返回(SELECT {inListTableName}.inValue FROM {inListTableName});

RemoveValueList清理。

此类的构造函数将获取值列表和oracle db数据类型,并生成inListTableName

如果您可以使用全局临时表,我建议您创建和删除表。

编辑:我想补充一点,如果你有涉及NOT IN列表或其他不等式运算符的子句,这种方法很有效。 以下面的例子为例:

 SELECT FirstName, LastName FROM Users WHERE Status == 'ACTIVE' OR UserID NOT IN (1,2,3,4,5,6,7,8,9,10); 

如果您使用将NOT IN部分拆分的方法,最终会得到无效结果。 以下分割前一个示例的示例将返回所有用户,而不是除UserIds 1-10之外的所有用户。

 SELECT FirstName, LastName FROM Users WHERE UserID NOT IN (1,2,3,4,5) UNION SELECT FirstName, LastName FROM Users WHERE UserID NOT IN (6,7,8,9,10); 

也许这对于你正在进行的查询来说过于简单,但是有没有理由为什么你不能将它分成几个查询并将结果组合在一起?

即让我们想象5个元素对查询来说太多了……

 select COUNT(*) from A where B in (1,2,3,4,5) 

你单独表演

 select COUNT(*) from A where B in (1,2,3) select COUNT(*) from A where B in (4,5) 

然后将这些结果一起添加。 当然,你必须确保条款清单是不同的,这样你就不会加倍计算。

如果您可以这样做,如果允许多个连接,则可以增加并行机会。