Delphi SqLite Date load to TDateEdit error - sqlite

I using SQlite database im my Firemonkey Android application and there is no native DateTime type.
I storing date as text type
insert command:
insert into table (value,date_of_change)
values (:val,date('now'));
it works fine, date is correct stored, order by date works fine but if I want load this date into TDate edit
query:
select id,value,date_of_change
from table
where id = :MyID
code:
FDQuery1.Close;
FDQuery1.ParamByName('MyID').Value:= myid;
FDQuery1.OpenOrExecute;
FDQuery1.First;
NumberBox1.Value:=FDQuery1.FieldByName('suma').AsFloat;
DateEdit1.Date:=FDQuery1.FieldByName('date_of_change').AsDateTime;
I get error 2016-10-16 is not valid date and time but in Date edit I can see correct date !
Do anybody knows correct solution of this problem ?

Since you store the date as a string FireDAC fails to parse the format properly. You need to change the way the string value in the database column date_of_change is parsed using the correct date format.
So, instead of doing this:
DateEdit1.Date:=FDQuery1.FieldByName('date_of_change').AsDateTime;
You should do this:
function ParseDateFromDB(const DateStr: String): TDateTime;
var
FormatSettings: TFormatSettings;
begin
FormatSettings.DateSeparator := '-';
FormatSettings.ShortDateFormat := 'YYYY-MM-DD';
Result := StrToDate(DateStr, FormatSettings);
end;
//[...]
DateEdit1.Date := ParseDateFromDB(FDQuery1.FieldByName('date_of_change').AsString);

FireDAC uses its own mapping to SQLite data types and adds the DATE pseudo data type for you. So as there is the SINGLE pseudo data type that you can use for storing value of that number box.
So you can create your table by FireDAC like this:
FDQuery.SQL.Text := 'CREATE TABLE MyTable (DateField DATE, SingleField SINGLE)';
FDQuery.ExecSQL;
Then you can insert data:
FDQuery.SQL.Text := 'INSERT INTO MyTable (DateField, SingleField) VALUES (:DateField, :SingleField)';
FDQuery.ParamByName('DateField').AsDate := DateEdit.Date;
FDQuery.ParamByName('SingleField').AsSingle := NumberBox.Value;
FDQuery.ExecSQL;
And read them for example this way:
FDQuery.SQL.Text := 'SELECT DateField, SingleField FROM MyTable';
FDQuery.Open;
DateEdit.Date := DateOf(FDQuery.FieldByName('DateField').AsDateTime);
NumberBox.Value := FDQuery.FieldByName('SingleField').AsSingle;

Related

Is there a way to INSERT Null value as a parameter using FireDAC?

I want to leave some fields empty (i.e. Null) when I insert values into table. I don't see why would I want to have a DB full of empty strings in fields.
I use Delphi 10, FireDAC and local SQLite DB.
Edit: Provided code is just an example. In my application values are provided by user input and functions, any many of them are optional. If value is empty, I would like to keep it at Null or default value. Creating multiple variants of ExecSQL and nesting If statements isn't an option too - there are too many optional fields (18, to be exact).
Test table:
CREATE TABLE "Clients" (
"Name" TEXT,
"Notes" TEXT
);
This is how I tried it:
var someName,someNote: string;
begin
{...}
someName:='Vasya';
someNote:='';
FDConnection1.ExecSQL('INSERT OR REPLACE INTO Clients(Name,Notes) VALUES (:nameval,:notesval)',
[someName, IfThen(someNote.isEmpty, Null, somenote)]);
This raises an exception:
could not convert variant of type (Null) into type (OleStr)
I've tried to overload it and specify [ftString,ftString] and it didn't help.
Currently I have to do it like this and I hate this messy code:
FDConnection1.ExecSQL('INSERT OR REPLACE INTO Clients(Name,Notes) VALUES ('+
IfThen(someName.isEmpty,'NULL','"'+Sanitize(someName)+'"')+','+
IfThen(someNote.isEmpty,'NULL','"'+Sanitize(someNote)+'"')+');');
Any recommendations?
Edit2: Currently I see an option of creating new row with "INSERT OR REPLACE" and then use multiple UPDATEs in a row for each non-empty value. But this looks direly ineffective. Like this:
FDConnection1.ExecSQL('INSERT OR REPLACE INTO Clients(Name) VALUES (:nameval)',[SomeName]);
id := FDConnection1.ExecSQLScalar('SELECT FROM Clients VALUES id WHERE Name=:nameval',[SomeName]);
if not SomeString.isEmpty then
FDConnection1.ExecSQL('UPDATE Clients SET Notes=:noteval WHERE id=:idval)',[SomeNote,id]);
According to Embarcadero documentation ( here ):
To set the parameter value to Null, specify the parameter data type,
then call the Clear method:
with FDQuery1.ParamByName('name') do begin
DataType := ftString;
Clear;
end;
FDQuery1.ExecSQL;
So, you have to use FDQuery to insert Null values, I suppose. Something like this:
//Assign FDConnection1 to FDQuery1's Connection property
FDQuery1.SQL.Text := 'INSERT OR REPLACE INTO Clients(Name,Notes) VALUES (:nameval,:notesval)';
with FDQuery1.ParamByName('nameval') do
begin
DataType := ftString;
Value := someName;
end;
with FDQuery1.ParamByName('notesval') do
begin
DataType := ftString;
if someNote.IsEmpty then
Clear;
else
Value := someNote;
end;
if not FDConnection1.Connected then
FDConnection.Open;
FDQuery1.ExecSql;
It's not very good idea to execute query as String without parameters because this code is vulnerable to SQL injections.
Some sources tells that it's not enough and you should do something like this:
with FDQuery1.ParamByName('name') do begin
DataType := ftString;
AsString := '';
Clear;
end;
FDQuery1.ExecSQL;
but I can't confirm it. You can try it if main example won't work.

