Why does my parameterized query with Integer fields fail? - delphi

I have a TQuery object, pointed to a dBase database and I'm wondering how I should go about parameterizing my insert statement.
The following INSERT-query will will work fine with qry.ExecSQL:
qry.SQL.Text :=
'INSERT INTO KUNDE ' +
'(FNAVN, ENAVN, INSTNR) ' +
'VALUES ' +
'(:FirstName, :LastName, ' + IntToStr(InstructorNo) + ' )';
qry.ParamByName('FirstName').AsString := FirstName;
qry.ParamByName('LastName').AsString := LastName;
But, this fully parameterized version fails with BDE error 'Type mismtach in expression':
qry.SQL.Text :=
'INSERT INTO KUNDE ' +
'(FNAVN, ENAVN, INSTNR) ' +
'VALUES ' +
'(:FirstName, :LastName, :InstructorNo)';
qry.ParamByName('FirstName').AsString := FirstName;
qry.ParamByName('LastName').AsString := LastName;
qry.ParamByName('InstructorNo').AsInteger := InstructorNo;
I have tried various variations of the assignment of InstructorNo, such as .Value instead of AsInteger, but they all produce the same error.
The column 'INSTNR' is defined as Numeric, maxwidth=4, decimals=0. The value I'm attempting to assign is 999.
The function parameter InstructorNo is of type Integer.
Is this some kind of known bug in BDE?
EDIT: I have partly figured this one out
I can overcome this issue on some of the fields by using .AsSmallInt instead of .AsInteger, however on another numeric field neither Integer, SmallInt or Word works. The only way around that was to manually insert the value into the SQL statement. What's so special with a dBase Numeric field with maxwidth=6 ?
ANOTHER EDIT: Finally got it
I had to use .AsFloat to get the value stored. Though a bit weird to use a float type field for a CustomerID.

By assigning the value through .AsInteger, the parameter gets marked as a 4-byte Integer.
That will not fit in a 2-byte dBase integer (4 position integer is 2 bytes).
Hence the error message.
--jeroen

Related

how i can filter the special character "ñ" in data set from delphi 7?

I have an application in delphi 7 that use a database query and bring information to client dataset. Then i need use the filter that provide the dataset to filter the info when the user press the keys. I have an issue with the character special "ñ" (aplication in spanish). The database connection is with PostgreSQL and works correctly.
SELECT * FROM public.users WHERE lastname ILIKE '%ñ%'
The function to filter the data is the following (simplified for this case):
procedure TfmForm.gdPersonalKeyPress(Sender: TObject;
var Key: Char);
begin
ClientDataSetUsers.Filtered := False;
ClientDataSetUsers.Filter := ' UPPER(lastname) LIKE ' + #39 + '%' + UpperCase(lbSearch.Caption) + '%' + #39;
ClientDataSetUsers.Filtered := True;
end;
This functionality also works well but not when I insert the character "Ñ" (it does not bring anything). Is there a way to filter the dataset using this character?
AnsiUpperCase('ñ')
was the answer that solved the problem

OnCalculate field in a table produces too long number

I have such a table in SQLite :
CREATE TABLE [TABLE1] (
[T_ID] INTEGER PRIMARY KEY ON CONFLICT IGNORE AUTOINCREMENT,
[DATE] DATE UNIQUE ON CONFLICT IGNORE,
[FIELD1] INTEGER,
[FIELD2] INTEGER,
[FIELD3] INTEGER,
[FIELD4] REAL);
I added a 2 new calculated fields to the TABLE1
called SUM1 (integer) and SUM2 (float).
procedure TForm3.UniTable1CalcFields(DataSet: TDataSet);
begin
UNITable1.Fields.FieldByName('SUM1').asInteger := (UNITable1.Fields.FieldByName('FIELD1').AsInteger) + (UNITable1.Fields.FieldByName('FIELD2').AsInteger) + (UNITable1.Fields.FieldByName('FIELD3').AsInteger);
UNITable1.Fields.FieldByName('SUM2').asFloat := (UNITable1.Fields.FieldByName('SUM1').AsInteger) / (UNITable1.Fields.FieldByName('FIELD4').AsInteger) ;
end;
This kind of works but I am having trouble vwith SUM2 displaying a 15 digit number.
I would like to display a number with only two decimals like 5,81.
I could do it in the cxGrid by setting the properties of the field to CalcEdit and set Precision to 3.
But I am wondering can this be done in code.
Is there a way I can accomplish this ?
Well 314 / 54 is 5.814814814814... . If you are using persistent fields you can set the DisplayFormat to ',0.00;; '
or in code:
TNumericField(UNITable1.Fields.FieldByName('SUM2')).DisplayFormat := ',0.00;; ';
Another option is to use the FormatFloat('0.00',myvariable) function.
'0.00' defines the format (2 decimal places), and myvariable is an aptly named variable.
The only drawbacks are that it requires an extra few lines of code to declare and assign a value to the variable, and if you wish to use this value for further calculations, the accuracy will be lower than the original floating point number you had.
procedure TForm3.UniTable1CalcFields(DataSet: TDataSet);
var
var1: real;
begin
//Assign the floating point value to the variable
var1 := (UNITable1.Fields.FieldByName('SUM1').AsInteger) / (UNITable1.Fields.FieldByName('FIELD4').AsInteger);
UNITable1.Fields.FieldByName('SUM1').asInteger := UNITable1.Fields.FieldByName('FIELD1').AsInteger) + (UNITable1.Fields.FieldByName('FIELD2').AsInteger) + (UNITable1.Fields.FieldByName('FIELD3').AsInteger);
UNITable1.Fields.FieldByName('SUM2').AsFloat := FormatFloat('0.00',var1);
end;
i haven't had a chance to test this, but the basic idea would be something along these lines.

