Interbase BLOB-поля

SQL

InterBase BLOB-поля отличаются от полей другого типа. Реально BLOB-поле имеет несколько подтипов (sub-type). Знание подтипа BLOB-поля существенно при создании приложения для работы с базами данных, которые включают в себя InterBase BLOB-поля. BLOB-поля могут быть трех подтипов: подтип 0, подтип 1 (два встроенных подтипа), и пользовательский подтип.

Подтип 0 BLOB-поля создается при выполнении команды CREATE, когда подтип не определен. Для ясности, в синтаксисе SQL все же рекомендуется явно указывать, что BLOB-поле относится к подтипу 0. Данный подтип BLOB-поля используется для хранения бинарных данных. InterBase не производит никакого анализа хранимых данных, он просто хранит данные в BLOB-поле байт-за-байтом. Наиболее частое применение BLOB-полей в приложениях Windows - хранение двоичных данных изображения, обычно отображаемое впоследствие компонентом TDBImage. Для этой цели подходит или BLOB-поле подтипа 0, или BLOB-поле пользовательского подтипа.

Второй встроенный подтип - 1. Данный подтип BLOB-поля разрабатывался для хранения текста. Обычно это данные свободного формата типа memo или заметок, отображаемых и редактируемых компонентом TDBMemo. Данный подтип BLOB-поля лучше подходит для хранения данных типа текст, чем поле, имеющее тип VARCHAR, поскольку, в отличие от поля типа VARCHAR, в режиме проектирования возможно задание ограничения по используемой областью памяти.

С помощью SQL-синтаксиса, подтип 1 BLOB-поля создается с указанием типа BLOB-поля, использованием ключевого слова SUB_TYPE и числа, указывающего на номер необходимого подтипа:

CREATE TABLE WITHBLOB  
(  
  ID CHAR(3) NOT NULL PRIMARY KEY,  
  MEMO BLOB SUB_TYPE 1,  
  AMOUNT NUMERIC  
)

Помимо двух встроенных подтипов BLOB-поля, существует также подтип, определяемый пользователем. Такой подтип задается отрицательным целым значением совместно с ключевым словом SUB_TYPE. Фактически учитывается только "отрицательность" целого числа, его значение может быть произвольным и остается на усмотрение того, кто создает таблицу. Указание числа -1 идентично указанию числа -2. Единственная рекомендация для использования подтипа, определяемого пользователем - гарантия того, что в каждой строке таблицы BLOB-поле будет иметь только данный подтип, определяемый пользователем. InterBase не имеет критерия для оценки хранимого подтипа, поэтому вся ответственность по определению подходящего типа двоичных данных ложится на приложение. Никакой ошибки со стороны InterBase при загрузке неверного типа двоичных данных в пользовательский подтип BLOB-поля быть не может, но приложение может столкнуться с трудностями, если оно ожидает один тип данных, но ей передают другой.

BLOB-поле пользовательского подтипа может создаваться следующим синтаксисом SQL:
CREATE TABLE IMAGE_DATA  
(  
  FILENAME CHAR(12) NOT NULL PRIMARY KEY,  
  BITMAP BLOB SUB_TYPE -1,  
  EXEs BLOB SUB_TYPE -2,  
)

При пользовании таблицей, созданной с помощью приведенной выше команды, поле BITMAP может использоваться для хранения одного типа двоичных данных для всех записей. В нашем случае хранятся данные изображения. Поле EXEs подразумевает хранение выполнимых файлов, загружаемых с диска. Если приложение, использующее данную таблицу, по ошибке сохранит двоичные данные в поле BITMAP вместо EXEs, InterBase ошибки не выдаст, но приложение при этом столкнется с серьезными трудностями при отображении в компоненте TDBImage сохраненного выполнимого файла.

InterBase BLOB-поля и Delphi

При определении объектов TField для InterBase BLOB-полей в Delphi, следует относить различные подтипы BLOB-поля к производным типам TField следующим образом:
Подтип 0: TBlobField
Подтип 1: TMemoField
Пользовательский: TBlobField

