Are unique indices on Access text fields always case insensitive? - delphi

I created an MS Access table using the following code:
tbl := Database.CreateTableDef('English', 0, '', '');
try
fld := tbl.CreateField('ID', dbLong, 0);
fld.Attributes := dbAutoIncrField + dbFixedField;
tbl.Fields.Append(fld);
fld := tbl.CreateField('Content', dbText, 255);
fld.Required := true;
fld.AllowZeroLength := false;
tbl.Fields.Append(fld);
Database.TableDefs.Append(tbl);
idx := tbl.CreateIndex('PrimaryKey');
idx.Fields.Append(idx.CreateField('ID', EmptyParam, EmptyParam));
idx.Primary := True;
idx.Unique := true;
tbl.Indexes.Append(idx);
idx := tbl.CreateIndex('IX_Content');
idx.Fields.Append(idx.CreateField('Content', EmptyParam, EmptyParam));
idx.Primary := false;
idx.Unique := true;
tbl.Indexes.Append(idx);
finally
tbl := nil;
end;
This works fine until I try to insert the two strings 'Field type' and 'Field Type' into this table. I get an error telling me that the unique index restricts me from doing that. As you can see they only differ in the case of the second word. Since I did not explicitly make the index case insensitive (I wouldn't even know how to do that), I don't quite understand why this happens. Are indices on text fields always case insensitive in MS Access? If not, what am I doing wrong?

Access Jet databases are fundamentally case insensitive. That is your problem. As far as I know there is no way to make an Access index case sensitive.

Use Binary field
Case insensivity problem in Microsoft Access was long time ago addressed in article KB244693 published by Microsoft and still can be found in Web archive.
Basically, the solution is to add a Binary field into your MS Access table and that one is finally case sensitive (uses Unicode for storing binary content) and still can be used as a text field with operators =, LIKE etc.
The field of type Binary cannot be added via the UI, but you add it to your existing table using SQL statement like this:
ALTER TABLE Table1
ADD COLUMN BinaryField1 BINARY(50)
Then you can manage it normally via the Access UI.

Related

how to copy a ftBlob with firedac arrayDML

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.

Datasnap\FireDAC: Query executed twice

I have the following problem:
1) I use Delphi XE7 to develop a 3-layer system.
2) The server layer, created with datasnap using REST.
3) I use Firebird as database and the access is performed with FireDAC.
4) I have a sequence with value 01.
5) I created the following query in the server layer:
Select GEN_ID (gen_my_sequence, 1) from rdb $ database
6) On the server returns the sequence value in the query is: 02.
7) But the client layer returns 03.
I do not understand why the query is executed twice.
Can anyone help me?
This is the nature of generators (sequences) in firebird. Their value is increased every time you request it and the value of the generator is updated from that request and remains updated. Also generators live outside of transaction control.
See this firebirdsql generatorguide-basics. It doesn't matter where you request it from.
I use technical standards that the Embarcadero indicates.
What I realized was this:
1) The unit Data.FireDACJSONReflect in TFDJSONInterceptor.ItemListToJSONObject routine has this block of code:
if not LActive then
LDataSet.Active := True;
try
LJSONDataSet := DataSetToJSONValue(LDataSet);
// Use AddPair overload that will accept blank key
AJSONObject.AddPair(TJSONPair.Create(LPair.Key, LJSONDataSet))
finally
if not LActive then
LDataSet.Active := False;
end;
See he activates the query once, causing the sequence to be incremented.
But in DataSetToJSONValue (LDataSet) routine; This code block is:
if (LMemTable = nil) then
begin
LMemTable := TFDMemTable.Create(nil);
LAdapter := TFDTableAdapter.Create(nil);
LMemTable.Adapter := LAdapter;
LAdapter.SelectCommand := ADataSet.Command;
LMemTable.Active := True;
end;
See he again activates the query, where the sequence is again incremented.
Now I do not know if I made a mistake or if it is a bug, but I created a new class inherited from TFDMemTable and thought there was some mistake in this class, but did a test with TFDMemTable component, standard component of FireDAC, and even then the activation of any query is performed twice, because the code does not consider any of these two classes, as a TFDCustomMemTable, even though they were inherited directly from this class.
I commented the code of DataSetToString routine (const ADataSet: TFDAdaptedDataSet) that looked like this:
LMemTable := nil;
LAdapter := nil;
try
//if (ADataSet is TFDCustomMemTable) then
LMemTable := TFDCustomMemTable(ADataSet);
{if (LMemTable = nil) then
begin
LMemTable := TFDMemTable.Create(nil);
LAdapter := TFDTableAdapter.Create(nil);
LMemTable.Adapter := LAdapter;
LAdapter.SelectCommand := ADataSet.Command;
LMemTable.Active := True;
end;}
In this way the problem was solved, and the performance of the application seemed to have improved.