How to get the ID of the last record inserted in SQLite with Delphi 10?

Delphi 10 with Firemonkey and SQLite: After running the code below I want to get the ID of the last record inserted into an SQLite table. How do I get the last ID?
NOTE: The ID field of Table 1 is autoincrement.
var myQr: TFDQuery;
begin
myQr := TFDQuery.Create(Self);
with myQr do begin
SQL.Add('Insert into table1 values (:_id, :_name, :_dthr)');
Params.ParamByName('_id').ParamType := TParamType.ptInput;
Params.ParamByName('_id').DataType := TFieldType.ftInteger;
Params.ParamByName('_id').Value := null;
ParamByName('_name').AsString := 'name test';
ParamByName('_dthr').AsDateTime := Now;
ExecSQL;
end;
// How to get last ID? <<<<<<<<<<<<<=================
myQr.DisposeOf;
You could query last_insert_rowid if your ID column is declared as INTEGER PRIMARY KEY. In such case the column becomes alias for the ROWID. If that is your case, you can query it natively e.g. this way:
uses
FireDAC.Phys.SQLiteWrapper;
function GetLastInsertRowID(Connection: TFDConnection): Int64;
begin
Result := Int64((TObject(Connection.CliObj) as TSQLiteDatabase).LastInsertRowid);
end;
Or in common way by calling GetLastAutoGenValue method:
function GetLastInsertRowID(Connection: TFDConnection): Int64;
begin
Result := Int64(Connection.GetLastAutoGenValue(''));
end;

FDQuery append gives error "no such table"

In Delphi 10.1 I made a small program to learn about FireDAC and SQlite.
I have FDConnection, FDQuery (with SQL= SELECT * FROM Sætning) and DataSource + DBGrid.
The DBGrid shows the (empty) table Sætning. I want to put data into my table from a listbox containg a CSV.
This is my code: (fdwSætning = an FDQuery)
procedure TMainForm.bCSV_SQLite_SætningClick(Sender: TObject);
var
loop : integer;
nr, lang, tekst : string;
begin
{ Read CSV file into Listbox }
Listbox1.Items.LoadFromFile('GMS_Saetninger.txt');
{ Put the values from the CSV into the fields in each record }
for loop:= 0 to Listbox1.Items.Count-1 do begin
fdqSætning.Edit;
nr:= copy(Listbox1.Items[loop],1,4);
lang:= copy(Listbox1.Items[loop],5,2);
tekst:= copy(Listbox1.Items[loop],8, length(Listbox1.Items[loop]));
fdqSætning.Append;
fdqSætning.FieldByName('SAETNING_ID').AsString:= nr;
fdqSætning.FieldByName('LANGUAGE').AsString:= lang;
fdqSætning.FieldByName('SENTENCE').AsString:= tekst;
fdqSætning.Post;
end;
end;
When I run this code I get the error message
[FireDAC][phys][SQLite]ERROR:no such table: Sætning
That should not happen. Since Delphi 2009, FireDAC fully supports Unicode metadata values, so as does SQLite DBMS. Possible explanation for what you describe is that you've created your table in some external tool (which cannot save Unicode metadata).
So even when I would highly suggest using only ASCII chars for database object names, you can still do something like this with FireDAC since Delphi 2009:
FDConnection.Params.Add('DriverID=SQLite');
FDConnection.Params.Add('Database=C:\MyDatabase.db');
FDQuery.SQL.Text := 'CREATE TABLE ṀÿṪäḅḷë (MɏFɨɇłđ INTEGER)';
FDQuery.ExecSQL;
FDQuery.SQL.Text := 'INSERT INTO ṀÿṪäḅḷë (MɏFɨɇłđ) VALUES (1234)';
FDQuery.ExecSQL;
FDQuery.SQL.Text := 'SELECT MɏFɨɇłđ FROM ṀÿṪäḅḷë';
FDQuery.Open;
Assert(FDQuery.FieldByName('MɏFɨɇłđ').AsInteger = 1234);

