ADO insert a record into access DB using delphi - delphi

Am learning how to use insert into statements and, with my access database, am trying to insert a single record. The table I'm inserting a new record into has three fields: StockID (AutoN), Description (Text), Cost (Number). I've looked at previous posts but the posted solutions seem to go beyond my basic level of Insert Into...which is what I'm interested in. Anyway, here is my code...
adoQuery1.Active := true;
adoQuery1.SQL.Clear;
adoQuery1.SQL.Add('INSERT INTO Stock (StockID,Description,Cost) VALUES (4,Cheese,5)');
adoQuery1.open;
adoQuery1.Close;
It compiles fine, but when press a command button to invoke the above, I get the following message:
'ADOQuery1: "Missing SQL property".'
what am I doing wrong?
Thanks, Abelisto. Your last post looks complex indeed...but I did my own little version since your last solution got me up and running. It works so I'm very chuffed. Am now going to focus on DELETE FROM using combobox (for field selection) and user value. Here was my solution I got working... ;)
x:=strtoint(txtStockID.Text);
y:=txtDescription.Text;
z:=strtoCurr(txtCost.Text);
adoQuery1.SQL.Clear;
adoQuery1.SQL.Add('INSERT INTO tblStock (StockID,Description,Cost)');
adoQuery1.SQL.Add('VALUES (:StockID,:Description,:Cost)'); // ':StockID' denotes a parameter
adoQuery1.Parameters.ParamByName('StockID').Value:= x;
adoQuery1.Parameters.ParamByName('Description').Value:= y;
adoQuery1.Parameters.ParamByName('Cost').Value:= z;
adoQuery1.ExecSQL;
adoQuery1.Close;

Using parameters is more efficient then constant SQL statements.
Additional to my comments here is some useful functions which I using frequently to call SQL statements with parameters (Maybe it will be useful for you too):
function TCore.ExecQuery(const ASQL: String; const AParamNames: array of string;
const AParamValues: array of Variant): Integer;
var
q: TADOQuery;
i: Integer;
begin
if Length(AParamNames) <> Length(AParamValues) then
raise Exception.Create('There are different number of parameter names and values.');
q := GetQuery(ASQL) as TADOQuery;
try
for i := Low(AParamNames) to High(AParamNames) do
SetParamValue(q, AParamNames[i], AParamValues[i]);
q.ExecSQL;
Result := q.RowsAffected;
finally
q.Free;
end;
end;
function TCore.GetQuery(const ASQL: String): TDataSet;
begin
Result := TADOQuery.Create(Self);
(Result as TADOQuery).CommandTimeout := 0;
(Result as TADOQuery).Connection := Connection;
(Result as TADOQuery).SQL.Text := ASQL;
end;
procedure TCore.SetParamValue(AQuery: TDataSet; const AName: string; const AValue: Variant);
var
i: Integer;
q: TADOQuery;
begin
q := AQuery as TADOQuery;
for i := 0 to q.Parameters.Count - 1 do
if AnsiSameText(AName, q.Parameters[i].Name) then
begin
case VarType(AValue) of
varString, varUString:
q.Parameters[i].DataType := ftString;
varInteger:
q.Parameters[i].DataType := ftInteger;
varInt64:
q.Parameters[i].DataType := ftLargeint;
end;
q.Parameters[i].Value := AValue;
end;
end;
And usage example in your case:
Core.ExecQuery(
'INSERT INTO Stock (StockID, Description, Cost) VALUES (:PStockID, :PDescription, :PCost)',
['PStockID', 'PDescription', 'PCost'],
[4, 'Cheese', 5]);

Related

How to internally process filtered tDataSet records not to be shown on tDBGrid the result