I have a syntax error in my insert into statement

I'm using a MS Access database, with the following columns in the Admins table:
Column Type
====== ====
Name Text
Surname Text
Dateadded Date/time
Adminnumber Number(long integer)
Password Text
ID type Autonumber (Not sure if ID is relevant)
This is my code but it keeps giving me a syntax error.
ADOquery1.Active := false;
adoquery1.sql.Text := 'insert into Admins(Name, surname, Adminnumber, Dateadded,password)Values('''+edit11.Text+''', '''+edit12.text+''', '''+edit13.Text+''', '''+edit14.Text+''', '''+edit15.text+''')';
ADOquery1.ExecSQL;
Adoquery1.SQL.Text := 'select * from Admins';
ADOquery1.Active := true;
i have been trying for a day to figure it out but its the same error no matter what code i use. The error is
Project project1.exe raised exception class eoleException
with message 'Syntax error in INSERT INTO statement'.
i have also tried:
ADOquery1.SQL.Add('Insert into admins');
ADOquery1.SQL.Add('(Name , Surname, Dateadded, Adminnumber, Password)');
ADOquery1.SQL.Add('Values :Name, :Surname, :Dateadded, :adminnumber :Password)');
ADOquery1.Parameters.ParamByName('Name').Value := edit11.Text;
ADOquery1.Parameters.ParamByName('Surname').Value := edit12.Text;
ADOquery1.Parameters.ParamByName('Dateadded').Value := edit13.Text;
ADOquery1.Parameters.ParamByName('Password').Value := edit14.Text;
ADOquery1.Parameters.ParamByName('Adminnumber').Value := edit15.Text;
ADOquery1.ExecSQL;
ADOquery1.SQL.Text := 'Select * from admins';
ADOquery1.Open ;
But this code gives me a problem with the from clause
The problem is that Name (and possibly Password) is a reserved word in MS Access. It's a poor choice for a column name, but if you must use it you should escape it by enclosing it in square brackets ([]). You're also missing an opening parenthesis (() after your VALUES statement, and a comma after the :adminnumber parameter.
ADOquery1.SQL.Add('Insert into admins');
ADOquery1.SQL.Add('([Name] , [Surname], [Dateadded], [Adminnumber], [Password])');
ADOquery1.SQL.Add('Values (:Name, :Surname, :Dateadded, :adminnumber, :Password)');
ADOquery1.Parameters.ParamByName('Name').Value := edit11.Text;
ADOquery1.Parameters.ParamByName('Surname').Value := edit12.Text;
ADOquery1.Parameters.ParamByName('Dateadded').Value := edit13.Text;
ADOquery1.Parameters.ParamByName('Password').Value := edit14.Text;
ADOquery1.Parameters.ParamByName('Adminnumber').Value := edit15.Text;
ADOquery1.ExecSQL;
ADOquery1.SQL.Text := 'Select * from admins';
ADOquery1.Open;
(The error can't be moving around, as you say in the comments to your question. The only line that can possibly cause the problem is the ADOQuery1.ExecSQL; line, as it's the only one that executes the INSERT statement. It's impossible for any other line to raise the exception.)
You should make some changes here that are pretty important to the maintainability of your code.
First, break the habit immediately of using the default names for controls, especially those you need to access from your code later. You change the name by changing the Name property for the control in the Object Inspector.
It's much easier in the code to use NameEdit.Text than it is to use Edit1.Text, especially by the time you get to Edit14. It would be much clearer if Edit14 was named PasswordEdit instead, and you'll be happy you did six months from now when you have to change the code.
Second, you should avoid using the default variant conversion from string that happens when you use ParamByName().Value. It works fine when you're assigning to a text column, but isn't really good when the type isn't text (such as when using dates or numbers). In those cases, you should convert to the proper data type before doing the assignment, so that you're sure it's done correctly.
ADOQuery1.ParamByName('DateAdded').Value := StrToDate(DateEdit.Text);
ADOQuery1.ParamByName('AdminNumber').Value := StrToInt(AdminNum.Text);
Finally, you should never, ever use string concatenation such as 'SOME SQL ''' + Edit1.Text + ''','''. This can lead to a severe security issue called SQL injection that can allow a malicious user to delete your data, drop tables, or reset user ids and passwords and giving them free access to your data. A Google search will find tons of information about the vulnerabilities that it can create. You shouldn't even do it in code you think is safe, because things can change in the future or you can get a disgruntled employee who decides to cause problems on the way out.
As an example, if a user decides to put John';DROP TABLE Admins; into edit14 in your application, and you call ExecSQL with that SQL, you will no longer have an Admins table. What happens if they instead use John';UPDATE Admins SET PASSWORD = NULL; instead? You now have no password for any of your admin users.

