有一个我需要修改的c#.net应用程序。目前的查询有效地做到了这一点:
select * from contract where contractnum = :ContractNum
(非常简化,只是为了表明我们正在使用=和一个参数)
该参数是从C#app上的Settings.Settings文件中读取的,并且其中包含一个字符串。我需要修改它以包含多个合同,所以我想我可以将SQL更改为:
select * from contract where contractnum in (:ContractNum)
但是无论我如何在参数中格式化字符串,都不会返回任何结果。
有没有办法让oracle用参数做一个IN?
任何帮助表示感谢,谢谢大家。
答案 0 :(得分:6)
您可以使用流水线函数将字符串转换为可与IN
运算符一起使用的表。例如(用10gR2测试):
SQL> select * from table(demo_pkg.string_to_tab('i,j,k'));
COLUMN_VALUE
-----------------
i
j
k
使用以下包:
SQL> CREATE OR REPLACE PACKAGE demo_pkg IS
2 TYPE varchar_tab IS TABLE OF VARCHAR2(4000);
3 FUNCTION string_to_tab(p_string VARCHAR2,
4 p_delimiter VARCHAR2 DEFAULT ',')
5 RETURN varchar_tab PIPELINED;
6 END demo_pkg;
7 /
Package created
SQL> CREATE OR REPLACE PACKAGE BODY demo_pkg IS
2 FUNCTION string_to_tab(p_string VARCHAR2,
3 p_delimiter VARCHAR2 DEFAULT ',')
4 RETURN varchar_tab PIPELINED IS
5 l_string VARCHAR2(4000) := p_string;
6 l_first_delimiter NUMBER := instr(p_string, p_delimiter);
7 BEGIN
8 LOOP
9 IF nvl(l_first_delimiter,0) = 0 THEN
10 PIPE ROW(l_string);
11 RETURN;
12 END IF;
13 PIPE ROW(substr(l_string, 1, l_first_delimiter - 1));
14 l_string := substr(l_string, l_first_delimiter + 1);
15 l_first_delimiter := instr(l_string, p_delimiter);
16 END LOOP;
17 END;
18 END demo_pkg;
19 /
Package body created
您的查询将如下所示:
select *
from contract
where contractnum in (select column_value
from table(demo_pkg.string_to_tab(:ContractNum)))
答案 1 :(得分:6)
当您使用ODP.NET作为数据提供者时,可以使用Oracle数字集合作为参数(绑定变量)。这适用于Oracle服务器9,10或11以及ODP.net版本> = 11.1.0.6.20。
当您使用适用于Oracle的Devart .NET数据提供程序时,可以使用类似的解决方案。
让我们选择contractnum 3和4的合约。
我们必须使用Oracle类型将一组合同号转移到我们的查询中。
使用 MDSYS.SDO_ELEM_INFO_ARRAY
是因为如果我们使用这个已经预定义的Oracle类型,我们就不必定义自己的Oracle类型。您可以使用最多1048576个数字填充MDSYS.SDO_ELEM_INFO_ARRAY
。
using Oracle.DataAccess.Client;
using Oracle.DataAccess.Types;
[OracleCustomTypeMappingAttribute("MDSYS.SDO_ELEM_INFO_ARRAY")]
public class NumberArrayFactory : IOracleArrayTypeFactory
{
public Array CreateArray(int numElems)
{
return new Decimal[numElems];
}
public Array CreateStatusArray(int numElems)
{
return null;
}
}
private void Test()
{
OracleConnectionStringBuilder b = new OracleConnectionStringBuilder();
b.UserID = "sna";
b.Password = "sna";
b.DataSource = "ora11";
using (OracleConnection conn = new OracleConnection(b.ToString()))
{
conn.Open();
using (OracleCommand comm = conn.CreateCommand())
{
comm.CommandText =
@" select /*+ cardinality(tab 10) */ c.* " +
@" from contract c, table(:1) tab " +
@" where c.contractnum = tab.column_value";
OracleParameter p = new OracleParameter();
p.OracleDbType = OracleDbType.Array;
p.Direction = ParameterDirection.Input;
p.UdtTypeName = "MDSYS.SDO_ELEM_INFO_ARRAY";
//select contract 3 and 4
p.Value = new Decimal[] { 3, 4 };
comm.Parameters.Add(p);
int numContracts = 0;
using (OracleDataReader reader = comm.ExecuteReader())
{
while (reader.Read())
{
numContracts++;
}
}
conn.Close();
}
}
}
当忽略提示/ * +基数(标签10)* /时,不使用contract.contractnum上的索引。我假设contractnum是主键,因此该列将被索引。
另见:http://forums.oracle.com/forums/thread.jspa?messageID=3869879#3869879
答案 2 :(得分:2)
尚未找到支持评估包含逗号的单个字符串变量的db,以将其作为唯一的IN
子句进行分隔。
您的选项是对变量进行子串,以便逗号分隔的变量内容转换为行,因此您可以加入此变量。或者使用动态SQL,它是在执行语句之前在sproc中构造为字符串的SQL语句。
答案 3 :(得分:0)
对于在IN语句中使用参数,您可以使用以下结构:
select * from contract where contractnum
in (select column_value from table (:ContractNum))
其中ContractNum是自定义数组类型。
答案 4 :(得分:0)
我知道这是一个古老的问题,但这是所选答案不能解决我的问题的几个问题之一,我不想就该主题开始另一个话题,所以我只列出我发现的内容希望能帮助到别人。
我与Oracle的合作并不多,但是,就像在SQL Server中一样,似乎要传递表值参数,您需要具有一个具有EXECUTE权限的对应UDT(用户定义表)(我可能是错误)。这意味着建议使用内置SYS UDT的其他答案也需要一些运费,我无法弄清楚是否确实有可能将表传递给当前版本中不是PL / SQL存储过程的对象ODP.net。
第二,出于所有明显的原因(无法缓存执行计划或Oracle所谓的执行计划,扩展性不佳等),字符串解析解决方案是一个难题。
因此,我花了很多时间尝试在数据集市上使用表值参数来执行IN子句,但在被明显的({{3} )。事实证明,Oracle“本地”支持Xml查询,因此您可以传递xml列表(如果您只需要这样),而不是传递值数组。同样,我可能是错的,但是它被当作合法的绑定参数来处理,这是一个使用起来很简单的示例(使用NuGet包的vb.net,ADO.net,ODP.net):
Dim xe As New XElement("l", New XElement("i", "ITEM-A"), New XElement("i", "ITEM-B"))
Using conn As New OracleConnection(myConnectionString)
conn.Open()
Using cmd As OracleCommand = conn.CreateCommand()
cmd.CommandType = CommandType.Text
Dim query As String
query = " SELECT s.FOO, q.BAR " & vbCrLf
query &= " FROM TABLE1 s LEFT OUTER JOIN " & vbCrLf
query &= " TABLE2 q ON q.ID = s.ID " & vbCrLf
query &= " WHERE (COALESCE(q.ID, 'NULL') NOT LIKE '%OPTIONAL%') AND "
query &= " (s.ID IN ("
query &= " SELECT stid "
query &= " FROM XMLTable('/l/i' PASSING XMLTYPE(:stid) COLUMNS stid VARCHAR(32) PATH '.')"
query &= " )"
query &= " )"
cmd.CommandText = query
Dim parameter As OracleParameter = cmd.Parameters.Add("stid", OracleDbType.NVarchar2, 4000)
parameter.Value = xe.ToString
Using r As OracleDataReader = cmd.ExecuteReader
While r.Read()
//Do something
End While
End Using
End Using
conn.Close()
这更是一种观察,而不是经过仔细研究的解决方案,因此请评论是否有任何不适当的方式这样做。
编辑。使用此方法显然有4000个字符的限制(如果为NVARCHAR,则为2000个字符),因此我不得不观看分页。如果经过检查,则得到的提示性错误消息是“ ORA-01460:请求未实现或不合理的转换”
答案 5 :(得分:0)
也许有人还在寻找答案,这里有一个 rexexp 的例子。 https://blogs.oracle.com/aramamoo/passing-comma-separated-string-as-bind-variable-for-vo-querys-in-operator-v2
在这种情况下,每个 emp 编号都是昏迷分隔的
WHERE Emp.ENAME in
(select regexp_substr(:Bind_Ename_Comma_Sep_List,'[^,]+', 1, level)
from dual
connect by
regexp_substr(:Bind_Ename_Comma_Sep_List, '[^,]+', 1, level)
is not null)