C# 处理要包含在oracle select语句中的大量数据
最近的错误报告指出,正在调用的方法正在使服务崩溃,导致服务重新启动。故障排除后,发现原因是一个令人讨厌的Oracle SQL调用传递了数千个字符串。有一组字符串从外部服务传递给一个方法,这些字符串通常超过10000条记录。原始代码使用LIKE关键字在传递的集合上使用where子句,我认为这非常非常糟糕C# 处理要包含在oracle select语句中的大量数据,c#,oracle,C#,Oracle,最近的错误报告指出,正在调用的方法正在使服务崩溃,导致服务重新启动。故障排除后,发现原因是一个令人讨厌的Oracle SQL调用传递了数千个字符串。有一组字符串从外部服务传递给一个方法,这些字符串通常超过10000条记录。原始代码使用LIKE关键字在传递的集合上使用where子句,我认为这非常非常糟糕 public IList<ContainerState> GetContainerStates(IList<string> containerNumbers) {
public IList<ContainerState> GetContainerStates(IList<string> containerNumbers)
{
string sql =
String.Format(@"Select CTNR_NO, CNTR_STATE FROM CONTAINERS WHERE CTRN_SEQ = 0 AND ({0})",
string.Join("OR", containerNumbers
.Select(item => string.Concat(" cntr_no LIKE '", item.SliceLeft(10), "%' ")))
);
return DataBase.SelectQuery(sql, MapRecordToContainerState, new { }).ToList();
}
澄清可能令人困惑的内部方法:
DataBase.SelectQuery是一种内部库方法,使用泛型传递sql字符串、将记录映射到.NET对象的函数以及传递的参数,并返回映射函数返回的IEnumerable类型的对象
SliceLeft是来自另一个内部帮助程序库的扩展方法,它只返回字符串的第一部分,最多返回参数指定的字符数
显然使用LIKE语句的原因是,传递的字符串和数据库中的字符串只能保证匹配前10个字符。正在传递的字符串中的示例xx000000-1应该与类似xx000000-8的数据库记录匹配
我相信使用SUBSTR的IN子句比使用多个LIKE子句更有效,并将代码替换为:
public IList<ContainerRecord> GetContainerStates(IList<string> containerNumbers)
{
string sql =
String.Format(@"Select CTNR_NO, CNTR_STATE FROM CONTAINERS WHERE CTRN_SEQ = 0 AND ({0})",
string.Format("SUBSTR(CNTR_NO, 1, 10) IN ({0}) ",
string.Join(",", containerNumbers.Select(item => string.Format("\'{0}\'", item.SliceLeft(10) ) ) )
)
);
return DataBase.SelectQuery(sql, MapRecordToContainerState, new { }).ToList();
}
这有点帮助,在我的测试中出现的问题也较少,但是当传递了大量记录时,仍然会抛出异常并发生核心转储,因为SQL的长度超过了服务器在这些时间内可以解析的长度。DBA建议保存传递到临时表的所有字符串,然后根据临时表进行连接
根据该建议,我将功能更改为:
public IList<ContainerRecord> GetContainerStates(IList<string> containerNumbers)
{
string sql =
@"
CREATE TABLE T1(cntr_num VARCHAR2(10));
DECLARE GLOBAL TEMPORARY TABLE SESSION.T1 NOT LOGGED;
INSERT INTO SESSION.T1 VALUES (:containerNumbers);
SELECT
DISTINCT cntr_no,
'_IT' cntr_state
FROM
tb_master
WHERE
cntr_seq = 0
AND cntr_state IN ({0})
AND adjustment <> :adjustment
AND SUBSTR(CTNR_NO, 1, 10) IN (SELECT CNTR_NUM FROM SESSION.T1);
";
var parameters = new
{
@containerNumbers = containerNumbers.Select( item => item.SliceLeft(10)).ToList()
};
return DataBase.SelectQuery(sql, MapRecordToContainerState, parameters).ToList();
}
现在我得到一个ORA-00900:无效的SQL语句。这真是令人沮丧,我如何才能正确地编写一个SQL语句,将此字符串列表放入临时表中,然后在SELECT语句中使用它来返回我需要的列表?有几个地方可能会导致此错误,如果声明全局临时性是JAVA API,我认为.net没有这个功能。请改为尝试创建全局临时表。而且,我不知道您的内部API是否可以在一个select sql中处理多个sql。据我所知,ODP.net命令类每次调用只能执行一个sql。此外,CREATETABLE是一个DDL,因此它有自己的事务。我看不出有什么理由我们应该把它们放在同一个sql中执行。下面是ODP.net的示例代码
using (OracleConnection conn = new OracleConnection(BD_CONN_STRING))
{
conn.Open();
using (OracleCommand cmd = new OracleCommand("create global temporary table t1(id number(9))", conn))
{
// actually this should execute once only
cmd.ExecuteNonQuery();
}
using (OracleCommand cmd = new OracleCommand("insert into t1 values (1)", conn)) {
cmd.ExecuteNonQuery();
}
// customer table is a permenant table
using (OracleCommand cmd = new OracleCommand("select c.id from customer c, t1 tmp1 where c.id=tmp1.id", conn)) {
cmd.ExecuteNonQuery();
}
}
一些简要说明:如果要使用全局临时表,请在使用它的任何过程或方法之外创建它一次,然后在过程内部使用它insert/select/delete。此外,它的会话特定,这可能会影响您如何使用它取决于您的设置。例如,看到我现在在家,我回到办公室后会尝试这样做。我使用的是答案中相同的sql,带有硬编码的值,而不是从列表中添加项目。我第一次运行它时,一切正常,下一次我与oracle中的现有对象发生冲突时,临时表在方法完成后没有消失。我可以在末尾添加一个放置台吗?或者在事务中运行它,并在获得结果集后回滚。这通常是如何处理的?当您在第二次运行代码时,会出现一个错误,因为表不会自动删除。在oracle中,临时表的内容将根据您选择的选项在会话结束或事务结束时被擦除。你用完桌子后可以把它放下。但这不是一个推荐的方法。它会导致一些问题,例如并发问题。例如,当您的第一个线程创建了表并运行了查询,然后您的第二个线程想要运行相同的代码并再次尝试创建表时,它将失败。像普通表一样维护临时表要简单得多。下面是关于如何使用临时表的一个很好的讨论。还有一个链接