How do I sort a CSV file in a TTable?

I have a TTable, and I am loading CSV files to this TTable. Three fields are there: Id, Hits & Path.
I made some lookup fields to this TTable with another query.
I want to sort the table. I am getting the message "Capability not supported." when I try to call AddIndex('ndxHits','HITS',[]);
Here is my code:
with DM.TblCVResults do
begin
try
Active := False;
TableName := 'C:\CSV\123.txt';
Active := True;
AddIndex('ndxHits','HITS',[]);
AddIndex('ndxCandidate','LkCandidate',[]);
AddIndex('ndxLastCV','LkLastCV',[]);
AddIndex('ndxPostCode','LkPostCode',[]);
IndexDefs.Update;
Active := True;
DM.TblCVResults.IndexName := 'ndxHits';
except
on E: Exception do
MsgError(E.Message);
end;
end
Your previous question mentioned you were using ttASCII as the TableType. ttASCII tables, AFAIK, don't support indexes.
Your best bet is to load the ttASCII TTable content into a TClientDataset` (CDS), which does support indexes. I haven't tested with a ttASCII table as the source, but it should be as simple as:
Add a TDatasetProvider component to your application. Set it's DataSet property to your TTable.
Add a TClientDataSet component to your application. Set it's ProviderName to the DataSetProvider you added above. (I've named it CDS in the steps below.)
Open both the Table and the ClientDataSet (CDS), in that order.
Table1.Active := True;
CDS.Active := True;
Turn off updating of the TTable if you don't need it. (It's much faster.)
CDS.LogChanges := False;
Run the following code to create the indexes:
// Repeat for each additional index
with CDS.IndexDefs.AddIndexDef do
begin
Name := 'ndxHits';
Fields := 'Hits';
Options := [];
end;
Set the ClientDataSet's IndexName property to the index you want active:
CDS.IndexName := 'ndxHits';
Use the ClientDataSet like you would any other dataset. Search it using Locate or FindKey, add to it using Insert or Append, filter it, and so forth.

Delphi 5 & Crystal XI Rel. 2 (RDC) how to?

I'm trying to work with the class from JosephStyons but I do get an "Invalid Index" Error on the line where the "User ID" should get set.
FRpt.Database.Tables[i].ConnectionProperties.Item['User ID'] := edUserName.Text;
Here's my environment:
WinXP Sp3, Crystal Reports Developer XI Rel.2 SP4, Delphi 5 Update Pack 1
Any help or ideas greatly appreciated!
Thx,
Reinhard
Your value for [i] could be the culprit...I can't remember for sure but I believe the first table will be Table[1] instead of Table[0] as one would expect.
I altered my loop to use:
CrTables := CrDatabase.Tables;
for crTableObj in crTables do
You might try stepping through the table using a for loop as shown above or by starting with 1 instead of 0.
I hope this helps.
Put a break point on that line and use Evaluate/Modify.
It will return an error if you try something invalid.
Examine FRpt.Database.Tables[i] and see if it's valid for what you think are the min and max values for i.
If Tables is an array, one way to avoid that is to use ...Low(Tables) to High(Tables)
If you get your Table Ok, examine FRpt.Database.Tables[i].ConnectionProperties.Item['User ID'] and see if it's valid.
It might be that the Item getter does not like the space embedded in "User ID". Some products need either to surround by special characters like "[User ID]", other to replace by an underscore like "User_ID"
Are you also setting the password, server name and database name?
procedure TReports.LogonToDBTables(cReport:
CrystalDecisions.CrystalReports.Engine.ReportDocument;
ConnInfo: ConnectionInfo);
var
CrDataBase: Database;
CrTables: Tables;
CrTableObj: TObject;
CrTable: Table;
CrTableLogonInfo: TableLogonInfo;
iSubReportIndex: smallint;
begin
CrDataBase := CReport.Database;
CrTables := CrDatabase.Tables;
cReport.DataSourceConnections[0].IntegratedSecurity := False;
for crTableObj in crTables do
begin
crTable := CrystalDecisions.CrystalReports.Engine.Table(crTableObj);
crTableLogonInfo := crTable.LogOnInfo;
crTableLogonInfo.ConnectionInfo := ConnInfo;
crTable.ApplyLogOnInfo(crTableLogonInfo);
end;
end;
function TReports.GetConnectionInfo(): ConnectionInfo;
var
cTemp: ConnectionInfo;
begin
cTemp := ConnectionInfo.Create();
cTemp.AllowCustomConnection := True;
cTemp.ServerName := GetServerName();
cTemp.DatabaseName := GetDBName();
cTemp.UserID := GetDBUserID();
cTemp.Password := GetDBPassword();
Result := cTemp;
end;

Resources