Insert date into a SQLite database

As a part of a parametrized query I am trying to insert date from the plannerCalendar.
Params.ParamByName('D').AsDate := JulianDateToDateTime(PlannerCalendar1.Date);
This will not work.
Any ides ?
EDIT :
Even a simpla date insert will not work:
with ClientdataSet1 do
begin
Close;
CommandText :='';
CommandText :='INSERT INTO TLOG (DATE) VALUES (:D)';
Params.ParamByName('D').Value := Plannercalendar1.Date;
Execute;
I get :
When I do this (just to test) :
CommandText :='INSERT INTO TLOG (DATE) VALUES (date(julianday("now", "LOCALTIME")))';
The date gets inserted.
When I use this (looks promising) :
Params.ParamByName('D').Value := DateTimeToJulianDate(Plannercalendar1.Date);
The date inserted in the database is OK but the cxgrid displays the date funny (bellow):
Changing of the parameter does not help either.
VALUES (julianday(:D),
If I change the DATE field to CHAR in the database then the :
DateToStr(Plannercalendar1.Date);
works properly....
I store datetime in SQL timestamps:
SomeQuery.ParamByName('PARAM').AsSQLTimeStamp := DateTimeToSQLTimeStamp(Now);
SomeDateTime := SQLTimeStampToDateTime(SomeQuery.ParamByName('PARAM').AsSQLTimeStamp);
Take a look at this functions From Data.SqlTimSt unit:
DateTimeToSQLTimeStamp
SQLTimeStampToDateTime

How to insert a string into sQlite.sdb (FireDac)?

I am trying to insert some text into a sqlite database.
I am using FireDac connection and FireDac Query(FDQuery1) to connect to the sqLite database.
Here is code.
FDQuery1.SQL.Text := 'select * from Invoice where Name = :Name';
FDQuery1.ParamByName('Name').AsString := '123';
FDQuery1.Open;
LinkListControlToField1.BindLink.FillList
I seems there is a new record inserted in the database but all fields are null.
What could be the problem ?
Now i am using
NEW_NAME:='dfddf';
SQL :='INSERT INTO INVOICE (Name) VALUES (:NEW_NAME)';
fdquery1.Close;
fdquery1.SQL.Text:= SQL;
FdQuery1.Open();
FDQuery1.Insert;
//Fdquery1.ParamByName('New_Name').AsString := NEW_NAME;
//fdquery1.SQL.Text:='INSERT INTO INVOICE (Name) VALUES (:NEW_NAME)';
fdquery1.FieldByName('Name').AsString := quotedstr(NEW_NAME);
//fdquery1.ExecSQL();
fdquery1.Post;
I am getting eerror message.
FireDac, Phys,Sqlite - 308 Can not open/define command, wiich does not return result sets. Hint use Execute? ExecSql metnod for non Select commands.
As you can see from the commented code I am trying the ExecSql but same error.
While SELECT sql statements cannot insert data into a table, records can be inserted/appended through TDataset descendents that are connected to a table via a SELECT sql statement.
For example:
FDQuery1.SQL.Text := 'select * from Invoice';
FDQuery1.Open;
NEW_NAME:='dfddf';
FDQuery1.Append; // or FDQuery1.Insert;
FDQuery1.FieldByName('Name').AsString := NEW_NAME;
// set other column values as needed
FDQuery1.Post;
If you prefer to use an INSERT:
FDQuery1.SQL.Text := 'INSERT INTO INVOICE (Name) VALUES (:NEW_NAME)';
NEW_NAME := 'dfddf';
FDQuery1.ParamByName('NEW_NAME').AsString := NEW_NAME;
// you will have to define parameters for each column
FDQuery1.ExecSQL;
Replace FDQuery1.Open to FDQuery1.ExecSQL;
But you statment "Select *..." dont Insert any record in database...

Resources