BLOB (от англ. Binary Large Object — «большой двоичный объект») — тип данных в базах данных. Данный тип служит для сохранения значительных объемов бинарных данных, включающих графические изображения, аудиозаписи, видеоконтент, документальные файлы и прочие виды цифровых объектов.
Delphi и BLOB-поля
Delphi как и многие другие языки программирования позволяет работать с BLOB-полями различных СУБД.
Основные операции, которые встречаются на практике при работе с полями типа BLOB являются следующие:
- загрузка двоичных данных в поле;
- выгрузка (сохранение) двоичных данных из поля;
- удаление содержимого (очистка) поля BLOB.
Рассмотрим эти операции на примере СУБД MySQL.
Загрузка данных в BLOB с помощью Delphi
Двоичные данные, как правило, выступают в роли файла. То есть, изображение, видео или музыка или даже текст с таблицами хранятся в файлах. Давайте посмотрим как мы можем загрузить этот файл в поле BLOB. Это можно сделать через ADOQuery и ADOCommand. Для тех, кто использует другие способы доступа к данным код будет выглядеть аналогично приведенному. Через ADOQuery загрузку можно выполнить следующим образом:
if OpenPictureDialog1.Execute then begin Screen.Cursor := crHourGlass; if adoGood.State=dsBrowse then adoGood.Edit; tblobfield(adoGood.FieldByName('Изображение')).LoadFromFile(OpenPictureDialog1.FileName); Screen.Cursor:=crArrow; end;
Приведенный код довольно прост. adoGood в данном примере — это набор данных типа ADOQuery. Для загрузки на форму нужно поместить компонент OpenpictureDialog.
Если пользователь в стандартном окне выбора файла выбрал файл и нажал «Открыть» (выполнился метод Execute), то собственно, далее выполняется код загрузки файла в поле BLOB. Сначала строкой Screen.Cursor := crHourGlass мы переводим курсор мыши в часы. Далее проверяем состояние набор данных и, если он находится в режиме просмотра (State=dsBrowse), то мы переводим его в режим редактирования.
В конце процедуры загрузки мы возвращаем курсор мыши в начальное состояние (стрелки) кодом: Screen.Cursor:=crArrow.
После этого, следующая строка, собственно, загружает изображение в поле. Осуществляется это через класс TBlobField, который представляет из себя поле набора данных, которое хранит в себе ссылку на объект BLOB (на файл), который мы выбрали.
Метод Edit мы вызываем, если нам необходимо загрузить файл в BLOB-поле текущей записи набора данных. А если нужно добавить новую запись, то вместо этого метода мы используем метод Append.
Ну и, соответственно, добавленное значение нам нужно сохранить (перенести из набора данных в физическую таблицу). Для этого в зависимости от ситуации нам либо в конце данной процедуры (можно перед Screen.Cursor:=crArrow) следует написать adoGood.Post, либо эту строку кода можно повесить на отдельную кнопку с названием «Сохранить».
Здесь мы на самом деле можем загрузить не только графический файл, но и аудио, видео и прочие файлы. Только вместо OpenPictureDialog нужно использовать OpenDialog. OpenDialog можно использовать и для загрузки изображений.
Это, если нам нужно просто загрузить объект в поле. Но иногда удобно иметь сразу представление о том, что мы загрузили. Я для этого люблю в таблицу добавлять пару-тройку полей, содержащих имя файла с расширением, который мы загрузили и просто его расширение. Посмотрим на еще один код:
procedure TfrmPhotoAlbom.imgAppendClick(Sender: TObject); var sName: String; // Имя файла с расширением sRas: string; // Просто расширение файла begin if OpenPictureDialog1.Execute then begin Screen.Cursor := crHourGlass; //Выясним имя файла с расширением и присвоим их переменным sName:=LowerCase(extractFileName(OpenDialog1.Filename)); sRas:=LowerCase(extractfileext(OpenDialog1.FileName)); adoPhoto.Append; adoPhoto.FieldByName('Имя_файла').AsString:=sName; adoPhoto.FieldByName('Расширение').AsString:=sRas; tblobfield(frmPhotoAlbom.adoPhoto.FieldByName('Фото')).LoadFromFile(OpenPictureDialog1.FileName); adoPhoto.Post; Screen.Cursor:=crArrow; end; end;
Когда мы выбрали файл, то мы присваиваем sName — имя выбранного файла с расширением, а sRas — просто расширение.
Имя файла с расширением, хранящееся в переменной «sName» и поле «Имя_файла» требуется мне для того, что бы я мог пользователю отобразить, какой файл хранится в поле:

