【问题标题】:C#/ODP.NET: large IN clause workaroundC#/ODP.NET:大型 IN 子句解决方法
【发布时间】:2014-10-13 15:18:42
【问题描述】:

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

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,但该场景并未提及对方法有相同的限制。)

【问题讨论】:

  • 您是否尝试过使用匿名 PL/SQL 块(例如在 BEGIN 和 END 之间)?它基本上是存储过程代码,只是它位于客户端,而不是存储在服务器中,因此您可以作为开发人员控制它。
  • 你是说你达到了参数数量的限制吗?或者是 SQL 的大小?你能分享你得到的 ORA 错误吗?
  • 如果不尝试更改,它会得到:ORA-01795:列表中的最大表达式数为 1000
  • 此链接中有一些建议的解决方法。它们中的任何一个都适用于你吗? stackoverflow.com/questions/17842453/…
  • 网上有几十个涉及 ORA-01795 的问题,它们都涉及以违反此问题中给出的要求的方式重新格式化 SQL。所以我最好的猜测是没有答案,并且 T2PS 将需要阻止向他提供 SQL 的人员或限制他修改它以添加多个“或 where”子句。如果您真的很绝望,请尝试在 OTN SQL 论坛上提出这个问题,在与此问题相同的论坛中:community.oracle.com/thread/235143

标签: c# oracle11g odp.net


【解决方案1】:

好吧,既然不允许您使用全局临时表,那么您至少可以创建普通表吗?如果是这样,这里有一个方法:

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

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

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

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

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

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

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

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

然后在你的 select sql 语句中使用下面的 sql sn-p 作为 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 将创建您的准备命令,添加参数并执行准备命令。

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);

【讨论】:

  • 对临时表的限制同样适用于常规表,恐怕——没有权限。
  • 那很不幸。如果您需要在任何时候使用 NOT IN,请记住我最后添加的部分适用。这些查询不能分解。它们是否允许您将一张表永久添加到您的数据模型中?如果是这样,您可以创建一个永久 InList 表并添加一个 ID 以对表中的值进行分区以执行特定的查询。然后,您只需更改 GetInListSQLSnippet 以按该 ID 过滤永久 InList 表中的值。您可以使用 ID 的任何查询特定值来过滤列表。
  • 查询运行的数据模型也不属于我们,这就是对 GTT、CREATE TYPE 等的限制的来源。有人告诉我,外部 DBA 批准此类更改的几率比我们团队在不购买任何彩票的情况下中奖的几率要小......
  • 您是否能够在其数据模型之外的同一个 oracle 数据库实例上获得自己的架构?如果是这样,那么您可以使用跨模式的完全限定表名来执行此操作。我在 2008 年为 SQL Server 和今年为 Oracle 研究了如何做到这一点。以我的经验,没有其他可靠的方法可以为这些类型的操作获取大量值到数据库中。例如,ArrayBindCount 的使用受到 Oracle 的限制,只能在 ExecuteNonQuery(更新/插入/删除)语句中使用。
  • 不要忘记,在执行 UNION 时,选择会隐含 DISTINCT。这可能有问题,也可能没有。
【解决方案2】:

对于您正在执行的查询类型而言,这可能过于简单,但是您是否有任何理由不能将其拆分为多个查询并将结果组合到代码中?

即假设 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)

然后将这些结果相加。当然,您必须确保子句列表是不同的,这样您就不会加倍计数。

如果您可以这样做,那么如果您被允许多个连接,则可以增加并行性。

【讨论】:

  • 不幸的是,我们正在谈论的查询是任意的,通常比您的示例更复杂,包括使用 CTE 执行分页,INNER JOINs 等。我被告知调用代码,在它知道它的查询结果可以以这种方式合并的地方,已经被修改为这样做;这个问题针对的是其余的情况。尤其是分页被描述为与这种方法不兼容。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-12-07
  • 2012-01-25
  • 1970-01-01
  • 2020-09-12
  • 1970-01-01
  • 2023-03-09
相关资源
最近更新 更多