Поскольку, как встроенный подтип 0, так и пользовательский подтип, относятся к объектам TBlobField, то забота об определении используемого подтипа во время проектирования приложения ложится на программиста. Единственный способ отличить подтип 0 от пользовательского подтипа заключается в просмотре информации о метаданных таблицы, что не может быть сделано с помощью Delphi. Для просмотра метаданных таблицы может быть использована утилита Local InterBase Server под названием WISQL.

InterBase BLOB-поля и Database Desktop

Утилита Database Desktop, поставляемая с Delphi (DBD), не создает пользовательские подтипы. При создании в Database Desktop BLOB-полей для хранения бинарных данных, включая данные изображения, используйте тип поля "BLOB". Этим вы создадите BLOB-поле встроенного подтипа 0.

В DBD также возможно создание BLOB-поля типа TEXT BLOB. Это эквивалент встроенного подтипа 1 и может использоваться для хранения текста свободного формата. Так как он только функционален встроенному подтипу 1 BLOB-поля, то при просмотре таблицы утилитой WISQL, обозначение его подтипа может отличаться от действительного.

Добавлено: 11 Апреля 2018 08:17:37 Добавил: Андрей Ковальчук

Генератор SQL-запросов Insert/Update

SQL

Вам ещё не надоело динамически генерировать SQL запросы insert и update ? Давайте посмотрим, как можно раз и навсегда упростить этот процесс.

Совместимость: Delphi 3.x (или выше)

Допустим Вы создавали запрос следующим образом (типы параметров Data1:string Data2: integer Data3:TdateTime)

SqlCmd := 'insert into MyTable (Field1,Field2,Field2) values (' +    
                QuotedStr(Data1) + ',' + IntToStr(Data2) + ',' + 'to_date('  
                + QuotedStr(FormatdateTime('dd/mm/yyyy',Data3)) + ','  
                + QuotedStr('dd/mm/yyyy') + '))';  
  
{Ужасно! ещё хуже, когда количество колонок увеличивается}

А если сделать функцию типа ..
SqlCmd := SqlInsert([Data1,Data2,Variant(Data3)],  
                   'MyTable',  
                   ['Field1','Field2','Field3']);

она эмулирует строку запроса наподобие ..
insert into MyTable  
(Fields1,Field2,Field3)  
values ('Sweets',934,to_date('21/05/2001','dd/mm/yyyy')) 

неправда ли она более проста в использовании ?

Здесь представлены функции SqlInsert и SqlUpdate. Вы наверное заметили, что я передаю TDateTime приведённый как Variant. Причина кроется в том, что VType в array of const не имеете TDateTime типа и даты просто представлены как vtExtended.

Функция SqlInsert имеет 2 переопределённых вызова, которые позволяют Вам включить или выполнить массив имён колонок.

Посмотрим, как выглядят эти функции:
interface  
  
const CrLf = #13#10;  // Возврат и перевод каретки  
  
// Прототипы функций  
  
function SqlInsert(Values : array of const;  
                   TableName : string;  
                   ColNames : array of string) : string; overload;  
  
function SqlInsert(Values : array of const;  
                   TableName : string) : string; overload;  
  
function SqlUpdate(Values : array of const;  
                   TableName : string;  
                   ColNames : array of string;  
                   WhereClause : string) : string;  
  
// ---------------------------------------------------------------------------  
implementation  
  
// Помещаем TDateTime в Values (array of const)  
// Представлен как Variant  
  
function SqlInsert(Values : array of const;  
                   TableName : string;  
                   ColNames : array of string) : string;  
var RetVar : string;  
    i : integer;  
