2009-10-26 124 views
7

得到了一个我需要修改的c#.net应用程序。目前该查询有效地做到这一点:带有IN语句的Oracle参数?

select * from contract where contractnum = :ContractNum 

(非常简单,只是为了显示我们正在使用=和一个参数)

该参数从Settings.Settings文件上读C#应用程序并在其中包含一个字符串。我需要修改它包括多个合同,所以我想我可以改变SQL语句:

select * from contract where contractnum in (:ContractNum) 

但不返回任何结果,无论我如何格式化参数字符串。

有没有一种方法可以让oracle用参数做一个IN?

任何帮助表示感谢,谢谢大家。

+0

当您使用odp.net或devart作为数据提供者时,可以使用Oracle集合(嵌套表)作为参数。这是最快的方式,但使用system.data.oracleclient时不可能。那么你使用什么样的数据提供者? – tuinstoel 2009-10-26 19:11:29

回答

2

还没有找到一个数据库,支持评估一个单独的字符串变量包含逗号分开作为唯一IN子句。

您的选择是对变量进行子字符串处理,以便将逗号分隔的变量内容转换为行,以便您可以加入到此行中。或者在执行语句之前使用动态SQL,这是一个构建为sproc中字符串的SQL语句。

+0

我发现了一个参考使用&代替:对参数辨识和工作原理: 参数值: '1182411', '1182423' SQL:SELECT * FROM合同,其中contractnum在(ContractNum) 我不知道为什么这会起作用,或者它是否正式由Orace或TOAD支持。你之前用过&for Oracle吗?任何想法有什么不同? – Gareth 2009-10-26 16:20:52

+0

:变量是一个绑定变量;我只记得使用&变量是在PLSQL Developer中定义/使用绑定变量。 – 2009-10-26 16:25:54

+2

&符号是指示SQL * Plus中的替代变量的默认字符。 TOAD(和其他IDE)支持一些SQL * Plus语法。 – APC 2009-10-26 16:27:42

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

+1 - AFAIK,这是使用所有:绑定变量,未知元素数量和“IN”子句的唯一方法。 如果您已知元素数量的上限,则始终可以对语句进行编码以使用该元素数量,并且在存在剩余占位符时以编程方式替换空值 – dpbradley 2009-10-26 16:45:11

+0

否这不是使用绑定变量的唯一方法。您还可以绑定一个Oracle数字集合并与表格(:数字)结合。您将不再需要流水线功能。但是您的数据提供者必须支持它。 – tuinstoel 2009-10-26 19:34:16

+0

这是我们使用近10年的方法。在(最后)移动到使用ODP.NET之后,我们被迫使用tuinstoel发布的答案中突出显示的方法。无论如何,这似乎是一个更好的解决方案,而不是分割字符串。 – Carl 2014-11-12 17:02:19

6

您可以使用数字的一个Oracle集合作为使用ODP.NET作为dataprovider时的参数(绑定变量)。这适用于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类型。您可以填写MDSYS.SDO_ELEM_INFO_ARRAY最多1048576个号码。

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

+0

另请参阅:http://blog.tanelpoder.com/2012/08/02/the-limitations-of-cursor_sharing-force-and-force_matching_signature-for-sql-plan-stability/ – TTT 2013-03-27 12:43:39

+0

这适用于我们的场景。对我来说,这个问题包括了NumberArrayFactory,即使它没有在代码中明确使用。 – Carl 2014-11-12 16:59:26

0

对于使用参数中陈述,你可以使用这样的结构:

select * from contract where contractnum 
in (select column_value from table (:ContractNum)) 

其中ContractNum是自定义数组类型。