Creating Tables MS ACCESS with FIREDAC - delphi

I am getting an error when creating tables in MS ACCESS when using FireDAC connection (Delphi Xe6).
The A new database file is created and 3 tables are created either using TFDCommand or TFDQuery, the tables do get created, but I always get an error 'Table already exists'. Any suggestions to stop the error being raised.
I do encompass the code in a try...Except routine.
Code:
FDCommand1.CommandText.Text := 'CREATE Table [SampleData] ' +
'([SampleID] INTEGER Primary Key NOT NULL, ' +
' [RiskIDX] INTEGER NULL,' +
' [RefSampleID] Char(10) NULL,' +
' [SerialNumber] Char(60) NULL,' +
' [TestDate] DateTime NULL,' +
' [ResampleDate] DateTime NULL,' +
' [SampleDate] DateTime NULL,' +
' [SamplingPoint] Char(60) NULL,' +
' [LabTech] Char(60) NULL, SampledBy Char(60) NULL,' +
' [Status] Char(60) NULL,' +
' [Source] Char(60) NULL,' +
' [Condition] Char(60) NULL,' +
' [PlotPnt] YESNO,' +
' [Comments] Memo' +
' CONSTRAINT FKSampleId Foreign Key SerialNumber
References AssetInfo SerialNo);';
OR
FDQuery1.SQL.Text := Memo1.text; //Create table SQL command
FDQuery1.Prepare;
FDQuery1.Execute(200,0);
With some experimentation I have found that the FDCommand and FDQuery components do not work in this instance; not even if you check before hand that the tables don't exist. (I not too sure why - but it seems to me that the SQL command is sent twice hence the table exists error). However, the FDConnection.ExecSQL does work. Tried and tested (creates 3 tables with the CONSTRAINTS as I needed it to).
Regards
TomD

I didn't test this code, but it should works in your porpouse.
function tableExists(TableName:String;Connection:TFDConnection):Boolean;
var
str: TStringList;
begin
str := TStringList.Create;
try
Connection.GetTableNames('','','',str);
result := str.IndexOf(TableName) <> -1;
finally
str.Free;
end;
end;
usage:
procedure TForm2.Button1Click(Sender: TObject);
begin
if not tableExists('SampleData',FDConnection1) then
begin
//create table here
end;
end;