begin  
  RetVar := 'insert into ' + TableName + CrLf +  
            '(' + ColNames[0];  
  for i := 1 to High(ColNames) do  
     RetVar := RetVar + ',' + ColNames[i];  
  RetVar := RetVar + ')' + CrLf;  
  
  RetVar := RetVar + 'values (';  
  
  for i := 0 to High(Values) do begin  
     case Values[i].VType of  
          vtInteger,  
          vtInt64    : RetVar := RetVar + IntToStr(Values[i].VInteger);  
          vtChar       : RetVar := RetVar + QuotedStr(Values[i].VChar);  
          vtString   : RetVar := RetVar + QuotedStr(Values[i].VString^);  
          vtPChar    : RetVar := RetVar + QuotedStr(Values[i].VPChar);  
          vtExtended : RetVar := RetVar + FloatToStr(Values[i].VExtended^);  
          vtAnsiString : RetVar := RetVar +  
                         QuotedStr(string(Values[i].VAnsiString));  
          // TDateTime - иначе получаем как vtExtended  
          vtVariant  : RetVar := RetVar + 'to_date(' +  
                       QuotedStr(FormatdateTime('dd/mm/yyyy',  
                       TDateTime(Values[i].VVariant^))) + ',' +  
                       QuotedStr('dd/mm/yyyy') + ')';  
     else  
       RetVar := RetVar + '??????';  
     end;  
  
     RetVar := RetVar + ',';  
  end;  
  
  Delete(RetVar,length(RetVar),1);  
  RetVar := RetVar + ')';  
  if High(Values) < High(ColNames) then  
     ShowMessage('SQL Insert - Not enough values.');  
  if High(Values) > High(ColNames) then  
     ShowMessage('SQL Insert - Too many values.');  
  
  Result := RetVar;  
end;  
  
  
function SqlInsert(Values : array of const;  
                   TableName : string) : string; overload;  
var RetVar : string;  
    i : integer;  
begin  
  RetVar := 'insert into ' + TableName + CrLf;  
  RetVar := RetVar + 'values (';  
  
  for i := 0 to High(Values) do begin  
     case Values[i].VType of  
          vtInteger,  
          vtInt64    : RetVar := RetVar + IntToStr(Values[i].VInteger);  
          vtChar       : RetVar := RetVar + QuotedStr(Values[i].VChar);  
          vtString   : RetVar := RetVar + QuotedStr(Values[i].VString^);  
          vtPChar    : RetVar := RetVar + QuotedStr(Values[i].VPChar);  
          vtExtended : RetVar := RetVar + FloatToStr(Values[i].VExtended^);  
          vtAnsiString : RetVar := RetVar +  
                         QuotedStr(string(Values[i].VAnsiString));  
          // TDateTime - иначе получаем как vtExtended  
          vtVariant  : RetVar := RetVar + 'to_date(' +  
                       QuotedStr(FormatdateTime('dd/mm/yyyy',  
                       TDateTime(Values[i].VVariant^))) + ',' +  
                       QuotedStr('dd/mm/yyyy') + ')';  
     else  
       RetVar := RetVar + '??????';  
     end;  
  
     RetVar := RetVar + ',';  
  end;  
  
  Delete(RetVar,length(RetVar),1);  
  RetVar := RetVar + ')';  
  
  Result := RetVar;  
end;  
  
  
function SqlUpdate(Values : array of const;  
                   TableName : string;  
                   ColNames : array of string;  
                   WhereClause : string) : string;  
var RetVar,Parm : string;  
    i : integer;  
begin  
  RetVar := 'update ' + TableName + ' set' + CrLf;  
  
  for i := 0 to Min(High(Values),High(ColNames)) do begin  
     case Values[i].VType of  
          vtInteger,  
          vtInt64    : Parm := IntToStr(Values[i].VInteger);  
          vtChar       : Parm := QuotedStr(Values[i].VChar);  
          vtString   : Parm := QuotedStr(Values[i].VString^);  
          vtPChar    : Parm := QuotedStr(Values[i].VPChar);  
          vtExtended : Parm := FloatToStr(Values[i].VExtended^);  
          vtAnsiString : Parm := QuotedStr(string(Values[i].VAnsiString));  
          // TDateTime - иначе получаем как vtExtended  
          vtVariant  : Parm := 'to_date(' +  
                       QuotedStr(FormatdateTime('dd/mm/yyyy',  
                       TDateTime(Values[i].VVariant^))) + ',' +  
                       QuotedStr('dd/mm/yyyy') + ')';  
     else  
       Parm := '??????';  
     end;  
  
     RetVar := RetVar + ColNames[i] + '=' + Parm + ',';  
  end;  
  
  Delete(RetVar,length(RetVar),1);  
  RetVar := RetVar + CrLf + 'where ' + WhereClause;  
  if High(Values) < High(ColNames) then  
     ShowMessage('SQL Update - Not enough values.');  
  if High(Values) > High(ColNames) then  
     ShowMessage('SQL Update - Too many values.');  
  
  Result := RetVar;  
