2009-10-26 3 views
7

Ich habe eine C# .net App, die ich ändern muss. Die Abfrage im Moment tut effektiv dies:Oracle-Parameter mit IN-Anweisung?

select * from contract where contractnum = :ContractNum 

(sehr vereinfacht, nur zu zeigen, dass wir ein = und ein Parameter verwenden)

Dieser Parameter gelesen wird, in der Settings.settings-Datei auf dem C# app und hat eine Zeichenfolge drin. Ich brauche es, zu modifizieren, mehrere Verträge zu schließen, so dass ich denke, ich die SQL ändern können:

select * from contract where contractnum in (:ContractNum) 

aber lieferte keine Ergebnisse, egal, wie ich in dem Parameter die Zeichenkette formatiert werden.

Gibt es eine Möglichkeit, Orakel zu bekommen, ein IN mit einem Parameter zu tun?

jede Hilfe geschätzt, danke an alle.

+0

Wenn Sie odp.net oder devart als Datenprovider verwenden, können Sie eine Oracle-Sammlung (eine geschachtelte Tabelle) als Parameter verwenden. Dies ist der schnellste Weg, aber es ist nicht möglich, wenn Sie system.data.oracleclient verwenden. Welche Art von Datenprovider verwenden Sie? – tuinstoel

Antwort

2

Sie müssen noch eine Datenbank finden, die die Auswertung einer einzelnen String-Variablen unterstützt, die Kommas enthält, die als einzige IN-Klausel getrennt werden sollen.

Sie haben die Möglichkeit, die Variable so zu substringieren, dass der Inhalt der kommagetrennten Variablen in Zeilen umgewandelt wird. Sie können sich dann daran anschließen. Oder um dynamisches SQL zu verwenden, bei dem es sich um eine SQL-Anweisung handelt, die vor dem Ausführen der Anweisung als String in einem Sproc erstellt wurde.

+0

fand ich einen Hinweis auf die Verwendung & statt: zur Parameteridentifikation und das funktioniert: Wert Parameter: '1182411', '1182423' SQL: select * from Vertrag, wo contractnum in (& ContractNum) Ich habe keine Ahnung warum das funktioniert, oder ob es "offiziell" von Orace oder nur von TOAD unterstützt wird. Haben Sie schon einmal für Oracle verwendet? Irgendeine Idee, was der Unterschied ist? – Gareth

+0

: Variable ist eine Bind-Variable; Das einzige Mal, dass ich mich daran erinnere, mit & variable zu arbeiten, war die Definition/Verwendung einer Bindevariablen in PLSQL Developer. –

+2

Das Und-Zeichen ist das Standardzeichen, das eine Substitutionsvariable in SQL * Plus angibt. TOAD (und andere IDEs) unterstützen einige SQL * Plus-Syntax. – APC

6

können Sie eine Pipeline-Funktion verwenden, um eine Zeichenfolge in eine Tabelle umzuwandeln, die mit dem Operator IN verwendet werden kann. Zum Beispiel (mit 10gR2 getestet):

SQL> select * from table(demo_pkg.string_to_tab('i,j,k')); 

COLUMN_VALUE 
----------------- 
i 
j 
k 

mit folgendem Paket:

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 

Ihre Abfrage würde wie folgt aussehen:

select * 
    from contract 
where contractnum in (select column_value 
         from table(demo_pkg.string_to_tab(:ContractNum))) 
+1

+1 - AFAIK, dies ist die einzige Möglichkeit, alle zu verwenden: Bindevariable, unbekannte Anzahl von Elementen und die "IN" -Klausel. Wenn Sie eine bekannte obere Schranke für die Anzahl der Elemente haben, können Sie die Anweisung immer so programmieren, dass sie diese Anzahl von Elementen verwendet und Nullen programmatisch ersetzt, wenn Platzhalter vorhanden sind. – dpbradley

+0

Nein, dies ist nicht die einzige Möglichkeit, Bind-Variablen zu verwenden. Sie können auch eine Oracle-Sammlung von Zahlen binden und mit der Tabelle (: Zahlen) verknüpfen. Sie werden keine Pipeline-Funktion mehr benötigen. Ihr Datenanbieter muss dies jedoch unterstützen. – tuinstoel

+0

Dies ist der Ansatz, den wir seit fast 10 Jahren verwenden. Nachdem wir (endlich) zu ODP.NET gewechselt haben, sind wir gezwungen, die in der Antwort von tuinstoel hervorgehobene Methode zu verwenden. Das scheint sowieso eine viel bessere Lösung zu sein, als String-Splitting. – Carl

6

Sie können eine Oracle-Sammlung von Zahlen verwenden als ein Parameter (Bindevariable), wenn Sie ODP.NET als Datenprovider verwenden. Dies funktioniert mit Oracle Server 9, 10 oder 11 und ODP.net release> = 11.1.0.6.20.

Eine ähnliche Lösung ist möglich, wenn Sie Devarts .NET-Datenprovider für Oracle verwenden.

Lasst uns die Verträge mit contractnum der Auswahl 3 und 4.

Wir haben eine Oracle-Typ verwenden, um eine Reihe von Vertragsnummern auf unsere Anfrage zu übertragen.

MDSYS.SDO_ELEM_INFO_ARRAY wird verwendet, da wir, wenn wir diesen bereits vordefinierten Oracle-Typ verwenden, keinen eigenen Oracle-Typ definieren müssen. Sie können MDSYS.SDO_ELEM_INFO_ARRAY mit maximal 1048576 Nummern füllen.

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

Der Index auf contract.contractnum nicht verwendet wird, wenn ein Hinweis/* + Kardinalität (Register 10) */weglässt. Ich habe angenommen, dass die Vertragsnummer der Primärschlüssel ist, daher wird diese Spalte indexiert.

Siehe auch hier: http://forums.oracle.com/forums/thread.jspa?messageID=3869879#3869879

+0

Lesen Sie auch: http://blog.tanelpoder.com/2012/08/02/the-limitations-of-cursor_sharing-force-and-force_matching_signature-for-sql-plan-stability/ – TTT

+0

Dies funktionierte für unser Szenario. Das Problem für mich war, die NumberArrayFactory zu integrieren, obwohl sie nicht explizit im Code verwendet wird. – Carl

0

Für mit IN-Anweisung Parameter Sie diese Konstruktion verwenden können:

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

wo ContractNum ist die benutzerdefinierte Array-Typ.