I want to store images into a database using SQL commands, I know other ways using TBlobField.LoadFromFile etc, but we make our own sql commands to update the database that's why I need to do this.
How should I go about doing this?
I've never tried this (and away from desk at the moment), but would parameters work? eg:
Query.Sql.Clear;
Query.Sql.Add('update myTable set myField = :blobVal where myId = :idVal');
Query.ParamByName('idVal').AsInteger := SomeId;
Query.ParamByName('blobVal').LoadFromFile(....
//or
Query.ParamByName('blobVal').LoadFromStream(....
Query.ExecSql;
This allows you to use SQL (rather than the .Edit, etc methods) and still insert blob data
If I understand correctly, you have some sort of SQL-generation system instead of using datasets, and you want to know how to generate the SQL to do an INSERT or UPDATE to a Blob field correctly.
What you'll need is a way to serialize your image to a string. For example:
function ImageToString(image: TImage): string;
var
stream: TStringStream;
begin
stream := TStringStream.Create;
try
image.SaveToStream(stream);
result := image.DataString;
finally
stream.Free;
end;
end;
Once that's ready, put a token in your SQL, something like set IMAGE_FIELD = $IMAGE$, and then use StringReplace to replace the $IMAGE$ token with the string representation of your image.
Or you could use parameters, like Graza suggested, if the system you're working with supports them.
Try this:
Query.Sql.Clear;
Query.Sql.Add('update myTable set myField = :blobVal where myId = :idVal');
Query.ParamByName('idVal').AsInteger := SomeId;
Query.Params.CreateParam(ftBlob,'blobVal',ptInput).LoadFromFile('c:\path.png',ftGraphic);
Query.ExecSql;
jpg := TJPEGImage.Create;
jpg.Assign(Image1.Picture.Graphic);
strm := TMemoryStream.Create;
strm.Position:= 0;
jpg.SaveToStream(strm);
IBSQL1.Close;
IBSQL1.SQL.Clear;
IBSQL1.SQL.Add('INSERT INTO ENTRY(FORMNUM, JPG) VALUES(');
IBSQL1.SQL.Add( quotedstr(edtFormNum.Text));
IBSQL1.SQL.Add(', :JPG');
IBSQL1.SQL.Add(')');
IBSQL1.Params.ByName('JPG').LoadFromStream(strm);
IBSQL1.ExecQuery;
strm.Free;
jpg.Free;
Related
I'm trying to get the price of medication from the table but i just get:
procedure TForm1.BuyButtonClick(Sender: TObject);
var
iAmount : integer;
rRate : real;
sMedication : string;
sRate : string;
begin
iAmount := 0;
sMedication := BuyCombobox.Items[BuyCombobox.ItemIndex];
dmHospital.qryPrices.SQL.Clear;
dmHospital.qryPrices.SQL.Add('SELECT Price(R) FROM MedicationPrices WHERE Medication = quaotedstr(sMedication)');
sRate := dmHospital.qryPrices.SQL;
ShowMessage(sRate);
end;
You're not using the query properly. qryPrices.SQL is the SQL statement itself. It's just text. You need to do something to actually run the statement. (See below.)
You've also embedded the variable inside the quotes, which means it's not being evaluated, and neither is the function call to the (misspelled) QuotedStr. There is no function quaotedStr(). If you insist on the poor idea of concatenating SQL, you need to do it properly. If you're going to clear and then add, you can just assign to SQL.Text instead to do it in one step:
dmHospital.qryPrices.SQL.Text := 'SELECT Price(R) FROM MedicationPrices WHERE Medication = ' + Quotedstr(sMedication);
Also, the query won't do anything until you actually execute it. You need to use qryPrices.Open to run a SELECT statement, or qryPrices.ExecSQL to run an INSERT, UPDATE or DELETE statement.
You should get out of the thought of concatenating SQL immediately (before you get the habit) and learn to use parameterized queries. It allows the database driver to handle the formatting and conversion and quoting for you, and it also prevents SQL injection that can give others access to your data. Here's a corrected version that should get you started.
procedure TForm1.BuyButtonClick(Sender: TObject);
var
sMedication : string;
sRate : string;
begin
iAmount := 0;
sMedication := BuyCombobox.Items[BuyCombobox.ItemIndex];
dmHospital.qryPrices.SQL.Text := 'SELECT Price(R) FROM MedicationPrices WHERE Medication = :Medication';
dmHospital.qryPrices.Parameters.ParamByName('Medication').Value := sMedication;
dmHospital.qryPrices.Open;
sRate := dmHospital.qryPrices.FieldByName('Price(R)').AsString;
dmHospital.qryPrices.Close;
ShowMessage(sRate);
end;
You should modify Your code to actually work:
My advise is to use parameters instead of QuotedStr:
dmHospital.qryPrices.SQL.Clear;
dmHospital.qryPrices.SQL.Add('SELECT Price(R) AS Rate FROM MedicationPrices WHERE Medication = :pMedication');
dmHospital.qryPrices.Params.ParamByName('pMedication').AsString=sMedication;
(Note that in ADOQuery You'd use .Parameters instead of .Params)
dmHospital.qryPrices.Open;
sRate=dmHospital.qryPrices.FieldByName('Rate').AsString;
ShowMessage(sRate);
Regards
Not tested it (dont have Delphi at hand here) but it should be something like this :
iAmount := 0;
sMedication := BuyCombobox.Items[BuyCombobox.ItemIndex];
dmHospital.qryPrices.SQL.Clear;
dmHospital.qryPrices.SQL.Add('SELECT Price(R) as price FROM MedicationPrices WHERE Medication = ' + QuotedStr(sMedication));
dmHospital.qryPrices.Open;
if (dmHospital.qryPrices.RecordCount = 1)
sRate := dmHospital.qryPrices.FieldByName('price').AsString;
ShowMessage(sRate);
In trying to update 10000 records by using batch update method over remote mysql connection. my server has 200+ms latency and by using this method it would take forever to do this since its sending queries one by one! any workaround?
query.params.arraysize := 10000;
query.sql.text := 'update table set field=:f1 where id=:f2;'
for i := 0 to query.params.arraysize-1 do
begin
query.params[0].asstrings[i] := 'VERY LONG STRING > 10KB';
query.params[1].asintegers[i] := id;
end;
query.execute(10000);
Try this:
Define the parameterized query.
Configure query params for performance.
e.g:
FDQuery1.Params[0].DataType := ftString;
Set the array size.
Supply the parameters values.
Execute the query in a transaction.
FDConnection.StartTransaction;
try
FDQuery1.Execute(FDQuery1.Params.ArraySize);
FDConnection.Commit;
except
FDConnection.Rollback;
raise;
end;
I use Delphi Berlin.
I am trying to make a function/procedure to copy data from a FireDac query (connected to a database) to another FireDac query (connected to another database) using ArrayDML. first database is firebird and the other is MSSQL in the first care, but in another case both databases are Firebird.
So far so good and almost all datatype are working correct except ftBlob.
Here is the body of the function:
while not querySource.Eof do begin
paramPosition := -1;
Inc(mIndex);
for i := 0 to querySource.FieldCount - 1 do begin
Inc(paramPosition);
// daca exista o valoare
if querySource.FieldByName(querySource.Fields[i].FieldName).AsVariant <> Null then begin
case querySource.Fields[i].DataType of
ftDateTime, ftDate, ftTime, ftTimeStamp : queryInsert.Params[paramPosition].AsDateTimes[mIndex] := querySource.FieldByName(querySource.Fields[i].FieldName).AsDateTime;
ftFloat, ftCurrency, ftBCD, ftFMTBcd : queryInsert.Params[paramPosition].AsFloats[mIndex] := querySource.FieldByName(querySource.Fields[i].FieldName).AsFloat;
ftSmallint, ftInteger, ftLargeint : queryInsert.Params[paramPosition].AsIntegers[mIndex] := querySource.FieldByName(querySource.Fields[i].FieldName).AsInteger;
ftString : queryInsert.Params[paramPosition].AsStrings[mIndex] := querySource.FieldByName(querySource.Fields[i].FieldName).AsString;
ftBlob, ftMemo, ftGraphic : queryInsert.Params[paramPosition].AsBlobs[mIndex] := querySource.FieldByName(querySource.Fields[i].FieldName).AsVariant;
end;
end;
end;
the blob value is not copy the correct value from the source.
how to use the arrayDML in this case? any workaround?
I won't answer your question but suggest you to use the TFDBatchMove component because you are reinventing wheel here. TFDBatchMove is just for what you want to do, for moving data from one database to another (and not only that).
You simply setup the TFDBatchMoveSQLReader as Reader and Writer, write SQL queries for both and the component will automatically map the fields by matching names. If the queries won't have matching field names, you can fine tune this by the Mappings property. Then you just call Execute.
We are using MSSQL 2012.
Trying to update Client photo with Stored Procedure
spui_SetClientPhoto
int ClientID
VarBinary(Max) Photo
Program runs fine with pure ADO:
ADO.ProcedureName:='spui_SetClientPhoto';
ADO.Parameters.CreateParameter('#ClientsID',ftInteger,pdInput,0,95075);
ADO.Parameters.CreateParameter('#Photo',ftBlob,pdInput,0,NULL);
ADO.Parameters[1].LoadFromFile('C:\Photo.png',ftBlob);
ADO.ExecProc;
But with CDS it cause an error:
Implicit Conversion from datatype Varchar(max) to Varbinary(max) is not allowed.
ADO.ProcedureName:='spui_SetClientPhoto';
cds.SetProvider(ADO);
cds.Params.CreateParam(ftInteger,'#ClientsID',ptInput).AsInteger:=95075;
cds.Params.CreateParam(ftBlob,'#Photo',ptInput).LoadFromFile('C:\Photo.png', ftBlob);
cds.Execute;
e.g. cannot run CDS with Parameters of BLOB type. Any solution for this?
The following works fine for me, with the Picture field type in the AdoQuery and
CDS set to ftGraphic, and the Stored Proc's DDL set to
CREATE PROCEDURE [dbo].[SetClientPhoto](#ClientID int, #Picture Image)
AS
BEGIN
SET NOCOUNT ON;
update table_2
set picture = #Picture
where
ID = #ClientID
END
Code
procedure TForm1.SavePictureViaStoredProc;
var
PrvCommandText,
PrvSql : String;
ID : Integer;
const
scTestImage = 'D:\TestPictures\TestBMP.BMP';
begin
// First, save the text of the AdoQuery's Sql and the CDS's CommandText
PrvCommandText := CDS1.CommandText;
PrvSql := AdoQuery1.SQL.Text;
// Save the iD of the row we want to use
ID := CDS1.FieldByName('ID').AsInteger;
try
// Allow CommandText changes on the DSP
DataSetProvider1.Options := DataSetProvider1.Options + [poAllowCommandText];
CDS1.Close;
// construct a Sql statement to invoke the Stored Proc
CDS1.CommandText := 'exec dbo.SetClientPhoto #ClientID = :' + IntToStr(ID) + ', #Picture = :Picture';
// Set up parameters
CDS1.Params.Clear;
CDS1.Params.CreateParam(ftInteger, '#ClientID', ptInput);
CDS1.Params.CreateParam(ftGraphic, '#Picture', ptInput);
CDS1.Params.ParamByName('#ClientID').Value := ID;
CDS1.Params.ParamByName('#Picture').LoadFromFile(scTestImage, ftGraphic);
AdoQuery1.Close;
AdoQuery1.SQL.Text := '';
CDS1.Execute; // This executes the stored proc
CDS1.Params.Clear;
finally
ADoQuery1.SQL.Text := PrvSql;
CDS1.CommandText := PrvCommandText;
CDS1.Open;
end;
end;
Note: I very rarely store images in databases, and have not yet managed to get this to work with .Jpg and .Png files. I vaguely recall that there is an extra step needed with storing those in a DB without getting a "Stream read error" or "Invalid image" exception, and I'll see if I can remind myself of it later.
I am currently trying to read an Image from a MS Access database that has an OLE Object field and contains a valid bitmap (for test purposes, I created a image using MS Paint and saved it in 24bit bmp).
I am linking to this via DBGrid. In theory everything should work good and it should show the image, however I am getting a: "bitmap image not valid" error. I can understand if this is a JPEG and not .bmp, but that isn't the case. So my question is, what is wrong?
I dont necessarily have to use a DBImage, a normal TImage will also do just fine (might even be more preferable), but I'm not sure on how to assign a TImage to an OLE Object field in a MS Access Database. I Have tried, to no avail:
//Select photo from Image field
Image1.Picture := ADOTable1['Image'];
I've read most of the articles, such as about.com etc, regarding this matter, but still don't get any good results.
Any help would be greatly appreciated!
UPDATE: This worked for me:
Add to USES clause : JPEG, ADODB, DB
function JpegStartsInBlob
(PicField:TBlobField):integer;
var
bS : TADOBlobStream;
buffer : Word;
hx : string;
begin
Result := -1;
bS := TADOBlobStream.Create(PicField, bmRead);
try
while (Result = -1) and
(bS.Position + 1 < bS.Size) do
begin
bS.ReadBuffer(buffer, 1);
hx:=IntToHex(buffer, 2);
if hx = 'FF' then begin
bS.ReadBuffer(buffer, 1);
hx:=IntToHex(buffer, 2);
if hx = 'D8' then Result := bS.Position - 2
else if hx = 'FF' then
bS.Position := bS.Position-1;
end;
end;
finally
bS.Free
end;
end;
procedure TfrmOne.btnShowImageClick(Sender: TObject);
var
bS : TADOBlobStream;
Pic : TJPEGImage;
begin
bS := TADOBlobStream.Create(table1.FieldByName('Photo') as TBlobField, bmRead);
bS.Seek(JpegStartsInBlob(table1.FieldByName('Photo') as TBlobField),
soFromBeginning);
Pic := TJPEGImage.Create;
Pic.LoadFromStream(bS);
frmOne.Image1.Picture.Graphic := Pic;
Pic.Free;
bS.Free;
end;
what would return ADOTable1['Image'] ?
i don't like FieldVallues property u use, for you don't know the actual type and cannot control things using type checking.
I guess you'd better use Data.DB.TDataSet.FieldByName
The very type of TField object would hint you the kind of data it contains.
I don't know about Microsoft Jet (database engine of Excel and Access), but i think it stores raw BMP file data and some link to Paint application (or Gimp, or Photoshop, or whatever used to edit BMP) Kinf of TBlobField i think.
http://support.microsoft.com/kb/205635/en-us - this shows a snippet how to save file from OLE filed.
try to find a way to save field content into TFileStream.
Check that created file is really BMP file.
After that save blob into TMemoryStream and TBitmap.LoadFromStream
consider Data.DB.TBlobField.SaveToStream, Data.DB.TField.AsBytes
One more snippet from docs - explore those classes if u can use them
"Use TADOBlobStream to access or modify the value of a BLOB or memo field in an ADO dataset. BLOB fields are represented by TBlobField objects and descendants of TBlobField such as TGraphicField and TMemoField."
As resume:
1) get the type of data - ADOTable1.FieldByName.ClassName
Read about it, which methods properties would allwo u to get the data
2) try to save the data into file and analyze what is it
3) try to save that data into stream and re-use for loading picture