end;

Добавлено: 11 Апреля 2018 08:14:10 Добавил: Андрей Ковальчук

SQL-запросы в Delphi

SQL

1. Введение
Компоненты Delphi для работы с базами данных были созданы в расчете на работу с SQL и архитектурой клиент/сервер. При работе с ними вы можете воспользоваться характеристиками расширенной поддержки удаленных серверов. Delphi осуществляет эту поддержку двумя способами. Во-первых, непосредственные команды из Delphi позволяют разработчику управлять таблицами, устанавливать пределы, удалять, вставлять и редактировать существующие записи. Второй способ заключается в использовании запросов на языке SQL, где строка запроса передается на сервер для ее разбора, оптимизации, выполнения и передачи обратно результатов.

Данный документ делает акцент на втором методе доступа к базам данных, на основе запросов SQL (pass-through). Авторы не стремились создать курсы по изучению синтаксиса языка SQL и его применения, они ставили перед собой цель дать несколько примеров использования компонентов TQuery и TStoredProc. Но чтобы сделать это, необходимо понимать концепцию SQL и знать как работают selects, inserts, updates, views, joins и хранимые процедуры (stored procedures). Документ также вскользь касается вопросов управления транзакциями и соединения с базой данных, но не акцентирует на этом внимание. Итак, приступая к теме, создайте простой запрос типа SELECT и отобразите результаты.

2. Компонент TQuery
Если в ваших приложениях вы собираетесь использовать SQL, то вам непременно придется познакомиться с компонентом TQuery. Компоненты TQuery и TTable наследуются от TDataset. TDataset обеспечивает необходимую функциональность для получения доступа к базам данных. Как таковые, компоненты TQuery и TTable имеют много общих признаков. Для подготовки данных для показа в визуальных компонентах используется все тот же TDatasource. Также, для определения к какому серверу и базе данных необходимо получить доступ, необходимо задать имя псевдонима. Это должно выполняться установкой свойства aliasName объекта TQuery.

Свойство SQL

Все же TQuery имеет некоторую уникальную функциональность. Например, у TQuery имеется свойство с именем SQL. Свойство SQL используется для хранения SQL-запроса. Ниже приведены основные шаги для составления запроса, где все служащие имеют зарплату свыше $50,000.

Создайте объект TQuery

Задайте псевдоним свойству DatabaseName. (Данный пример использует псевдоним IBLOCAL, связанный с демонстрационной базой данных employee.gdb).

Выберите свойство SQL и щелкните на кнопке с текстом - '...' (три точки, Инспектор Объектов - В.О.). Должен появиться диалог редактора списка строк (String List Editor).

Введите:

Select * from EMPLOYEE where SALARY>50000. Нажмите OK.


Выберите в Инспекторе Объектов свойство Active и установите его в TRUE.

Разместите на форме объект TDatasource.

Установите свойство Dataset у TDatasource в Query1.

Разместите на форме TDBGrid.

Установите его свойство Datasource в Datasource1.
Свойство SQL имеет тип TStrings. Объект TStrings представляет собой список строк, и чем-то похож на массив. Тип данных TStrings имеет в своем арсенале команды добавления строк, их загрузки из текстового файла и обмена данными с другим объектом TStrings. Другой компонент, использующий TStrings - TMemo. В демонстрационном проекте ENTRSQL.DPR (по идее, он должен находится на отдельной дискетте, но к "Советам по Delphi" она не прилагается - В.О.), пользователь должен ввести SQL-запрос и нажать кнопку "Do It" ("сделать это"). Результаты запроса отображаются в табличной сетке. В Листинге 1 полностью приведен код обработчика кнопки "Do It".