Это достаточно удобно. А вот значения переменной «sRas» и поля «Расширение» нам понадобятся, когда мы будем выгружать (сохранять) наши файлы из поля BLOB. Но еще больше мне они пригождаются, когда я хочу открыть файл, хранящийся в поле BLOB и открыть его сразу той программой, которая зарегистрирована для конкретного типа открываемого файла в операционной системе Windows. Но это чуть дальше.
Давайте теперь рассмотрим, как можно загрузить данные в BLOB-поле с помощью ADOCommand:
procedure TfrmMeetings.BitBtn2Click(Sender: TObject); var sName: string; // Имя файла с расширением sRas: string; // Просто расширение файла iRecno: integer; begin if OpenDialog1.Execute then begin Screen.Cursor := crHourGlass; //Выясним имя файла с расширением и присвоим его переменной sName:=LowerCase(extractFileName(OpenDialog1.Filename)); sRas:=LowerCase(extractfileext(OpenDialog1.FileName)); ADOCommand1.CommandText:='insert into Документы (Файл, Имя, Расширение) VALUES (:pFile, '''+sName+''', '''+sRas+''')'; ADOCommand1.Parameters.ParamByName('pFile').LoadFromFile(OpenDialog1.FileName, ftVarBytes); ADOCommand1.Execute; iRecno:=adoCase.RecNo; adoDocs.DisableControls; adoDocs.Requery(); if adoDocs.RecordCount>0 then adoDocs.RecNo:=iRecno; adoDocs.EnableControls; Screen.Cursor:=crArrow; end; end;
Как видно из кода сам файл добавляется как параметр :pFile, а имя файла и расширение как переменные. Их тоже можно и нужно добавлять через параметры. Просто здесь я показал, что так тоже можно делать, хотя это считается «опасным кодом».
О работе с параметрами можно почитать в статье «SQL запросы к базе данных».
После выполнения команды Execute потребуется обновить набор данных, в котором теперь будет загружен наш файл (adoDocs.Requery ();).
Выгрузка (сохранение/экспорт) данных из BLOB с помощью Delphi
Загрузить загрузили и эти данные можно показать, например с помощью компонентов отображения графических данных, если это изображение. А вот как сохранить наш загруженный файл на жесткий диск или просто просмотреть то, что мы загрузили? Тоже легко.
procedure TfrmPhotoAlbom.Image3Click(Sender: TObject); begin SavePictureDialog1.FileName:=adoPhoto.FieldByName('Имя_файла').AsString; if SavePictureDialog1.Execute then begin Screen.Cursor:=crHourGlass; TBlobField(adoPhoto.FieldByName('Фото')).SaveToFile(SavePictureDialog1.FileName+adoPhoto.FieldByName('Расширение').AsString); Screen.Cursor:=crArrow; end; end;
В результате выполнения этой процедуры на жесткий диск компьютера выгружается файл, содержащийся в поле типа BLOB.
Открытие файла из поля BLOB
Открытие файла из поля BLOB (MySQL). Привязка открываемого файла к ассоциируемой с ним программе.
Теперь давайте представим, что пользователю нужно просмотреть файл, который загружен в поле BLOB, но мы не знаем, какой файл загружен. Это может быть файл doc, docx, xls, pdf и все, что угодно... Пользователь не знает какой это тип файла загружен в программе, да ему и все-равно, ему бы посмотреть документ, не сохраняя его при этом на диск компьютера. Как это сделать? Сохранять мы конечно будем, но мы будем это делать без участия пользователя. Например, мы можем создать каталог Temp в папке программы и при открытии выгрузить файл в него, а затем открыть.

