Работа с СУБД Oracle через интерфейс OCCI - Работа с параметризованными запросами

ОГЛАВЛЕНИЕ


Работа с параметризованными запросами

Обычно запросы содержат переменные в своем составе, например в предложении WHERE. Для эффективной работы с такими запросами используются параметризованные запросы. Конечно, запрос может быть и не параметризованным - собираться программой динамически, а затем подаваться для создания Statement - однако это не самый легкий(применительно к С++) и что самое главное далеко не оптимальный подход.

Если вы разрабатываете приложения под Oracle то скорее всего в курсе некоторых вопросов повышения производительности приложений, одним из которых является применение связываемых переменных. Использование связываемых переменных уменьшает количество жестких разборов sql-выражений, снижает нагрузку на разделяемый пул и в итоге может значительно увеличить скорость работы. Аналогичная ситуация с многократным использованием курсора - уменьшается число мягких разборов и т.д. - речь сейчас не об этом. Должен добавить только что в случае если вы узнали о работе со связываемыми переменными из того, что я написал выше - скорее всего вам следует почитать Т. Кайта "Oracle для профессионалов", главу "Стратегии и средства настройки".

Вернемся к делу - параметризованный запрос это sql-выражение на месте переменных(не обязательно всех) стоят идентификаторы подставляемых значений - ":1", ":var", ":data1". Перед использованием их надо связать с конкретными величинами - это производится вызовом метода stmt->setXXX(setString, setInt, ….) . Для более скоростной обработки можно использовать stmt->setDataBuffer , отличие этого метода от setXXX в том что методы setXXX копируют содержимое источника в выделяемый OCCI буфер что может снизить производительность приложений при больших размерах источника данных. В противоположность setXXX setDataBuffer использует в качестве источника непосредственно переданный параметр, без выделения памяти и копирования. Если вы используете setXXX - сразу после вызова метода можно менять источник, что недопустимо при использовании setDataBuffer

Вот участок кода иллюстрирующий вышесказанное:

// некоторые объявления параметров NLS
#define number_template "99999999999999999999"
#define number_nls "NLS_NUMERIC_CHARACTERS = \"dg\""
#define dur_number_template "99999999.999"
#define dur_number_nls "NLS_NUMERIC_CHARACTERS = \".,\""
#define date_template "yyyymmddhh24miss"
#define date_nls "NLS_DATE_LANGUAGE = American"

/*
Ранее введенные объекты
Environment* env;
Connection* conn; 
*/