Листинг 1
procedure TForm1.BitBtn1Click(Sender: TObject);  
begin  
    Query1.close; {Деактивируем запрос в качестве одной из мер предосторожности }    
    Query1.SQL.Clear; {Стираем любой предыдущий запрос}    
    If Memo1.Lines[0] <> '' {Проверяем на предмет пустого ввода} then    
      Query1.SQL.Add(Memo1.Text) {Назначаем свойству SQL текст Memo}    
    else    
      
    begin  
   
      
    messageDlg('Не был введен SQL-запрос', mtError, [mbOK], 0);    
    exit;    
      
    end;    
      
    try  
        {перехватчик ошибок}    
      
    Query1.Open;     {Выполняем запрос и открываем набор данных}  
   
      
    except  
     {секция обработки ошибок}    
      
    On e : EDatabaseError do  
 {e - новый дескриптор ошибки}    
      
    messageDlg(e.message, mtError, [mbOK],0); {показываем свойство message объекта e}    
      
    end;      {окончание обработки ошибки}    
      
end;

Свойство Params

Этого должно быть достаточно для пользователя, знающего SQL. Тем не менее, большинство пользователей не знает этого языка. Итак, ваша работа как разработчика заключается в предоставлении интерфейса и создании SQL-запроса. В Delphi, для создания SQL-запроса на лету можно использовать динамические запросы. Динамические запросы допускают использование параметров. Для определения параметра в запросе используется двоеточие (:), за которым следует имя параметра. Ниже приведе пример SQL-запроса с использованием динамического параметра:
select * from EMPLOYEE  where DEPT_NO = :Dept_no   

Если вам нужно протестировать, или установить для параметра значение по умолчанию, выберите свойство Params объекта Query1. Щелкните на кнопке '...'. Должен появиться диалог настройки параметров. Выберите параметр Dept_no. Затем в выпадающем списке типов данных выберите Integer. Для того, чтобы задать значение по умолчанию, введите нужное значение в поле редактирования "Value".
Для изменения SQL-запроса во время выполнения приложения, параметры необходимо связать (bind). Параметры могут изменяться, запрос выполняться повторно, а данные обновляться. Для непосредственного редактирования значения параметра используется свойство Params или метод ParamByName. Свойство Params представляет из себя массив TParams. Поэтому для получения доступа к параметру, необходимо указать его индекс. Для примера,
Query1.params[0].asInteger := 900;   

Свойство asInteger читает данные как тип Integer (название говорит само за себя). Это не обязательно должно указывать но то, что поле имеет тип Integer. Например, если тип поля VARCHAR(10), Delphi осуществит преобразование данных. Так, приведенный выше пример мог бы быть записан таким образом:
Query1.params[0].asString := '900';   

или так:
Query1.params[0].asString := edit1.text;   

Если вместо номера индекса вы хотели бы использовать имя параметра, то воспользуйтесь методом ParamByName. Данный метод возвращает объект TParam с заданным именем. Например:
Query1.ParamByName('DEPT_NO').asInteger := 900;   

В листинге 2 приведен полный код примера.

Листинг 2
procedure TForm1.BitBtn1Click(Sender: TObject);  
begin  
    Query1.close;     {Деактивируем запрос в качестве одной из мер предосторожности }  
   
    if not Query1.prepared then    
      Query1.prepare; {Убедимся что запрос подготовлен}    
      {Берем значение, введенное пользователем и заменяем   
      им параметр.}    
    if edit1.text <> '' then {Проверяем на предмет пустого ввода}    
      Query1.ParamByName('DEPT_NO').AsString := edit1.text    
    else    
      Begin    
        Query1.ParamByName('DEPT_NO').AsInteger := 0;    
        edit1.text := '0';    
      end;    
    try        {перехватчик ошибок}    
      Query1.Open;     {Выполняем запрос и открываем набор данных}    
    except     {секция обработки ошибок}    
      On e : EDatabaseError do {e - новый дескриптор ошибки} messageDlg(e.message,    
         mtError,    
         [mbOK],0); {показываем свойство message объекта e}    
    end;     {окончание обработки ошибки}    
      
