gpt4 book ai didi

.net - 在 Oracle Merge 语句的 Using 子句中指定参数

转载 作者:行者123 更新时间:2023-12-03 14:39:56 26 4
gpt4 key购买 nike

Oracle 的 PL/SQL 对我来说相当新,所以我需要一些帮助来了解我尝试在 Merge 的 Using 子句中使用参数的方式是否可行。

我正在使用 ODP.NET 使用 Oracle 11g 与现有的 C# .NET 4.0 代码库进行通信,该代码库使用 SQL 连接来检索/修改数据。现有的 SQL 语句如下所示:

MERGE INTO Worker Target
USING
(
SELECT
:Id0 Id
,:Options0 Options
FROM dual
UNION ALL
SELECT
:Id1 Id
,:Options1 Options
FROM dual
) Source
ON (Target.Id = Source.Id)
WHEN MATCHED THEN
UPDATE SET
Target.StateId = :StateId
,Target.Options = Source.Options

Using 子句是在 C# StringBuilder 中生成的,以适应不同数量的 worker Id/Option 对,同时创建匹配的参数。
StringBuilder usingClause = new StringBuilder();
List<OracleParameter> parameters = new List<OracleParameter>();
for (int i = 0; i < workers.Count; ++i)
{
if (i > 0)
usingClause.Append("UNION ALL\n");
usingClause.AppendFormat("SELECT\n :Id{0} Id\n ,:Options{0} Options\n FROM dual\n", i);

parameters.Add(new OracleParameter("Id" + i, workers[i].Id));
parameters.Add(new OracleParameter("Options" + i, workers[i].Options))
}
parameters.Add(new OracleParameter("StateId", pendingStateId));

usingClause StringBuilder 与 Merge 命令的其余部分组合成一个名为“sql”的字符串,然后在 OracleCommand 对象中使用该字符串。执行 SQL Merge 语句的 C# 如下所示:
OracleConnection cn = new OracleConnection(
ConfigurationManager.ConnectionStrings["OracleSystemConnection"].ConnectionString
);

using (OracleCommand cmd = new OracleCommand(sql, cn))
{
cmd.BindByName = true;
cn.Open();
foreach (OracleParameter prm in parameters)
cmd.Parameters.Add(prm);

cmd.ExecuteNonQuery();
cn.Close();
}

我已经尝试过按名称绑定(bind)和不绑定(bind)参数,并确保在没有按名称绑定(bind)参数的情况下顺序正确。我不断得到的是“ORA-01008:并非所有变量都绑定(bind)”错误。

我还尝试在 SQL Developer 中运行 Merge 命令,并得到“未声明绑定(bind)变量 'Id0'”的响应。通常,当我在 SQL Developer 中使用未声明的绑定(bind)变量运行命令时,它会打开一个对话框来输入值,但不是使用此 SQL 命令,因此在 SQL Developer 中未声明它是可以理解的,但我不明白为什么会这样ODP.NET/C# 实现的情况,因为我将参数添加到 OracleCommand 对象。

如果有人能指出我做错了什么,或者告诉我如何达到同样的效果,将不胜感激。此外,如果有人知道将值列表传递到 Merge 的 Using 子句中的更好方法,而不是在它们之间执行一堆 SELECTs FROM dual 和 UNION ALL,那么也将不胜感激。

使用 Long Raw 作为选项列的答案

经过一番努力,这是最终的解决方案。感谢 tomi44g 为我指明了正确的方向。
DECLARE
TYPE id_array IS TABLE OF NUMBER INDEX BY PLS_INTEGER;
TYPE option_array IS TABLE OF LONG RAW INDEX BY PLS_INTEGER;

t_ids id_array := :ids;
t_options option_array := :options;
BEGIN
FORALL i IN 1..t.ids.count
EXECUTE IMMEDIATE '
MERGE INTO Worker Target
USING (SELECT :1 Id, :2 Options FROM dual) Source
ON (Source.Id = Target.Id)
WHEN MATCHED THEN
UPDATE SET
Target.StateId = :3
,Target.Options = Source.Options' USING t_ids(i), t_options(i), :state_id;
END;

这就是 C# 更改的内容,以适应解决方案。
// Gather the values into arrays for binding.
int[] workerIds = new int[workers.Count];
byte[][] workerOptions = new byte[workers.Count][];
BinaryFormatter binaryFormatter = new BinaryFormatter();
for (int i = 0; i < workers.Count; ++i)
{
workerIds[i] = workers[i].Id;

// There's an assumed limit of 4096 bytes here; this is just for testing
MemoryStream memoryStream = new MemoryStream(4096);
binaryFormatter.Serialize(memoryStream, workers[i].Options);
workerOptions[i] = memoryStream.ToArray();
}


// Excute the command.
OracleConnection cn = new OracleConnection(
ConfigurationManager.ConnectionStrings["OracleSystemConnection"].ConnectionString
);
using (OracleCommand cmd = new OracleCommand(sql, cn))
{
cmd.BindByName = true;
cn.Open();

OracleParameter ids = new OracleParameter();
ids.OracleDbType = OracleDbType.Int32;
ids.CollectionType = OracleCollectionType.PLSQLAssociativeArray;
ids.Value = workerIds;
ids.ParameterName = "ids";

OracleParameter options = new OracleParameter();
options.OracleDbType = OracleDbType.LongRaw;
options.CollectionType = OracleCollectionType.PLSQLAssociativeArray;
options.Value = workerOptions;
options.ParameterName = "options";

cmd.Parameters.Add(ids);
cmd.Parameters.Add(options);
cmd.Parameters.Add(new OracleParameter("state_id", pendingStateId));

try
{
cmd.ExecuteNonQuery();
}
catch (OracleException e)
{
foreach (OracleError err in e.Errors)
{
Console.WriteLine("Message:\n{0}\nSource:\n{1}\n", err.Message, err.Source);
System.Diagnostics.Debug.WriteLine("Message:\n{0}\nSource:\n{1}\n", err.Message, err.Source);
}
}
cn.Close();
}

最佳答案

您最好将 id 和选项列表绑定(bind)到数组,然后在 PL/SQL block 中使用 FORALL 执行 MERGE:

DECLARE
TYPE id_array_type IS TABLE OF NUMBER INDEX BY PLS_INTEGER;
TYPE options_array_type IS TABLE OF VARCHAR2 (100) INDEX BY PLS_INTEGER;

t_ids id_array_type := :ids;
t_options options_array_type := :options;
v_state_id NUMBER := :stateId;
BEGIN
FORALL i IN 1 .. t_ids.count
EXECUTE IMMEDIATE '
MERGE INTO worker target
USING (SELECT :id id, :options options FROM dual) source
ON (source.id = target.id)
WHEN MATCHED THEN UPDATE SET target.stateId = :state_id, target.options = source.options'
USING t_ids (i), t_options (i), v_state_id;
END;

然后可以将参数绑定(bind)为 PL/SQL Associative Array
这样做,您将在 SGA 中始终拥有一条 SQL 语句,而不是针对所有可能数量的参数的许多语句,并且(这可能更重要)您将能够一次合并 1000 多个元素。

实际上,我注意到您没有使用 WHEN NOT MATCHED 子句。如果您真的对插入新记录不感兴趣,那么根本不需要使用 MERGE,只需使用 UPDATE 即可。您可以使用 Array Binding 在一次往返中有效地多次执行 UPDATE 语句。 .

关于.net - 在 Oracle Merge 语句的 Using 子句中指定参数,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/11110772/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com