if (conn)
    {
    try
      {
      /*
      Создаем выражение 
      */
      Statement *stmt = conn->createStatement("INSERT /*+ APPEND */ INTO cdrs
      (CDRsequenceNumber, CallingAddress, CallingAddress_NAI, IncomingCalledAddress, IncomingCalledAddress_NAI,
      OutgoingCalledAddress, OutgoingCalledAddress_NAI, StartTimeStamp, AlertingTimeStamp, AnswerTimeStamp, ReleaseTimeStamp,
      CallDuration, CauseIndicator, InSigAddr_OPC, InSigAddr_DPC, InSigAddr_CIC, InSigAddr_slote, InSigAddr_ds, InSigAddr_card,
      InSigAddr_timeslote, InSigAddr_gateway, OutSigAddr_OPC, OutSigAddr_DPC, OutSigAddr_CIC, OutSigAddr_slote, OutSigAddr_ds,
      OutSigAddr_card,OutSigAddr_timeslote,OutSigAddr_gateway,OctSent,OctRxd,PktLost,
      Jitter,Latency,CallingPartyCategory,ConnectedNumber,ConnectedNumber_NAI)
      VALUES(:1,:2,:3,:4,:5,:6,:7,:8,:9,:10,:11,:12,:13,:14,:15,:16,:17,:18,:19,:20,:21,:22,:23,
      :24,:25,:26,:27,:28,:29,:30,:31,:32,:33,:34,:35,:36,:37)");
      // Отключаем автоматическую фиксацию транзакции, вставляем все в рамках одной транзакции
      stmt->setAutoCommit(false);
      /*
      Новые объекты OCCI(представляют типы данных базы)
      */
      Number CDRsequenceNumber = 0;
      Number CallingAddress = 0;
      Number IncomingCalledAddress = 0;
      Number OutgoingCalledAddress = 0;
      Date   StartTimeStamp(env);
      Date   AlertingTimeStamp(env);
      Date   AnswerTimeStamp(env);
      Date   ReleaseTimeStamp(env);
             
      // Контейнер с указателями на структуры данных которые будут помещаться в базу
      deque::iterator p = c_cdrs.begin();
      while (p != c_cdrs.end())
        {
        /*
        Обнуляем значения чисел 
        */
        CDRsequenceNumber.setNull();
        CallingAddress.setNull();
        IncomingCalledAddress.setNull();
        OutgoingCalledAddress.setNull();
        …

        if (strlen((*p)->CDRsequenceNumber) > 0)
          {
          CDRsequenceNumber = 0;
          CDRsequenceNumber.fromText(env,(*p)->CDRsequenceNumber,number_template,number_nls);
          }
     
        if (strlen((*p)->StartTimeStamp) > 0)
          {
          StartTimeStamp.setDate();
          StartTimeStamp.fromText((*p)->StartTimeStamp,date_template,date_nls,env);
          }
         
        …
        …
        /*
        Прямое назначение обьекту Number значение через оператор =
        */
        OctSent = (*p)->OctSent;
        OctRxd = (*p)->OctRxd;
        PktLost = (*p)->PktLost;
                   
        /*
        Устанавливаем переменные в курсор 
        */
        stmt->setNumber(1,CDRsequenceNumber);
        stmt->setNumber(2,CallingAddress);
        stmt->setInt(3,(*p)->CallingAddress_NAI);
        stmt->setNumber(4,IncomingCalledAddress);
        stmt->setInt(5,(*p)->IncomingCalledAddress_NAI);
        stmt->setNumber(6,OutgoingCalledAddress);
        stmt->setInt(7,(*p)->OutgoingCalledAddress_NAI);
       
        stmt->setDate(8,StartTimeStamp);
        stmt->setDate(9,AlertingTimeStamp);
        stmt->setDate(10,AnswerTimeStamp);
        stmt->setDate(11,ReleaseTimeStamp);
       
        stmt->setDouble(12,(*p)->CallDuration);
        stmt->setInt(13,(*p)->CauseIndicator);
        …
        …
       
        stmt->setInt(22,(*p)->OutSigAddr_OPC);
        stmt->setInt(23,(*p)->OutSigAddr_DPC);

        /* Выполнение курсора и повторное его использование */
        stmt->executeUpdate();
        p++;
        } // while
      conn->commit();
      conn->terminateStatement(stmt);
      }
    // ловим исключения при возникшие в процессе работы с базой
    catch(SQLException &sqlExcp)
      {
      conn->rollback();
      cerr << sqlExcp.getMessage() << endl;
      }

Некоторые примечания по коду:

  • stmt->setAutoCommit(false); - для информации, чтобы не было автоматических фиксаций транзакций при каждом выполнении, по умолчанию стоит false.
  • Переменные в Statement* присваиваются по порядку следования в выражении!!!. В случае если вы напишите, к примеру
    "Insert into test(field_1,field_2) values(:2,:1)" и прикрепите переменные используя
    setInt(1,10);
    setInt(2,20);
    переменная с именем :2 получит значение 10, c именем :1 - соответственно 20
  • Особое внимание следует уделить методу fromText - он очень полезен если в базу нужно ввести число, например типа unsigned long long - прямое присваивание в этом случае не возможно, удобно использовать метод fromText. Нужно помнить что он не срабатывает после того как к объекту применялся метод setNull() - сначала нужно инициализировать объект просто присвоив ему начальное значение. С типом Date аналогичная ситуация, кроме того env должен быть создан с параметром OBJECT. Также для работы метода требуется явное указание параметров nlsParam.
  • При присоединении данных переменной длины к повторно используемому курсору(например строк) выделяйте изначально достаточно места в буфере вызывая setString c параметром максимальной длины. Например:
    первая итерация
    stmt->setString(1,"123");
    вторая итерация
    stmt->setString(1,"1234");
    ORA-01461 can bind a LONG value only for insert into a LONG column
    Cause: An attempt was made to insert a value from a LONG datatype into another datatype. This is not allowed.
    Action: Do not try to insert LONG datatypes into other types of columns.