end;

Обратите внимание на процедуру, первым делом подготовливающую запрос. При вызове метода prepare, Delphi посылает SQL запрос на удаленный сервер. Сервер выполняет грамматический разбор и оптимизацию запроса. Преимущество такой подготовки запроса состоит в его предварительном разборе и оптимизации. Альтернативой здесь может служить подготовка сервером запроса при каждом его выполнении. Как только запрос подготовлен, подставляются необходимые новые параметры, и запрос выполняется.

Источник данных

В предыдущем примере пользователь мог ввести номер отдела, и после выполнения запроса отображался список сотрудников этого отдела. А как насчет использования таблицы DEPARTMENT, позволяющей пользователю легко перемещаться между пользователями и отделами?

Примечание: Следующий пример использует TTable с именем Table1. Для Table1 имя базы данных IBLOCAL, имя таблицы - DEPARTMENT. DataSource2 TDatasource связан с Table1. Таблица также активна и отображает записи в TDBGrid.

Способ подключения TQuery к TTable - через TDatasource. Есть два основных способа сделать это. Во-первых, разместить код в обработчике события TDatasource OnDataChange. Например, листинг 3 демонстрирует эту технику.

Листинг 3 - Использования события OnDataChange для просмотра дочерних записей
procedure TForm1.DataSource2DataChange(Sender: TObject; Field: TField);  
begin  
    Query1.Close;    
    if not Query1.prepared    
    then    
    Query1.prepare;    
    Query1.ParamByName('Dept_no').asInteger := Table1Dept_No.asInteger;    
    try    
     Query1.Open;    
    except    
    On e : EDatabaseError do    
    messageDlg(e.message, mtError, [mbOK], 0);    
    end;    
      
end;   

Техника с использованием OnDataChange очень гибка, но есть еще легче способ подключения Query к таблице. Компонент TQuery имеет свойство Datasource. Определяя TDatasource для свойства Datasource, объект TQuery сравнивает имена параметров в SQL-запросе с именами полей в TDatasource. В случае общих имен, такие параметры заполняются автоматически. Это позволяет разработчику избежать написание кода, приведенного в листинге 3 (*** приведен выше ***).

Фактически, техника использования Datasource не требует никакого дополнительного кодирования. Для поключения запроса к таблице DEPT_NO выполните действия, приведенные в листинге 4.

Листинг 4 - Связывание TQuery c TTable через свойство Datasource

Выберите у Query1 свойство SQL и введите:
select * from EMPLOYEE  where DEPT_NO = :dept_no  

Выберите свойство Datasource и назначьте источник данных, связанный с Table1 (Datasource2 в нашем примере)
Выберите свойство Active и установите его в True

Это все, если вы хотите создать такой тип отношений. Тем не менее, существуют некоторые ограничения на параметризованные запросы. Параметры ограничены значениями. К примеру, вы не можете использовать параметр с именем Column или Table. Для создания запроса, динамически изменяемого имя таблицы, вы могли бы использовать технику конкатенации строки. Другая техника заключается в использовании команды Format.

Команда Format

Команда Format заменяет параметры форматирования (%s, %d, %n и пр.) передаваемыми значениями. Например,
Format('Select * from %s', ['EMPLOYEE'])

Результатом вышеприведенной команды будет 'Select * from EMPLOYEE'. Функция буквально делает замену параметров форматирования значениями массива. При использовании нескольких параметров форматирования, замена происходит слева направо. Например,
tblName := 'EMPLOYEE';  
fldName := 'EMP_ID';  
fldValue := 3;  
Format('Select * from %s where %s=%d', [tblName, fldName, fldValue])

Результатом команды форматирования будет 'Select * from EMPLOYEE where EMP_ID=3'. Такая функциональность обеспечивает чрезвычайную гибкость при динамическом выполнении запроса. Пример, приведенный ниже в листинге 5, позволяет вывести в результатах поле salary. Для поля salary пользователь может задавать критерии.