Delphi: Help needed with errors with strings and integers

I need to make a 'Calculator' that works out and average for 3 different marks.
procedure TForm1.btnAddClick(Sender: TObject);
var
sName : string;
iprac, itheory, iproject : integer;
begin
sName := edtStudentName.text;
iprac := sedPrac.value;
iTheory := sedTheory.Value;
iProject := sedProject.Value;
lblOutput.caption := sName + ' You got ' + IntToStr (iPrac + iTheory + iProject) / 3;
end;
end.
Thats the code so far, but I get an error:
[Error] ComputerStudyMarks_u.pas(46): Incompatible types: 'String' and 'Integer'
Please be patient with me! I am a hardcore noob but this is for school.
Maybe someone can help me with this program.
I am busy using the book "Enjoy Delphi" and the code I am copying from the book itself is giving me an error.
Any help would be much appreciated!
P.S I cant add a picture of the form because I dont have enough reputation :(
iprac := sedPrac.value;
It's not clear what sedPrac is. And therefore what the Value property it. If Value is a string then you cannot assign a string to an integer. Your would need to convert to integer first, like this:
iprac := StrToInt(sedPrac.value);
Likewise for iTheory and iProject.
Of course, it may be that Value is already an integer, in which case your code is fine. My best guess is that these variables refer to TSpinEdit controls, and that Value is an integer, and so all is well. But that is just a guess.
IntToStr (iPrac + iTheory + iProject) / 3
Now, IntToStr is a function that returns a string. And there is no / operator defined for strings. Presumably you meant to write:
IntToStr((iPrac + iTheory + iProject) div 3)
Note that you have to use div since the value is an integer and / is floating point division.
Some final pieces of advice.
When presenting code, present all the information. You omitted to present some types here and so we don't know what sedPrac is.
When you present an error message, make sure that we can marry up the line number in the message with the code that you present. In your question, we don't know which line is line 46.

TAdoquery date format

I am Java developer. I have some old program in Delphi. In old version they work with mdb. I fixed it for connection with SQL Server. All SQL queries are implemented with TAdoQuery.
qryTemp.SQL.Text:='select sum(iif(ComeSumm>0,comesumm,0)),sum(iif(lostSumm>0,lostsumm,0)) from cash '+
'where (IdCashClause is null or idcashclause<>8) '+
' and cashNum='+IntToStr(i)+
' and CashType=0'+
' and format(PayDate,"dd/mm/yyyy")=format('''+DateToStr(Date)+''',"dd/mm/yyyy") ';
The program throws an exception:
Invalid column name 'dd/mm/yyyy'.
I have fixed other query for comparison:
qryTemp.SQL.Text:=' select top 1 iif(ComeSumm>0,comesumm,0) from cash '
+' where idCashReason=1 and idCashClause=8 and cashNum='+IntToStr(i)
+' and PayDate<:D'
+' order by payDate desc';
qryTemp.Parameters.ParamByName('D').Value:=DateTimeToStr(Date);
Can I quickly fix all queries for work with SQL Server without rewriting the whole project?
Assuming PayDate is defined as date/datetime in MSSQL you could use parameters as follow:
qryTemp.SQL.Text:=' select top 1 iif(ComeSumm>0,comesumm,0) from cash '
+' where idCashReason=1 and idCashClause=8 and cashNum='+IntToStr(i)
+' and PayDate<:D'
+' order by payDate desc';
qryTemp.Parameters.ParamByName('D').Value := Date;
qryTemp.Parameters.ParamByName('D').DataType := ftDateTime;
I'd also change cashNum to parameter i.e.:
...
+' where idCashReason=1 and idCashClause=8 and cashNum=:cashNum'+
...
qryTemp.Parameters.ParamByName('cashNum').Value := i;
Always prefer to use compatible data types with your parameters, rather than formatting and using strings. SQL does not need to guess your data types if you can explicitly define them.
Note: IIF was introduced in SQL Server 2012. for older version use CASE expression.
In older Non-Unicode Delphi versions, Parameters have issue with Unicode.
So, If you don't use Parameters you could use the following:
function DateTimeToSqlDateTime(const DT: TDateTime): WideString;
begin
Result := FormatDateTime('yyyy-MM-dd', DT) + ' ' + FormatDateTime('hh:mm:ss', DT);
end;
function SqlDateTimeStr(const DT: TDateTime; const Is_MSSQL: Boolean): WideString;
var
S: WideString;
begin
S := DateTimeToSqlDateTime(DT);
if Is_MSSQL then
Result := Format('CONVERT(DATETIME, ''%s'', 102)', [S])
else
Result := Format('#%s#', [S]); // MS-ACCESS
end;
And your query will look as follow:
...
+' and PayDate<' + SqlDateTimeStr(Date, True)
...
It´s very likely that the PayDate column is declared as DATE in the cash table. Considering that, your parameter should be a TDateTime and not a string, like this:
qryTemp.SQL.Text:=' select top 1 iif(ComeSumm>0,comesumm,0) from cash '
+' where idCashReason=:cashReason and idCashClause=8 and cashNum='+IntToStr(i)
+' and PayDate<:D'
+' order by payDate desc';
qryTemp.Parameters.ParamByName('D').Value := Date;
I replaced only one of the parameters, but you should consider replacing all of them, since this will improve the server performance by enabling its sentence cache.
Getting back to your original question, I guess the only way to refactor all the application would be to have a refactoring program that could parse your code, find those situations and follow a pattern to replace one piece of code by another.
I do not know any tool that can do that nowaways.
Maybe using find/replace that supports regular expressions can help you, but it certainly wont´t fix the cases in one single pass. You would have to run a series of replace phases in order to transform your code from what it is to what you want it to be.
DateToStr uses localization information contained in global variables to format the date string. Maybe this is the problem.
You can try FormatDateTime:
qryTemp.SQL.Text:='select sum(iif(ComeSumm>0,comesumm,0)),sum(iif(lostSumm>0,lostsumm,0)) from cash '+
'where (IdCashClause is null or idcashclause<>8) '+
' and cashNum='+IntToStr(i)+
' and CashType=0'+
' and format(PayDate,"dd/MM/yyyy")='''+FormatDateTime('dd/mm/yyyy',Date)+'''';

Inserting dates using "A Simple Delphi Wrapper for SQLite3"

I am using delphi 2010, and Tim Anderson's SQLite3 wrapper - http://www.itwriting.com/blog/?page_id=659 - but I am having trouble inserting dates
Here is my database creation
DB.ExecSql('CREATE TABLE Tags (No Integer NOT NULL, Title VarChar(25) NOT NULL, Creator VarChar(25) NULL, Born Date NULL, Charter Boolean Default False NULL, Owned Boolean Default False NULL, Image Blob NULL, CONSTRAINT PK_No PRIMARY KEY (No));');
Which builds and works fine. I tested it with SQLite administrator - http://sqliteadmin.orbmu2k.de/
I am even able to manually enter dates using adminstrator
here is my insert
DB.ExecSql('Insert into Tags (No, Title, Creator, Born, Charter, Owned) ' +
'values (' + quotedStr(frmTag.edtTagNo.Text) + ',' + quotedStr(frmTag.edtTitle.Text) + ',' +
quotedStr(frmTag.edtCreator.Text) + ',' + quotedStr(frmTag.edtBorn.Text) + ',' +
quotedStr(BoolToStr(frmTag.cbxCharter.Checked)) + ',' + quotedStr(BoolToStr(frmTag.cbxOwned.Checked)) + ');');
The date field is being supplied by the edtBorn control (TRzDateEdit)
I have checked the values of edtBorn.Text amd edtBorn.date prior to the insert and the date is always correct.
I have tried inserting the following ways:
frmTag.edtBorn.Text
FormatDateTime('mm/dd/yyyy',frmTag.edtBorn.Text)
quotedStr(frmTag.edtBorn.Text)
quotedStr(FormatDateTime('mm/dd/yyyy',frmTag.edtBorn.Text))
I have even tried using a parameters
DB.AddParamText('#ABorn', frmTag.edtBorn.Text);
DB.AddParamFloat('#ABorn', frmTag.edtBorn.Date);
Nothing seems to work! I get no exceptions, yet my field never gets a dat value!
Date and Time Datatype
SQLite are capable of storing dates and times as TEXT, REAL, or INTEGER values:
TEXT as ISO8601 strings ("YYYY-MM-DD HH:MM:SS.SSS").
REAL as Julian day numbers, the number of days since noon in Greenwich on November 24, 4714 B.C. according to the proleptic Gregorian calendar.
INTEGER as Unix Time, the number of seconds since 1970-01-01 00:00:00 UTC.
Showing here for example the REAL and INTEGER part.
modified SQLite3 wrapper Testfile: uTestSqlite
sSQL := 'CREATE TABLE testtable ([ID] INTEGER PRIMARY KEY,[OtherID] INTEGER NULL,';
sSQL := sSQL + '[Name] VARCHAR (255),[Number] FLOAT,[Date] INTEGER, [notes] BLOB,
[picture] BLOB COLLATE NOCASE);';
sldb.execsql(sSQL);
sldb.execsql('CREATE INDEX TestTableName ON [testtable]([Name]);');
//begin a transaction
sldb.BeginTransaction;
sSQL := 'INSERT INTO testtable(Name,OtherID,Number,Date) VALUES ("Some Name", 4,
julianday("now"), strftime("%s","now"));';
sldb.ExecSQL(sSQL);
sSQL := 'INSERT INTO testtable(Name,OtherID,Number,Date,Notes) VALUES ("Another Name",12,
julianday("2013-03-01"),strftime("%s","2013-03-01"), "More notes");';
sldb.ExecSQL(sSQL);
//end the transaction
sldb.Commit;
[...]
//query the data
sltb := slDb.GetTable('SELECT * FROM testtable');
if sltb.Count > 0 then
begin
//display first row
updateFields;
end;
Show the values:
procedure TForm1.updateFields;
var
Notes: string;
myDate :TDateTime;
begin
ebName.Text := sltb.FieldAsString(sltb.FieldIndex['Name']);
ebID.Text := inttostr(sltb.FieldAsInteger(sltb.FieldIndex['ID']));
if TryJulianDateToDateTime(sltb.FieldAsDouble(sltb.FieldIndex['Number']),myDate)
then
ebNumber.Text := DateTimeToStr(myDate)
else
ShowMessage('Not a valid Julian date');
myDate:=UnixToDateTime(sltb.FieldAsInteger(sltb.FieldIndex['Date']));
ebDate.Text := DateTimeToStr(myDate);
[...]
end;
Output:
In your problem with DB.ExecSql('CREATE TABLE Tags (..., Born Date NULL,...);');
replace in DB.ExecSql('Insert into Tags (....) VALUES (...
+ quotedStr(frmTag.edtBorn.Text)
with
'strftime("%Y-%m-%d","'+frmTag.edtBorn.Text+'")'
The value of frmTag.edtBorn.Text must be like 1975-10-21
You can get it with:
ebDate.Text := sltb.FieldAsString(sltb.FieldIndex['Born']);
I've made my own 'really light' SQLite3 wrapper, but use Variants to decide whether which sqlite3 internal type to use: https://github.com/stijnsanders/TSQLite
There I've found the 'loose typing' sqlite3 internally works really well, and I found out that Delphi's dates stored in a Variant turn into a floating point value in SQLite3 (which TDateTime actually is by the way). A possible downside is the date values are a bit clumsy to manipulate from SQL. I regret to see you've already tried AddParamFloat (and FieldAsFloat) and this didn't appear to work.
So I suggest you store the date as a string, using one if the sqlite date formats, for example using FormatDateTime('yyyy-mm-dd hh:nn:ss.zzz',d). See more here: http://www.sqlite.org/lang_datefunc.html
I use both Delphi 2010 and Tim Anderson's SQLite3 wrapper.
Here's what I use to create & use a datetime field. Its fairly straight forward and has been working for me. I trust that you can figure out the concept illustrated below with me having to write you a demo program.
SQL to create field:
sSQL := 'CREATE TABLE [someTable] (' +
' [somefield1] VARCHAR(12),' +
' [somefield2] VARCHAR(12),' +
' [myDateTime] DATETIME );';
SQL to populate field:
sSQL := 'INSERT INTO someTable(somefield1, somefield2, myDateTime)' +
' VALUES ( "baloney1", "baloney2","' + FloatToStr(Now) + '");';
Example of retrieving data from field:
var
sDBFilePathString: string;
sl3tbl: TSqliteTable;
fsldb : TSQLiteDatabase;
FromdbDTField : TDateTime;
begin
...
...
fsldb := TSQLiteDatabase.Create(sDBFilePathString);
sl3tbl := fsldb.GetTable('SELECT * FROM someTable');
FromdbDateTime := StrToFloat(sl3tbl.FieldAsString(sl3tbl.FieldIndex['myDateTime']));
Showmessage('DT: ' + DateTimeToStr(FromdbDTField));
end;
Result:
**DT: 10/10/2013 1:09:53 AM**
I leave it to you to make things prettier or more elegant.

Resources