In the following tFDMemTable I try to sum value of records whose ID field starting letter A. A1, A2 and the result should be 4.
type
TForm1 = class(TForm)
FDMemTable1: TFDMemTable;
DBGrid1: TDBGrid;
DataSource1: TDataSource;
Button1: TButton;
Button2: TButton;
procedure FormCreate(Sender: TObject);
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
end;
procedure TForm1.FormCreate(Sender: TObject);
var
_FieldDef: TFieldDef;
begin
_FieldDef := FDMemTable1.FieldDefs.AddFieldDef;
_FieldDef.Name := 'ID';
_FieldDef.DataType := ftString;
_FieldDef.Size := 5;
_FieldDef := FDMemTable1.FieldDefs.AddFieldDef;
_FieldDef.Name :='value';
_FieldDef.DataType := ftInteger;
FDMemTable1.CreateDataSet;
FDMemTable1.Append;
FDMemTable1.FieldValues['ID'] := 'A1';
FDMemTable1.FieldValues['value'] := 1;
FDMemTable1.Append;
FDMemTable1.FieldValues['ID'] := 'B1';
FDMemTable1.FieldValues['value'] := 2;
FDMemTable1.Append;
FDMemTable1.FieldValues['ID'] := 'A2';
FDMemTable1.FieldValues['value'] := 3;
FDMemTable1.Append;
FDMemTable1.FieldValues['ID'] := 'B2';
FDMemTable1.FieldValues['value'] := 4;
end;
I wrote the following code but it changes tDBGrid as filtered. What I want is just an internal process that tDBGrid should stay without any change.
procedure TForm1.Button1Click(Sender: TObject);
var
_ValueSum: Integer;
i: Integer;
begin
FDMemTable1.Filter := 'ID like ' + QuotedStr('A%');
FDMemTable1.Filtered := True;
_ValueSum := 0;
FDMemTable1.FindFirst;
for i := 0 to FDMemTable1.RecordCount - 1 do
begin
_ValueSum := _ValueSum + FDMemTable1.FieldValues['value'];
FDMemTable1.FindNext;
end;
Button1.Caption := IntToStr(_ValueSum);
end;
I know tDataSet.Locate doesn't allow NEXT SEARCH that I tried a primitive way like this. It works fine but seems a little stupid.
procedure TForm1.Button2Click(Sender: TObject);
var
_ValueSum: Integer;
i: Integer;
begin
_ValueSum := 0;
FDMemTable1.First;
for i := 0 to FDMemTable1.RecordCount do
begin
if Copy(FDMemTable1.FieldValues['ID'], 1, 1) = 'A' then
begin
_ValueSum := _ValueSum + FDMemTable1.FieldValues['value'];
end;
FDMemTable1.FindNext;
end;
Button2.Caption := IntToStr(_ValueSum);
end;
When I disconnect tFDMemTable and tDBGrid or set inactive before filtering to hold the last grid status, the grid changes to blank one. Is the last code the best solution or is there any better way which shows not filtered result while the filtering is working?
There are several things which, if not "wrong", are not quite right with your code.
You should be using Next, not FindNext to move to the next row in the dataset. Next moves to the next row in the dataset, whereas FindNext moves to the next row which matches search criteria you have already set up e.g. using DataSet.SetKey; ... - read the online help for FindKey usage.
You should NOT be trying to traverse the dataset using a For loop; use a While not FDMemData.Eof do loop. Eof stands for 'End of file' and returns true once the dataset is on its last row.
You should be calling FDMemTable1.DisableControls before the loop and FDMemTable1.EnableControls after it. This prevents db-aware controls like your DBGrid from updating inside the loop, which would otherwise slow the loop down as the grid is updating.
Unless you have a very good reason not to, ALWAYS clear a dataset filter in the same method as you set it, otherwise you can get some very confusing errors if you forget the filter is active.
Try to avoid using RecordCount when you don't absolutely need to. Depending on the RDMS you are using, it can cause a lot of avoidable processing overhead on the server and maybe the network (because with some server types it will cause the entire dataset to be retrieved to the client).
Change your first loop to
procedure TForm1.Button1Click(Sender: TObject);
var
_ValueSum : Integer;
begin
_ValueSum := 0;
FDMemTable1.Filter := 'ID like ' + QuotedStr('A%');
try
FDMemTable1.DisableControls;
FDMemTable1.First;
while not FDMemTable1.Eof do begin
_ValueSum:= _ValueSum + FDMemTable1.FieldByName('Value').AsInteger;
FDMemTable1.Next;
end
finally
FDMemTable1.Filter := '';
FDMemTable1.Filtered := False;
FDMemTable1.EnableControls;
end;
Button1.Caption := IntToStr(_ValueSum);
end;
If you do that, you don't need your Button2Click method at all.
As noted in a comment, you can use a TBookMark to record your position in the dataset before the loop and return to it afterwards, as in
var
_ValueSum : Integer;
BM : TBookMark;
begin
_ValueSum := 0;
BM := FDMemTable.GetBookMark;
FDMemTable1.Filter := 'ID like ' + QuotedStr('A%');
try
[etc]
finally
FDMemTable1.Filter := '';
FDMemTable1.Filtered := False;
FDMemTable1.GotoBookMark(BM);
FDMemTable1.FeeBookMark(BM);
FDMemTable1.EnableControls;
end;
By the way, you can save yourself some typing and get more concise code by using the InsertRecord method as in
FDMemTable1.InsertRecord(['A1', 1]);
FDMemTable1.InsertRecord(['B1', 2]);
FDMemTable1.InsertRecord(['A2', 3]);
FDMemTable1.InsertRecord(['B2', 4]);
Btw#2: The time to use FindKey is after you've set up a key to find, using by calling SetKey than then setting the key value(s).
For ordinary navigation of a dataset, use the standard navigation methods, e.g. Next, Prior, First, Last, MoveBy etc.
FireDAC has another interesting option - Aggregates:
procedure TForm1.Button1Click(Sender: TObject);
begin
FDMemTable1.Aggregates.Clear;
with FDMemTable1.Aggregates.Add do
begin
Name := 'SUM';
Expression := 'sum(iif(ID like ''A%'', value, 0))';
Active := True;
end;
FDMemTable1.AggregatesActive := True;
FDMemTable1.Refresh;
Button1.Caption := VarToStr(FDMemTable1.Aggregates[0].Value));
end;