Листинг 5 - Использование команды Format для создания SQL-запроса
procedure TForm1.BitBtn1Click(Sender: TObject);  
var  
  sqlString: string; {здесь хранится SQL-запрос}  
  fmtStr1,  
    fmtStr2: string; {здесь хранится строка, передаваемая для форматирования}  
  
begin  
{ Создание каркаса запроса }  
  sqlString := 'Select EMP_NO %s from employee where SALARY %s';  
  
  if showSalaryChkBox.checked {Если checkbox Salary отмечен} then  
    fmtStr1 := ', SALARY'  
  else  
    fmtStr1 := '';  
  if salaryEdit.text <> '' { Если поле редактирования Salary не пустое } then  
    fmtStr2 := salaryEdit.text  
  else  
    fmtStr2 := '>0';  
  
  Query1.Close; {Деактивируем запрос в качестве одной из мер предосторожности }  
  Query1.SQL.Clear; {Стираем любой предыдущий запрос}  
  Query1.SQL.Add(Format(sqlString, [fmtStr1, fmtStr2])); {Добавляем}  
{форматированную строку к свойству SQL}  
  
  try {перехватчик ошибок}  
    Query1.Open; {Выполняем запрос и открываем набор данных}  
  except {секция обработки ошибок}  
    on e: EDatabaseError do {e - новый дескриптор ошибки}  
      messageDlg(e.message, mtError, [mbOK], 0);  
{показываем свойство message объекта e}  
  end; {окончание обработки ошибки}  
end;   

В этом примере мы используем методы Clear и Add свойства SQL. Поскольку "подготовленный" запрос использует ресурсы сервера, и нет никакой гарантии что новый запрос будет использовать те же таблицы и столбцы, Delphi, при каждом изменении свойства SQL, осуществляет операцию, обратную "подготовке" (unprepare). Если TQuery не был подготовлен (т.е. свойство Prepared установлено в False), Delphi автоматически подготавливает его при каждом выполнении. Поэтому в нашем случае, даже если бы был вызван метод Prepare, приложению от этого не будет никакой пользы.

Open против ExecSQL

В предыдущих примерах TQuerie выполняли Select-запросы. Delphi рассматривает результаты Select-запроса как набор данных, типа таблицы. Это просто один класс допустимых SQL-запросов. К примеру, команда Update обновляет содержимое записи, но не возвращает записи или какого-либо значения. Если вы хотите использовать запрос, не возвращающий набор данных, используйте ExecSQL вместо Open. ExecSQL передает запрос для выполнения на сервер. В общем случае, если вы ожидаете, что получите от запроса данные, то используйте Open. В противном случае допускается использование ExecSQL, хотя его использование с Select не будет конструктивным. Листинг 6 содержит код, поясняющий сказанное на примере.

Листинг 6
procedure TForm1.BitBtnClick(sender: TObject)  
begin  
  Query1.Close;  
  Query1.Clear;  
  Query1.SQL.Add('Update SALARY from EMPLOYEE ' +  
    'where SALARY<:salary values (SALARY*(1+:raise)');  
  Query1.paramByName('salary').asString := edit1.text;  
  Query1.paramByName('raise').asString := edit2.text;  
  try  
    Query1.ExecSQL;  
  except  
    on e: EDatabaseError do  
      messageDlg(e.message, mtError, [mbOK], 0);  
  end;  
end;   

Все приведенные выше примеры предполагают использования в ваших приложениях запросов. Они могут дать солидное основание для того, чтобы начать использовать в ваших приложениях TQuery. Но все же нельзя прогнозировать конец использования SQL в ваших приложених. Типичные серверы могут предложить вам другие характеристики, типа хранимых процедур и транзакций. В следующих двух секциях приведен краткий обзор этих средств.

3. Компонент TStoredProc