Процедура у этого всего дела будет выглядеть так:
procedure TfrmAppeals.Label10Click(Sender: TObject); var Ptkwrd: TMemoryStream; sFileFull: string; //Полный путь к файлу begin //Открываем вложение только, если поле с ним не пустое if not adoDocuments.FieldByName('Документ').isNull then begin ptkwrd:= Tmemorystream.Create; tblobfield(adoDocuments.FieldByName('Документ')).SaveToStream(ptkwrd); ptkwrd.Position:=0; if adoDocuments.FieldByName('Расширение').AsString='.doc' then begin ptkwrd.SaveToFile(STekPapka+'\Temp\temp.doc'); sFileFull:=STekPapka+'\Temp\temp.doc'; shellexecute(Handle, 'open',PChar(sFileFull),nil,nil,SW_SHOWMAXIMIZED); end; if adoDocuments.FieldByName('Расширение').AsString='.docx' then begin ptkwrd.SaveToFile(STekPapka+'\Temp\temp.docx'); sFileFull:=STekPapka+'\Temp\temp.docx'; shellexecute(Handle, 'open',PChar(sFileFull),nil,nil,SW_SHOWMAXIMIZED); end; if adoDocuments.FieldByName('Расширение').AsString='.xls' then begin ptkwrd.SaveToFile(STekPapka+'\Temp\temp.xls'); sFileFull:=STekPapka+'\Temp\temp.xls'; shellexecute(Handle, 'open',PChar(sFileFull),nil,nil,SW_SHOWMAXIMIZED); end; if adoDocuments.FieldByName('Расширение').AsString='.xlsx' then begin ptkwrd.SaveToFile(STekPapka+'\Temp\temp.xlsx'); sFileFull:=STekPapka+'\Temp\temp.xlsx'; shellexecute(Handle, 'open',PChar(sFileFull),nil,nil,SW_SHOWMAXIMIZED); end; if adoDocuments.FieldByName('Расширение').AsString='.jpeg' then begin ptkwrd.SaveToFile(STekPapka+'\Temp\temp.ipeg'); sFileFull:=STekPapka+'\Temp\temp.ipeg'; shellexecute(Handle, 'open',PChar(sFileFull),nil,nil,SW_SHOWMAXIMIZED); end; if adoDocuments.FieldByName('Расширение').AsString='.jpg' then begin ptkwrd.SaveToFile(STekPapka+'\Temp\temp.jpg'); sFileFull:=STekPapka+'\Temp\temp.jpg'; shellexecute(Handle, 'open',PChar(sFileFull),nil,nil,SW_SHOWMAXIMIZED); end; if adoDocuments.FieldByName('Расширение').AsString='.png' then begin ptkwrd.SaveToFile(STekPapka+'\Temp\temp.png'); sFileFull:=STekPapka+'\Temp\temp.png'; shellexecute(Handle, 'open',PChar(sFileFull),nil,nil,SW_SHOWMAXIMIZED); end; if adoDocuments.FieldByName('Расширение').AsString='.pdf' then begin ptkwrd.SaveToFile(STekPapka+'\Temp\temp.pdf'); sFileFull:=STekPapka+'\Temp\temp.pdf'; shellexecute(Handle, 'open',PChar(sFileFull),nil,nil,SW_SHOWMAXIMIZED); end; if adoDocuments.FieldByName('Расширение').AsString='.txt' then begin ptkwrd.SaveToFile(STekPapka+'\Temp\temp.txt'); sFileFull:=STekPapka+'\Temp\temp.txt'; shellexecute(Handle, 'open',PChar(sFileFull),nil,nil,SW_SHOWMAXIMIZED); end; if adoDocuments.FieldByName('Расширение').AsString='.odt' then begin ptkwrd.SaveToFile(STekPapka+'\Temp\temp.odt'); sFileFull:=STekPapka+'\Temp\temp.odt'; shellexecute(Handle, 'open',PChar(sFileFull),nil,nil,SW_SHOWMAXIMIZED); end; if adoDocuments.FieldByName('Расширение').AsString='.ods' then begin ptkwrd.SaveToFile(STekPapka+'\Temp\temp.ods'); sFileFull:=STekPapka+'\Temp\temp.ods'; shellexecute(Handle, 'open',PChar(sFileFull),nil,nil,SW_SHOWMAXIMIZED); end; ptkwrd.Free; end; end;
По сути мы тут осуществляем ту же выгрузку данных в файл, которую мы уже рассматривали выше, но в каталог Temp и при этом мы сразу запускаем ассоциируемую с данным типом файла программу. То есть, по сути в каталог Temp все время выгружается файл с именем Temp, но расширения ему присваиваются разные — они хранятся в поле с именем «Расширение».
Хотя по сути можно было бы и не использовать наверное поле с именем «Расширение». Мне кажется достаточно было бы просто поля «Имя_файла», поскольку в нем имя уже содержит расширение. Тогда не пришлось бы для каждого типа файла делать проверку по типу «if adoDocuments.FieldByName ('Расширение').AsString='.ods' then», а просто сразу проводить выгрузку и присваивать расширение из этого поля.
Здесь можно, в принципе, по разному осуществлять организацию хранения данных в зависимости от типа задачи и от того, насколько удобно использовать тот или иной способ. Представленный способ отнюдь не оптимизирован и не минимизирован. Но я привел его, чтобы показать саму суть того, как можно открыть данные, чтобы это было просто и удобно для самого пользователя.
С точки зрения программиста конечно данный код можно и нужно оптимизировать, так как его недостаток в том, что для каждого типа файла приходится осуществлять проверку конструкцией «if ... end», что не очень удобно. Более того, сама по себе конструкция «if ... end» в данном случае является слишком громоздкой и не удобной. Можно использовать CASE или вообще не использовать проверку типа. Все это на усмотрение программиста.
Удаление содержимого поля BLOB
Чтобы просто очистить поле BLOB можно также использовать компонент ADOQuery и ADOCommand или их аналоги из других механизмов доступа к данным:
Через ADOQuery:
procedure TfrmInput.Button3Click(Sender: TObject); begin ADODocs.Edit; tblobfield(frmInput.ADODocs.FieldByName('Документ')).Clear; ADODocs.Post; end;
Через ADOCommand:
procedure TfrmAppeals.cmdDeleteDocClick(Sender: TObject); var iID: Integer; iRecno: integer; begin if adoDocuments.RecordCount>0 then begin DeleteCreate(Left,Top,Height,Width,'Удалить документ из обращения?'); if bModal=false then abort else begin iID:=adoDocuments.FieldByName('Код_документа').AsInteger; iRecno:=adoDocuments.RecNo; ADOCommand1.CommandText:='DELETE FROM Документы WHERE Код_документа = :piID'; ADOCommand1.Parameters.ParamByName('piID').Value:=iID; ADOCommand1.Execute; adoDocuments.Requery(); end; end; end;
Вот таким образом, можно осуществлять работу с полями типа BLOB.