Delphi: check if Record of DataSet is visible or filtered

At work we have a component called a "ClientdatasetGrid",
which allows the user to sort the records of the grid by clicking on one or multiple column-titles.
I have made a component for work also, a descendant from TEdit, which I call TDBFilterEdit.
once you assign a DataSet or DBGrid to it, it creates an OnFilterRecord event for the DataSet and after you stop changing the text that Event is executed.
the problem arises whenever the Dataset is already filtered and the user sorts the grid.
the grid-component adds IndexDefs to the Clientdataset by first deleteing the current IndexDef, Updating, Adding the new Index and updating again.
whenever an index is deleted or added my OnFilterRecord event is triggered.
I mitigated this by disableing controls and NIL-ing the OnFilterRecord event from inside the grid until the new index is added.
cds.DisableControls();
try
extProc:=nil;
if (TMethod(cds.OnFilterRecord).Code<>nil) and (TMethod(cds.OnFilterRecord).Data<>nil) then
begin
TMethod(extProc):=TMethod(cds.OnFilterRecord);
cds.OnFilterRecord:=nil;
end;
...
... //<-- Delete Index & create new Index
...
finally
cds.OnFilterRecord:=extProc;
cds.EnableControls();
end;
Once the Event is assigned again, it is immeadeately called and is iterating through all X records even though the user may only see 5.
Now I am searching for a way to see if a record is already filtered out so I can skip it inside my filter-method if the text hasn't changed.
Edit: Since a MVCE has been demanded I'll post a short version of my OnFilterRecord procedure.
the following procedure is executed everytime the component hasn't recieved an input for 1 second
fStringtypes and fTimeTypes are both a set of TFieldType
fStringTypes:=[ftString,ftMemo,ftFMTMemo,ftFixedChar,ftWideString];
fTimeTypes:=[ftDate,ftTime,ftDateTime,ftTimeStamp];
after the procedure is completely finished the timer is disabled and controls are enabled again.
procedure TDBEditFilter.FilterRecords(DataSet:TDataSet; var Accept:Boolean);
var
...
begin
//initiliaztion//
s:=FilterText; //Filtertext=User Input into the TDBEditFilters Textfield
TestFloat:=0;
Accept:=False;
/////////////////
for i:=0 to fDBGrid.Columns.Count-1 do //for all DBGrid-Columns
begin
if fDataSet.FieldByName(fDBGrid.Columns[i].FieldName).DataType in fStringTypes then
begin
Strvalue:=fDataSet.FieldByName(fDBGrid.Columns[i].FieldName).AsString;
Accept:=AnsiContainsText(Strvalue,s); //<--to ignore Upper/lowercase
end
else if fDataSet.FieldByName(fDBGrid.Columns[i].FieldName).DataType in fTimeTypes then
begin
StrValue:=DateTimeToStr(fDataSet.FieldByName(fDBGrid.Columns[i].FieldName).As DateTime,Local_Form_Settings);
Accept:=Pos(StrValue,s)<>0;
end
else if fDataSet.FieldByName(fDBGrid.Columns[i].FieldName).DataType=ftBlob then
begin
//ignore Blob
end
else //whatever fieldtype is left must be a numeric Field-type like integer or float
begin
if TryStrToFloat(s,TestFloat)=True then
begin
Accept:=(TestFloat=fDataSet.FieldByName(fDBGrid.Columns[i].FieldName).AsFloat);
end;
end;
if Accept=True then break; //stop checking this record and check next record
end;
end;
I thought I would post this as a separate answer because I've been experimenting
with a "Filter TEdit" that works in a similar way as I'm guessing yours does, and it doesn't
seem to exhibit any particular performance problems. My main assumption is that you are using one filter TEdit per datafield of interest, rather that a single one into which the user types a compound Sql-like expression including the field names, comparison operators, etc.
The number of guesses I've had to make is why I said it would have been helpful for you to include an MCVE.
I've written it to be self-contained, i.e. it generates its own data instead of needing
an external database.
As you'll see if you try it, with a CDS containing, say, 3000 records,
the time to update the filters is a few tens of milliseconds (under 20 on my laptop).
If the CDS contains 30000 records, the filter update time increases roughly linearly
to about 200 ms which seems perfectly acceptable from a gui-responsivenes pov.
(Traditionally, TCDSs have been regarded as hitting a brick wall performance-wise when the number of records gets into the tens of thousands)
Note that for simplicity
a) I haven't used a DateTime fiield for BirthDate or whatever,
because of the complications of dealing with partial dates inputted by the user.
b) In the OnFilterRecord event, the LastName, FirstName and Age comparisons
are done by comparing the field as a string with the corresponding filter expression.
c) The Filter expressions, if non-blank are left- and right-padded with asterisks
and the value comparisons are done using the MatchesMask function from the Masks unit.
See FilterExpr.
d) The IndexDef's FieldNames are composed of the names of the fields for which
the filter edit's text is non-blank.
e) If the gui-updating is too slow if the user rapidly types several
characters in succession into the TEdits, you can work around this by
replacing the TEdits' OnChange event code by code in their KeyUp event
which enables a TTimer which has an interval of, say, 150 ms. Then, in its OnTimer, call UpdateFilter.
Code:
TForm1 = class(TForm)
DBGrid1: TDBGrid;
CDS1: TClientDataSet;
DataSource1: TDataSource;
Memo1: TMemo;
CDS1ID: TIntegerField;
CDS1Age: TIntegerField;
CDS1LastName: TStringField;
CDS1FirstName: TStringField;
edLastNameFilter: TEdit;
edFirstNameFilter: TEdit;
edAgeFilter: TEdit;
procedure CDS1FilterRecord(DataSet: TDataSet; var Accept: Boolean);
procedure edLastNameFilterChange(Sender: TObject); // Set the OnChange events for the
// FirstName and Age TEdits to this, too
procedure FormCreate(Sender: TObject);
private
procedure Log(const Title, Msg: String);
function FilterExpr(const Input: String): String;
protected
public
LastNameFilter,
FirstNameFilter,
AgeFilter : String;
IndexFields : String;
IndexDef : TIndexDef;
procedure UpdateFilterExprsAndIndex;
procedure UpdateFilter;
end;
[...]
rocedure TForm1.FormCreate(Sender: TObject);
var
i : Integer;
Ch1,
Ch2 : Char;
LastName,
FirstName : String;
Age : Integer;
begin
CDS1.CreateDataSet;
CDS1.DisableControls;
try
for i := 1 to 30000 do begin
Ch1 := Chr(Ord('a') + random(26));
Ch2 := Chr(Ord('a') + random(26));
LastName:= StringOfChar(Ch1, 1 + Random(10));
FirstName := StringOfChar(Ch2, 1 + Random(10));
Age := Trunc(Random(71));
CDS1.InsertRecord([i, LastName, FirstName, Age]);
end;
finally
CDS1.First;
CDS1.EnableControls;
end;
end;
procedure TForm1.Log(const Title, Msg : String);
begin
Memo1.Lines.Add(Title + ' : ' + Msg);
end;
procedure TForm1.CDS1FilterRecord(DataSet: TDataSet; var Accept: Boolean);
begin
Accept := True;
if LastNameFilter <> '' then
Accept := MatchesMask(CDS1LastName.AsString, LastNameFilter);
if not Accept then exit;
if FirstNameFilter <> '' then
Accept := Accept and MatchesMask(CDS1FirstName.AsString, FirstNameFilter);
if not Accept then exit;
if AgeFilter <> '' then
Accept := Accept and MatchesMask(CDS1Age.AsString, AgeFilter);
end;
procedure TForm1.edLastNameFilterChange(Sender: TObject);
begin
UpdateFilter;
end;
procedure TForm1.UpdateFilter;
var
T1 : Integer;
begin
T1 := GetTickCount;
UpdateFilterExprsAndIndex;
CDS1.DisableControls;
try
CDS1.Filtered := False;
if (edLastNameFilter.Text <> '') or (edFirstNameFilter.Text <> '') or (edAgeFilter.Text <> '') then begin
CDS1.Filtered := True;
end;
if IndexFields <> '' then
CDS1.IndexDefs[0].Fields := IndexFields; // Warning: This IndexDef needs to exist
Log('Filter update time', IntToStr(GetTickCount - T1) + 'ms');
finally
CDS1.EnableControls;
end;
end;
function TForm1.FilterExpr(const Input : String) : String;
begin
Result := Input;
if Result <> '' then
Result := '*' + Result + '*';
end;
procedure TForm1.UpdateFilterExprsAndIndex;
begin
LastNameFilter := FilterExpr(edLastNameFilter.Text);
FirstNameFilter := FilterExpr(edFirstNameFilter.Text);
AgeFilter := FilterExpr(edAgeFilter.Text);
IndexFields := '';
if LastNameFilter <> '' then
IndexFields := 'LastName';
if FirstNameFilter <> '' then begin
if IndexFields <> '' then
IndexFields := IndexFields + ';';
IndexFields := IndexFields + 'FirstName';
end;
if AgeFilter <> '' then begin
if IndexFields <> '' then
IndexFields := IndexFields + ';';
IndexFields := IndexFields + 'Age';
end;
end;
I hope this at least gives you a basis for comparison with your own
code so that you can identify any bottlenecks.
Update Rather to my surprise, I found that with the compound filter expression I used for testing, it is much faster to set the CDS's Filter to the expression and leave it do to the filtering using OnFilterRecord, With 30000 records, UpdateFilter2 takes under 20 ms, compared with 200 ms for a similar expression set using the `UpdateFilter'.
procedure TForm1.btnSetFilterExprClick(Sender: TObject);
begin
edFilter.Text := 'LastName=''aaa'' and FirstName = ''zz'' and Age > 30 ';
UpdateFilter2;
end;
procedure TForm1.UpdateFilter2;
var
T1 : Integer;
begin
CDS1.OnFilterRecord := Nil;
T1 := GetTickCount;
CDS1.DisableControls;
try
CDS1.Filtered := False;
CDS1.Filter := edFilter.Text;
if CDS1.Filter <> '' then begin
CDS1.Filtered := True;
end;
Log('Filter update time', IntToStr(GetTickCount - T1) + 'ms');
finally
CDS1.EnableControls;
end;
end;
I don't think you could do this using the standard TClientDataset's implementations
of indexing and filtering.
Changes to the index or filter on a TCDS both invoke a traversal of its data records
and you have no control over that because in both cases, the TCDS functionality depends
on calls into the interfaces provided by Midas.Dll.
Setting up a new or changed index involves calling procedure TCustomClientDataSet.SortOnFields
which in turn calls Cursor.SortOnFields, where Cursor is of type IDSCursor - see DSIntf.Pas
Equally, changing the CDS filter involves calling TCustomClientDataSet.AddExprFilter, which in
turn calls FDSCursor.AddFilter, where FDSCursor is again of type IDSCursor.
So, you would need to re-implement both of these at the Midas level, on the other side
of the IDSCursor interfave to avoid the default behaviour.

