Параметры Oracle с оператором IN? - PullRequest
7 голосов
/ 26 октября 2009

Получил приложение на c # .net, которое мне нужно изменить. Запрос на данный момент эффективно делает это:

select * from contract where contractnum = :ContractNum

(очень упрощенно, просто чтобы показать, что мы используем = и один параметр)

Этот параметр считывается из файла Settings.Settings в приложении C # и содержит одну строку. Мне нужно изменить его, чтобы включить несколько контрактов, поэтому я решил, что я могу изменить SQL на:

select * from contract where contractnum in (:ContractNum)

но это не дает результатов, независимо от того, как я отформатирую строку в параметре.

Есть ли способ заставить оракула сделать IN с параметром?

любая помощь приветствуется, спасибо всем.

Ответы [ 5 ]

6 голосов
/ 26 октября 2009

вы можете использовать конвейерную функцию для преобразования строки в таблицу, которую можно использовать с оператором 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)))
6 голосов
/ 01 ноября 2009

Вы можете использовать коллекцию чисел Oracle в качестве параметра (переменную связывания), когда используете ODP.NET в качестве поставщика данных. Это работает с сервером Oracle 9, 10 или 11 и выпуском ODP.net> = 11.1.0.6.20.

Подобное решение возможно, если вы используете поставщик данных .NET для Devart.

Давайте выберем контракты с номерами контрактов 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();
    }
  }
}

Индекс для contract.contractnum не используется, если не указывать подсказку / * + кардинальность (таб. 10) * /. Я предположил, что contractnum является первичным ключом, поэтому этот столбец будет проиндексирован.

Смотрите также здесь: http://forums.oracle.com/forums/thread.jspa?messageID=3869879#3869879

2 голосов
/ 26 октября 2009

Еще предстоит найти базу данных, которая поддерживает оценку одной строковой переменной, содержащей запятые, для разделения в качестве единственного предложения IN.

Ваши варианты заключаются в подстроке переменной, чтобы содержимое переменной с разделителями-запятыми превращалось в строки, чтобы вы могли присоединиться к ней. Или использовать динамический SQL, который является оператором SQL, созданным в виде строки в sproc перед выполнением оператора.

0 голосов
/ 21 мая 2019

Я знаю, что это старый вопрос, но один из нескольких, в котором выбранный ответ не решил мою проблему, и я не хочу начинать еще одну ветку по этой теме, поэтому я просто изложу то, что нашел в моих путешествиях в надежде, что это кому-нибудь поможет.

Я не очень много работаю с Oracle, но, как и в SQL Server, кажется, что для передачи табличного параметра необходим соответствующий UDT (пользовательская таблица), на который у вас есть разрешения EXECUTE (я мог бы быть неправильно). Это означает, что другие ответы, предполагающие использование встроенного SYS UDT, идут с некоторым грузом, и я не мог понять, действительно ли возможно передать таблицу чему-то, что не является хранимой процедурой PL / SQL в текущей версии ODP.net.

Во-вторых, решение для разбора строк является препятствием по всем очевидным причинам (невозможно кэшировать план выполнения или как его называет Oracle, плохо масштабируется и т. Д.).

Таким образом, я потратил довольно много времени, пытаясь выполнить предложение IN, используя табличный параметр в datamart, на который у меня есть только разрешение READ, прежде чем меня поразила ослепительная вспышка очевидного ( В Форум ASP.net не менее ). Оказывается, Oracle поддерживает Xml-запросы «изначально», поэтому вместо передачи массива значений вы можете передать список XML (если это все, что вам нужно). Опять же, я могу ошибаться, но он обрабатывается как допустимый параметр связывания, и это пример того, как его просто использовать (vb.net, ADO.net, ODP.net с использованием пакета 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()

Это скорее наблюдение, чем тщательно изученное решение, поэтому, пожалуйста, прокомментируйте, если есть что-то неуместное в этом.

EDIT. При использовании этого метода, по-видимому, существует ограничение в 4000 символов (2000, если NVARCHAR), поэтому мне пришлось наблюдать за своей страницей. Информативное сообщение об ошибке, которое вы получите, если перейти, «ORA-01460: запрошено невыполненное или необоснованное преобразование» *

0 голосов
/ 07 августа 2013

Для использования параметра с оператором IN вы можете использовать эту конструкцию:

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

где ContractNum - тип пользовательского массива.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...