Хранимая процедура представляет собой список команд (SQL или определенного сервера), хранимых и выполняемых на стороне сервера. Хранимые процедуры не имеют концептуальных различий с другими типами процедур. TStoredProc наследуется от TDataset, поэтому он имеет много общих характеристик с TTable и TQuery. Особенно заметно сходство с TQuery. Поскольку хранимые процедуры не требуют возврата значений, те же правила действуют и для методов ExecProc и Open. Каждый сервер реализует работу хранимых процедур с небольшими различиями. Например, если в качестве сервера вы используете Interbase, хранимые процедуры выполняются в виде Select-запросов. Например, чтобы посмотреть на результаты хранимой процедуры, ORG_CHART, в демонстрационной базе данных EMPLOYEE, используйте следующих SQL-запрос:
Select * from ORG_CHART   

При работе с другими серверами, например, Sybase, вы можете использовать компонент TStoredProc. Данный компонент имеет свойства для имен базы данных и хранимой процедуры. Если процедура требует на входе каких-то параметров, используйте для их ввода свойство Params.

4. TDatabase

Компонент TDatabase обеспечивает функциональность, которой не хватает TQuery и TStoredProc. В частности, TDatabase позволяет создавать локальные псевдонимы BDE, так что приложению не потребуются псевдонимы, содержащиеся в конфигурационном файле BDE. Этим локальным псевдонимом в приложении могут воспользоваться все имеющиеся TTable, TQuery и TStoredProc. TDatabase также позволяет разработчику настраивать процесс подключения, подавляя диалог ввода имени и пароля пользователя, или заполняя необходимые параметры. И, наконец, самое главное, TDatabase может обеспечивать единственную связь с базой данных, суммируя все операции с базой данных через один компонент. Это позволяет элементам управления для работы с БД иметь возможность управления транзакциями.

Транзакцией можно считать передачу пакета информации. Классическим примером транзакции является передача денег на счет банка. Транзакция должна состоять из операции внесения суммы на новый счет и удаления той же суммы с текущего счета. Если один из этих шагов по какой-то причине был не выполнен, транзакция также считается невыполненной. В случае такой ошибки, SQL сервер позволяет выполнить команду отката (rollback), без внесения изменений в базу данных. Управление транзакциями зависит от компонента TDatabase. Поскольку транзакция обычно состоит из нескольких запросов, вы должны отметить начало транзакции и ее конец. Для выделения начала транзакции используйте TDatabase.BeginTransaction. Как только транзакция начнет выполняться, все выполняемые команды до вызова TDatabase.Commit или TDatabase.Rollback переводятся во временный режим. При вызове Commit все измененные данные передаются на сервер. При вызове Rollback все изменения теряют силу. Ниже в листинге 7 приведен пример, где используется таблица с именем ACCOUNTS. Показанная процедура пытается передать сумму с одного счета на другой.

Листинг 7
procedure TForm1.BitBtn1Click(Sender: TObject);  
{ ПРИМЕЧАНИЕ: Поле BALANCE у ACCOUNTS имеет триггер, проверяющий 
ситуацию, когда вычитаемая сумма превышает BALANCE. Если так, UPDATE 
будет отменен}  
begin  
  try  
    database1.StartTransaction;  
    query1.SQL.Clear;  
{ Вычитаем сумму из выбранного счета }  
    query1.SQL.Add(Format('update ACCOUNTS ' +  
      'set BALANCE = BALANCE - %s ) ' +  
      'where ACCT_NUM = %s ',  
      [edit1.text,  
      Table1Acct_Num.asString]));  
    query1.ExecSQL;  
    query1.SQL.Clear;  
{ Добавляем сумму к выбранному счету }  
    query1.SQL.Add(Format('update ACCOUNTS ' +  
      'set BALANCE = BALANCE + %s ' +  
      'where ACCT_NUM = %s ',  
      [edit1.text,  
      Table2Acct_Num.asString]));  
    query1.ExecSQL;  
    database1.Commit; {В этом месте делаем все изменения}  
    table1.Refresh;  
    table2.Refresh;  
  except  
{При возникновении в приведенном коде любых ошибок, 
откатываем транзакцию назад}  
    One: EDatabaseError do  
    begin  
      messageDlg(e.message, mtError, [mbOK], 0);  
      database1.rollback;  
      exit;  
    end;  
    One: Exception do  
    begin  

Добавлено: 11 Апреля 2018 08:08:17 Добавил: Андрей Ковальчук