You are calling Execute with ATimes parameter set to 200. That means that you want execute the command in array DML mode and you have 200 rows of parameter values. And since you have no parameters, it simply executes the command that number of times which fails on second attempt when DBMS rejects to create already created table.
You can either call Execute this way (there's no need for explicit call of Prepare):
FDQuery1.SQL.Text := Memo1.Text; // Create table SQL command
FDQuery1.Execute;
Or ExecSQL which internally calls Execute the way shown above:
FDQuery1.SQL.Text := Memo1.Text; // Create table SQL command
FDQuery1.ExecSQL;

Related

DEFINE vs DECLARE - escaping quotes

I have defined a variable
define myStrings = "'abc','def'"
which I later need to use inside a procedure block and convert into a table of varchars
declare
type varcharListType is table of varchar(200);
myList varcharListType;
begin
myList := varcharListType(&myStrings);
.
.
.
end;
/
I am attempting to use either the variable or the table inside an IN clause in a create query within the procedure block
execute immediate 'create table tmp_foo '
|| 'as select * from bar '
|| 'where bar_val in (&myStrings) ';
I have tried using the REPLACE function also
myNewStrings := replace(&myStrings, '''' , '''''');
but I get an exception related to abc and def not being defined.
ISSUE:
I am getting a syntax exception because the quotes around abc and def in myString are not escaped. The value "'abc','def'" must be 'defined' rather then 'declared' so it is substituted later.
QUESTION:
Is it possible to 'define' a variable in such a way that I can use it both as table type values and also a string in the execute immediate statement?
TO REPRODUCE:
Create
create table bar (bar_id number not null, bar_val varchar2(20),
constraint bar_pk primary key (bar_id)
enable
);
Insert
insert into bar (bar_id, bar_val)
values (1, 'abc'),
(2, 'def'),
(3, 'ghi');
SAMPLE PROCEDURE
set verify off;
set serveroutput on;
define myStrings = "'abc','def'"
declare
type varcharListType is table of varchar(20);
myList varcharListType;
begin
myList := varcharListType(&myStrings);
execute immediate 'create table tmp_foo '
|| 'as select * from bar '
|| 'where bar_val in (&myStrings) ';
for i in myList.FIRST..myList.LAST loop
dbms_output.put_line('VALUE: ' || myList(i));
end loop;
end;
/
set serveroutput off;
set verify on;
The below is the approch I would take, Note the use of tablen in the loop, this is because the DBMS_UTILITY.COMMA_TO_TABLE procedure adds a null value at the end of the table.
Hope you find this helpfull
declare
myStrings varchar2(100) := '''abc'',''def''';
myList dbms_utility.uncl_array;
tablen number :=0;
begin
DBMS_UTILITY.COMMA_TO_TABLE ( replace(myStrings, '''', ''), tablen, myList);
execute immediate 'create table tmp_foo '
|| 'as select * from bar '
|| 'where bar_val in (' ||myStrings||')';
for i in myList.FIRST..tablen loop
dbms_output.put_line('VALUE: ' || myList(i));
end loop;
end;
/
Thanks to #ShaunPeterson for inspiring the solution to this issue. While it solve the issue directly it provided the correct approach so all +1s should go to him.
Where his answer fell short was that he 'declared' myStrings rather then 'defining' it.
declare
myStrings varchar2(100) := '''abc'',''def''';
NOT
define myStrings = "'abc','def'"
Herein lay the crux of the issue. In PL/SQL variables that are 'declared' for a procedure block like myStringsVar below are not substituted like 'defined' variables are. As per the OP the requirement was that 'myStrings' was first 'defined' then later transformed for use in a procedure block.
Therefore the resulting solution looks like this:
define myStrings = "''abc'',''def''"
declare
myStringsVar varchar2(100) := '&myStrings';
myList dbms_utility.uncl_array;
tablen number :=0;
begin
DBMS_UTILITY.COMMA_TO_TABLE ( replace(myStringsVar, '''', ''), tablen, myList);
execute immediate 'create table tmp_foo '
|| 'as select * from bar '
|| 'where bar_val in (' || myStringsVar||')';
for i in myList.FIRST..tablen loop
dbms_output.put_line('VALUE: ' || myList(i));
end loop;
end;
/

How Can I Create Insert and Update SQL's by pure code

I'm currently using AdoQuery's and append post commands. But for data security I want to change my code with insert into and update table name...
But I have a lot of forms and tables...
Because of that I think maybe someone has already developed code for generating insert statements.
Actually I have found a way but I'm stuck.
I have query1. it contains the fieldlist.
I'm creating a parameter list in another query from this fieldlist.
I'm updating the parameters field by field.
This is not very convenient
Can someone give me a easy ways to do this.
Note: I prefer coding this job with only standard components. I don't want to install additional components.
Maybe not the reply you want. I think you need to raise the abstraction level. You need to skip SQL. An ORM framework can do this for you. It maybe feels like a big step for you but I promise it is also a relief to just use code like:
Person.name := 'Bob';
Invoice.customer.address.street := 'Abbey road';
Edit1.text := Invoice.customer.name;
To actually update database you need to call an update method that differ depending on framework. For a list of frameworks see here. I am also aware of TMS Aurelius. I use Bold on daily use. Bold also have features like OCL, derived attributes and links in the model, some boldaware components (it updates whenever db changes). But it has one big disadvantage. It is only available for D2006/D2007. I am working for a solution on this because I think it is the best and most mature ORM framework for Delphi. See also my blog on Bold for Delphi. Ask if you have questions!
You take the fieldlist from your query.
Create a new query with parameters.
And fill in the values.
Something like this:
const
TableNameEscapeStart = '['; //SQL server, use '`' for MySQL
TableNameEscapeEnd = ']'; //SQL server, use '`' for MySQL
FieldNameEscapeStart = '[';
FieldNameEscapeEnd = ']';
function CreateInsertStatementFromTable1ToTable2(Table1, Table2: TTable): String;
var
i: integer;
comma: string;
begin
i:= 0;
Result:= 'INSERT INTO '+TableNameEscapeStart + Table2.TableName + TableNameEscapeEnd + ' (';
comma:= ' , '
while i < Table1.FieldCount do begin
if (i = Table1.FieldCount -1) then begin comma:= ' '; end;
Result:= Result + FieldNameEscapeStart + Table1.Fields.Field[i].Name + FieldNameEscapeEnd + comma;
end;
Result:= Result +' ) VALUES ( ';
i:= 0;
comma:= ' , '
while i < Table1.FieldCount do begin
if (i = Table1.FieldCount -1) then begin comma:= ' '; end;
Result:= Result +':' + IntToStr(i+1) + comma;
end; {while}
Result:= Result + ' ); ';
end;
There are three avenues for SQL injection here.
1. The field values
2. The table name
3. The field names
The first is covered by the use of parameters.
The second and third are covered, because you're using the table and field names of the table directly.
If you don't have a trusted source of table and fields names, then you need to compare these against the table and fieldnames obtained directly from the table.
See: Delphi - prevent against SQL injection
You insert the data using ParamByName (slowly) or more efficiently using Param[i] where i starts at 0.
In MySQL it's even easier:
If table1 and table2 have the same fields, the following SQL will insert all data in table2 into table1:
INSERT INTO table1 SELECT * FROM table2;

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.

Why does my parameterized query with Integer fields fail?

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

Resources