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

时间:2014-08-20 09:55:44

标签: c# oracle11g odp.net

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

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

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

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

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

  • 全球临时表
  • 存储过程
  • 要求CREATE TYPE执行的任何内容

只需执行一个查询即可解决此问题。

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

IList<string> 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;

//...

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

对此的问题是:

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

(我知道这与this (unanswered) question类似,但是这种情况没有提到对方法有相同的限制。)

2 个答案:

答案 0 :(得分:1)

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

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

@"BEGIN
CREATE TABLE {inListTableName}
(
  inValue   {dbDataType}
)

INSERT INTO {inListTableName}(inValue) VALUES(:inValue);
END"

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

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

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

将OracleParameter添加到名为&#34; inValue&#34;的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);

答案 1 :(得分:0)

对于您正在进行的查询类型而言,这可能过于简单了,但是有什么理由说明您无法将其拆分为多个查询并将结果合并到代码中?

即。让我们设想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)

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

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