Delphi Using Locate function in data source in memory table

I need to check given value is already exists in the memory table, so i have tried following method.
function TForm4.findValueExists(id: Integer): Boolean;
var
state: Boolean;
begin
state := DBGrid1.DataSource.DataSet.Locate('code', id, []);
end;
here is the Search button procedure
procedure TForm4.butSearchClick(Sender: TObject);
var
id: Integer;
Name: String;
Sell: Double;
Qty: Integer;
Amount: Double;
OldQty: Integer;
RecordExist: Boolean;
begin
if txtprocode.Text <> '' then
begin
FDQuery1.Params.ParamByName('ID').Value := txtprocode.Text;
if FDQuery1.Active then
FDQuery1.Close;
FDQuery1.Open();
try
FDQuery1.First;
if not FDQuery1.Eof then
begin
id := FDQuery1.FieldByName('Id').AsInteger;
Name := FDQuery1.FieldByName('name').AsString;
Sell := FDQuery1.FieldByName('selling').AsFloat;
Qty := 1;
Amount := Sell * Qty;
if Form4.findValueExists(id) then
begin
FDMemTable1.Edit;
OldQty := DBGrid1.Fields[3].Value;
FDMemTable1.FieldByName('Qty').AsInteger := (OldQty + 1);
FDMemTable1.FieldByName('amount').AsFloat := (Sell * (OldQty + 1));
FDMemTable1.Post;
end
else
begin
FDMemTable1.InsertRecord([id, Name, Sell, Qty, Amount]);
end;
end;
finally
end;
end;
end;
unfortunately this method always gives me result as 'false'. but physically i can found matched result for given id.
here is UI
Guys i`m new to Delphi language.
You're not returning a value from your FindValueExists function, so you have absolutely no way of knowing if it locates the record or not. If you'd turn on compiler hints and warnings, the compiler would have pointed that fact out to you. (It would have informed you function findValueExists might not return a value, and also that Value assigned to 'state' is never used.)
Change your findValueExists so that it actually returns the result of the Locate call.
function TForm4.findValueExists(id: Integer): Boolean;
begin
Result := DBGrid1.DataSource.DataSet.Locate('code', id, []);
end;

Forming a variable name with string + integer and calling functions

Firstly, I am new to these and the question could be stupid. Anyway, I have a procedure like this:
procedure Tform1.QueryChange(sqltext : String; query : Integer);
begin
if query = 1 then begin
ADOQuery1.Close;
ADOQuery1.SQL.Clear;
ADOQuery1.SQL.Add(sqltext);
ADOQuery1.Open;
end;
if query = 2 then begin
ADOQuery2.Close;
ADOQuery2.SQL.Clear;
ADOQuery2.SQL.Add(sqltext);
ADOQuery2.Open;
end;
I would like to remove the if blocks and make one united code:
ADOQuery+query.Close; (know that looks very silly)
ADOQuery+query.SQL.Clear;
ADOQuery+query.SQL.Add(sqltext);
ADOQuery+query.Open;
My goal is when query=1 code will use ADOQuery1.Close; etc.
when query=2 code will use ADOQuery2.Close;
You could create a local variable that referred to the TADOQuery object that you wish to operate on. Like this:
var
ADOQuery: TADOQuery;
begin
if query=1 then
ADOQuery := ADOQuery1
else if query=2 then
ADOQuery := ADOQuery2;
ADOQuery.Close;
ADOQuery.SQL.Clear;
ADOQuery.SQL.Add(sqltext);
ADOQuery.Open;
end;
Instead of creating variables ADOQuery1, ADOQuery2, ADOQuery3 etc of type TADOQuery, create an array:
ADOQueries: array of TADOQuery;
Then set the number of elements in it, when you know how many they will be:
SetLength(ADOQueries, NumberOfQueries);
Alternatively, if you know from the beginning how many elements there will be, you can define ADOQueries to be a static array instead:
ADOQueries: array[0..7] of TADOQuery;
Now you can do
procedure TForm1.QueryChange(sqltext: String; query: Integer);
begin
ADOQueries[Query].Close;
ADOQueries[Query].SQL.Clear;
ADOQueries[Query].SQL.Add(sqltext);
ADOQueries[Query].Open;
end;
Another way you can do this is with the FindComponent method, ie assuming the form owns the query components
procedure Tform1.QueryChange(sqltext : String; query : Integer);
var cmp: TComponent;
Query: TADOQuery;
begin
cmp := FindComponent('ADOQuery' + IntToStr(query));
if cmp <> nil then begin
Query := cmp as TADOQuery;
Query.Close;
...
end;
end;

Programmatically finding MS-Access 2007 Table 'Field index' to be present? TField.IsIndexedField not working

My table Customers has a field UserID which is indexed.
Now when I am dropping this Field from delphi, I am getting EOleExecption as its a indexed field.
I tried with following code:
ObjCustomers := TADOTable.Create(nil);
ObjCustomers.Connection := Connection;
ObjCustomers.TableName := 'Customers';
ObjCustomers.Open;
if (ObjCustomers.FindField('UserID').IsIndexField) then
begin
ExecuteSQLStatements(['DROP INDEX UserID ON Customers']);
end;
But this Tfield.IsIndexField is coming up False for this case.
Further I dont wanna do something like this:
try
ExecuteSQLStatements(['DROP INDEX UserID ON Customers']);
except
on E: exception do
end;
Is there any way so that I can check whether the field is Indexed, before executing SQL query?
Thankx in advance!
GetIsIndexField is not implemented by TADODataSet, and the result will be False.
Use TADOConnection.OpenSchema to retrieves table indexes:
var DataSet: TADODataSet;
DataSet := TADODataSet.Create(nil);
try
Connection.OpenSchema(siIndexes, VarArrayOf([Unassigned, Unassigned, Unassigned, Unassigned, 'Customers']), EmptyParam, DataSet);
while not DataSet.Eof do begin
ShowMessage(DataSet.FieldByName('INDEX_NAME').AsString);
DataSet.Next;
end;
finally
DataSet.Free;
end;
To make this answer complete:
As suggested by TLama you can use the TADODataSet method GetIndexNames.
ADO is internally using Command.ActiveConnection.OpenSchema(adSchemaIndexes...
function IsIndexField(DataSet: TADODataSet; FieldName: string): Boolean;
var
SL: TStringList;
begin
SL := TStringList.Create;
try
DataSet.GetIndexNames(SL);
Result := SL.IndexOf(FieldName) <> -1;
finally
SL.Free;
end;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
ObjCustomers: TADOTable;
begin
ObjCustomers := TADOTable.Create(nil);
ObjCustomers.Connection := Connection;
ObjCustomers.TableName := 'Customers';
if IsIndexField(TADODataSet(ObjCustomers), 'UserID') then
begin
Showmessage('Index');
Connection.Execute('DROP INDEX UserID ON Customers');
end
else
Showmessage('Not Index');
// ObjCustomers.Open;
ObjCustomers.Free;
end;
VAR
AdoTbl:TAdoDataset;
BEGIN
AdoTbl:=TAdoDataset.Create(Self); // use TAdoDataset
AdoTbl.Connection :=MyAdoConnection;
AdoTbl.CommandType:=cmdTable; //Importent !!
AdoTbl.CommandText:='Refx_Ceramics_Hist_PreHist'; //Tablename
AdoTbl.GetIndexNames(ListBox1.Items);
END;
This works for me on DelphiXE2

Resources