Domanda

Ho un'app c # .net che devo modificare. La query al momento lo fa efficacemente:

select * from contract where contractnum = :ContractNum

(molto semplificato, solo per mostrare che stiamo usando un = e un parametro)

Quel parametro viene letto dal file Settings.Settings sull'app C # e contiene una stringa. Devo modificarlo per includere più contratti, quindi immagino di poter cambiare l'SQL in:

select * from contract where contractnum in (:ContractNum)

ma questo non restituisce risultati, non importa come formattare la stringa nel parametro.

C'è un modo in cui posso ottenere l'oracolo per fare un IN con un parametro?

qualsiasi aiuto apprezzato, grazie a tutti.

È stato utile?

Soluzione

Devo ancora trovare un db che supporti la valutazione di una singola variabile stringa contenente virgole da separare come unica clausola IN .

Le tue opzioni sono di sottoporre a substring la variabile in modo che i contenuti della variabile delimitata da virgole vengano trasformati in righe, quindi puoi unirti a questo. Oppure utilizzare SQL dinamico, che è un'istruzione SQL costruita come stringa in uno sproc prima dell'esecuzione dell'istruzione.

Altri suggerimenti

è possibile utilizzare una funzione pipeline per trasformare una stringa in una tabella che può essere utilizzata con l'operatore IN . Ad esempio (testato con 10gR2):

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

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

con il seguente pacchetto:

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

La tua query sarebbe simile a questa:

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

È possibile utilizzare una raccolta di numeri Oracle come parametro (variabile di bind) quando si utilizza ODP.NET come provider di dati. Funziona con Oracle Server 9, 10 o 11 e ODP.net versione > = 11.1.0.6.20.

Una soluzione simile è possibile quando si utilizza il dataprovider .NET di Devart per Oracle.

Selezioniamo i contratti con i contratti 3 e 4.

Dobbiamo utilizzare un tipo Oracle per trasferire un array di numeri di contratto alla nostra query.

MDSYS.SDO_ELEM_INFO_ARRAY viene utilizzato perché se utilizziamo questo tipo Oracle già predefinito non dobbiamo definire il nostro tipo Oracle. Puoi inserire MDSYS.SDO_ELEM_INFO_ARRAY con un massimo di 1048576 numeri.

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

L'indice su contract.contractnum non viene utilizzato quando si omette suggerimento / * + cardinalità (scheda 10) * /. Supponevo che contractnum fosse la chiave primaria, quindi questa colonna verrà indicizzata.

Vedi anche qui: http://forums.oracle.com/ forum / thread.jspa? messageID = 3869879 # 3869879

Per utilizzare il parametro con l'istruzione IN è possibile utilizzare questa costruzione:

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

dove ContractNum è il tipo di array personalizzato.

So che questa è una vecchia domanda ma è una delle molte in cui la risposta selezionata non ha risolto il mio problema e non voglio iniziare ancora un altro thread su questo argomento, quindi metterò giù quello che ho trovato nei miei viaggi nella speranza che possa aiutare qualcuno.

Non lavoro molto con Oracle ma, come in SQL Server, sembra che per passare un parametro con valori di tabella sia necessario disporre di un UDT corrispondente (tabella definita dall'utente) a cui si disponga delle autorizzazioni EXECUTE (potrei essere sbagliato). Ciò significa che altre risposte che suggeriscono l'uso di un SYS UDT integrato vengono fornite con alcune merci e non sono riuscito a capire se è davvero possibile passare una tabella a qualcosa che non è una procedura memorizzata PL / SQL nella versione corrente di ODP.net.

In secondo luogo, la soluzione string-parse è un kludge per tutte le ovvie ragioni (non è possibile memorizzare nella cache il piano di esecuzione o come Oracle lo chiama, non si adatta bene, ecc.)

Quindi ho trascorso un bel po 'di tempo a provare a fare la clausola IN usando un parametro con valori di tabella su un datamart a cui ho solo l'autorizzazione READ prima di essere colpito da un lampo accecante dell'ovvio ( In un forum ASP.net non meno ). Scopre che Oracle supporta le query Xml "in modo nativo", quindi invece di passare una matrice di valori è possibile passare un elenco xml (se è tutto ciò che serve). Ancora una volta, potrei sbagliarmi, ma viene gestito come un parametro di bind legittimo e questo è un esempio di quanto sia semplice da usare (vb.net, ADO.net, ODP.net usando il pacchetto NuGet):

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

Questa è più un'osservazione che una soluzione attentamente studiata, quindi commenta se c'è qualcosa di inappropriato nel farlo in questo modo.

EDIT. Apparentemente c'è un limite di 4000 caratteri usando questo metodo (2000 se NVARCHAR) quindi ho dovuto guardare il mio paging. Il messaggio di errore informativo che ricevi se vai oltre è 'ORA-01460: richiesta conversione non implementata o irragionevole'

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top