Работа с СУБД 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.