Getting database field value not working - database

I am using the ado connection a adoquery and dsr and adotable
I have a database with bookingnumbers as a field in the table client.
I would like to get the last bookingnumber in the field and store it in a variable.
The booking number is saved as text on access.
So far I have:
Var
sNum : string;
....
sNum := Datamodule1.tblClient['BookingNumber'].Last;
but it is not working.
please help?

It is not a good idea to try and find the maximum value of a field by navigating the dataset, especially if the dataset isn't necessarily ordered by the field in question. Try something like this instead:
function TForm1.GetMaxBookingNumber : Integer;
var
Q : TAdoQuery;
begin
Q := TAdoQuery.Create(Nil);
Q.Connection := DataModule1.AdoConnection1; // or whatever the name of your connection is
try
Q.SQL.Text := 'SELECT MAX(BookingNumber) FROM CLIENT';
Q.Open;
// the `not IsNull` in the following allows for the table being empty
if not Q.Fields[0].IsNull then
Result := Q.Fields[0].AsInteger
else
Result := -1;
finally
Q.Free;
end;
end;

So here is what i wanted :
var
sNum : string;
procedure button click
begin
with Datamodule1 do
begin
qryClient.SQL.Add('SELECT BookingNumber FROM Client');
qryClient.Open;
qryClient.last;
sNum := qryClient['BookingNumber'];
end;
end;

Related

Aggregate of multiple UI controls bound to one database column

I currently have a TDBRadioGroup bound to a CHAR column on the database using the Values property to specify the value stored in the database column for each radio button. Now we have a requirement to add 2 new radio groups along side the existing one that would modify the value. In other words I basically need 3 controls on the form to act as one in a data-aware configuration for the purposes of determining the value stored. A simple example might be if each radio group has a one-character value, and in the database you concatenate them into a 3-character string. Our mapping is more complex than that but that's the general idea. What would be a good way to do this? If someone tells me live bindings would be the way to go, that would be good to know, as it would be help me to convince management to upgrade Delphi. We're stuck on XE.
Interesting q!
I hope I've understood you correctly. Suppose there is a form with 3 radiogroups, one per line, like so
*A B C
D *E F
G H *I
where the asterisks indicates the positions of the selected buttons and suppose
that these settings translate into a field value AEI in a row of a dataset.
If the G entry of the 3rd RG is clicked, the dataset field value should become
GEI. That's what I've assumed you are looking for.
The code below is a "proof of concept" which implements the above functionality.
It does so using 3 standard (non-db-aware) TRadioGroups and an adaptor class
TDBRadioGroupAdaptor which makes these operate as db-aware RadioGroups. The
TDBRadioGroupAdaptor uses a descendant, TRGFieldDataLink, of the standard TFieldDataLink (see DBCtrls.Pas)
to interface with the dataset. I've used a TClientDataSet so the project can be completely
self-contained and I've avoided the use of generices as you didn't specify a Delphi version.
Most of the db-aware functionality is contained in the TRGFieldDataLink, which
serves to interface the dataset and the RadioGroups and differs from a standard
TFieldDataLink in that it treats the field value as composed of N (3 in the code example but it supports an arbitrary number)
sub-fields which each have their own RadioGroup. As ever with implementing db-aware
functionality, the code for the TDBRadioGroupAdaptor and TRGFieldDataLink is fairly
long-winded, as most of it is necessary but not very interesting. About the only
ones that are interesting, in that they make the wheels go around, are
function TRGFieldDataLink.GetGroupString : String;
// Returns a string from the ItemIndexes of the RadioGroups
procedure TRGFieldDataLink.GetGroupValues;
// Sets the DataSet field from the RadioGroup ItemIndexes
which I hope are self-explanatory from the embedded comments.
Because it's only supposed to be a proof-of-concept, the implementation is incomplete,
lacking f.i. keyboard support, but that could easily be added by mimicking the
source code of the standard TDBRadioGroup.
Obviously, something similar could be used to treat subfields of a dataset field as
a group of strings independently editable in a group of TEdits.
Also obviously, it would be possible to turn the TDBRadioGroupAdaptor into a full-blown compound component which includes its own radiogroups, but I've left that as an exercise for the reader.
Code (warning: long!)
type
TDBRadioGroupAdaptor = class;
TRGFieldDataLink = class(TFieldDataLink)
private
FAdaptor: TDBRadioGroupAdaptor;
FRecordChanging : Boolean;
procedure GetGroupValues;
function GetGroupString: String;
procedure SetGroupValues(AValue: String);
public
constructor Create(AAdaptor : TDBRadioGroupAdaptor);
destructor Destroy; override;
procedure RecordChanged(Field : TField); override;
procedure UpdateData; override;
property Adaptor : TDBRadioGroupAdaptor read FAdaptor write FAdaptor;
end;
TDBRadioGroupAdaptor = class
private
FDataLink : TRGFieldDataLink;
FRadioGroups : TList;
procedure SetDataSource(Value : TDataSource);
function GetDataSource : TDataSource;
function GetRadioGroup(Index: Integer): TRadioGroup;
procedure ItemClicked(Sender : TObject);
procedure SetFieldName(const Value: string);
function GetFieldName : String;
public
constructor Create;
destructor Destroy; override;
procedure Add(ARadioGroup : TRadioGroup);
property DataSource : TDataSource read GetDataSource write SetDataSource;
property FieldName : string read GetFieldName write SetFieldName;
property RadioGroup[Index : Integer] : TRadioGroup read GetRadioGroup;
end;
type
TForm1 = class(TForm)
DBGrid1: TDBGrid;
ClientDataSet1: TClientDataSet;
DataSource1: TDataSource;
DBNavigator1: TDBNavigator;
Button1: TButton;
RadioGroup1: TRadioGroup;
RadioGroup2: TRadioGroup;
RadioGroup3: TRadioGroup;
DBEdit1: TDBEdit;
DBRadioGroup1: TDBRadioGroup;
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
private
protected
public
Adaptor : TDBRadioGroupAdaptor;
end;
[...]
{ TRGFieldDataLink }
constructor TRGFieldDataLink.Create(AAdaptor : TDBRadioGroupAdaptor);
begin
inherited Create;
Adaptor := AAdaptor;
end;
destructor TRGFieldDataLink.Destroy;
begin
inherited;
end;
procedure TRGFieldDataLink.SetGroupValues(AValue : String);
// Sets the ItemIndexes of the RadioGroups by matching each character of AValue
// to the contents of their Items
var
i,
Index : Integer;
S : String;
begin
if AValue = '' then Exit; // To avoid error when CreateDataSet is called
for i := 0 to Adaptor.FRadioGroups.Count - 1 do begin
S := AValue[i + 1];
Index := Adaptor.RadioGroup[i].Items.IndexOf(S);
Adaptor.RadioGroup[i].ItemIndex := Index;
end;
end;
procedure TRGFieldDataLink.RecordChanged(Field : TField);
// called when the dataset goes to a new record, e.g. during scrolling
// and sets the RadioGroups' ItemIndexes from the dataset data
var
FieldValue : String;
begin
Assert(DataSet <> Nil);
if FRecordChanging then exit; // just in case, avoid re-entrancy
try
FRecordChanging := True;
if Field = Nil then
Field := DataSet.FieldByName(FieldName); // Yukky way of setting Field
FieldValue := Field.AsString;
SetGroupValues(FieldValue);
finally
FRecordChanging := False;
end;
end;
function TRGFieldDataLink.GetGroupString : String;
// Returns a string from the ItemIndexes of the RadioGroups
var
i : Integer;
S : String;
begin
Result := '';
for i := 0 to Adaptor.FRadioGroups.Count - 1 do begin
S := Adaptor.RadioGroup[i].Items[Adaptor.RadioGroup[i].ItemIndex];
Result := Result + S [1];
end;
end;
procedure TRGFieldDataLink.GetGroupValues;
// Sets the DataSet field from the RadioGroup ItemIndexes
var
FieldValue,
S : String;
begin
Assert(DataSet <> Nil);
S := Field.AsString;
FieldValue := GetGroupString;
Field.AsString := FieldValue;
end;
procedure TRGFieldDataLink.UpdateData;
// Called by RTL to update the dataset record from the RadioGroups
begin
GetGroupValues;
end;
{ TDBRadioGroupAdaptor }
procedure TDBRadioGroupAdaptor.Add(ARadioGroup: TRadioGroup);
begin
FRadioGroups.Add(ARadioGroup);
ARadioGroup.OnClick := ItemClicked;
end;
constructor TDBRadioGroupAdaptor.Create;
begin
inherited;
FRadioGroups := TList.Create;
FDataLink := TRGFieldDataLink.Create(Self);
end;
destructor TDBRadioGroupAdaptor.Destroy;
begin
FDataLink.Free;
FRadioGroups.Free;
inherited Destroy;
end;
function TDBRadioGroupAdaptor.GetDataSource: TDataSource;
begin
Result := FDataLink.DataSource;
end;
procedure TDBRadioGroupAdaptor.SetDataSource(Value: TDataSource);
begin
FDataLink.DataSource := Value;
end;
function TDBRadioGroupAdaptor.GetRadioGroup(Index: Integer): TRadioGroup;
begin
Result := TRadioGroup(FRadioGroups[Index]);
end;
procedure TDBRadioGroupAdaptor.ItemClicked(Sender: TObject);
// Responds to one of the RadioGroups being clicked to put the DataSet into Edit state
// and updates the DataSet field from the ItemIndexes of the RadioGroups
var
S : String;
begin
if not FDataLink.FRecordChanging then begin
S := FDataLink.GetGroupString;
FDataLink.Edit;
FDataLink.SetGroupValues(S);
end;
end;
procedure TDBRadioGroupAdaptor.SetFieldName(const Value: string);
begin
FDataLink.FieldName := Value;
end;
function TDBRadioGroupAdaptor.GetFieldName: string;
begin
Result := FDataLink.FieldName;
end;
procedure TForm1.FormCreate(Sender: TObject);
var
Field : TField;
begin
// Create 2 fields in the CDS
Field := TIntegerField.Create(Self);
Field.FieldName := 'ID';
Field.FieldKind := fkData;
Field.DataSet := ClientDataSet1;
Field := TStringField.Create(Self);
Field.FieldName := 'Value';
Field.Size := 40;
Field.FieldKind := fkData;
Field.DataSet := ClientDataSet1;
RadioGroup1.Items.Add('A');
RadioGroup1.Items.Add('B');
RadioGroup1.Items.Add('C');
RadioGroup2.Items.Add('D');
RadioGroup2.Items.Add('E');
RadioGroup2.Items.Add('F');
RadioGroup3.Items.Add('G');
RadioGroup3.Items.Add('H');
RadioGroup3.Items.Add('I');
Adaptor := TDBRadioGroupAdaptor.Create;
Adaptor.Add(RadioGroup1);
Adaptor.Add(RadioGroup2);
Adaptor.Add(RadioGroup3);
Adaptor.DataSource := DataSource1;
Adaptor.FieldName := 'Value';
// Next, set up the CDS
ClientDataSet1.CreateDataSet;
ClientDataSet1.InsertRecord([1, 'AEI']);
ClientDataSet1.InsertRecord([2, 'BDG']);
ClientDataSet1.InsertRecord([3, 'ADG']);
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
Adaptor.Free;
end;

Add field to FDMemTable in Delphi

I am coding something with DBGrid and there is not enough data witch i cen get with FDQuery. I would like custom data beside "FDQuery" data. I found component that should be able to do this and it is called FDMemTable. I can get data from FDQuery to FDMemTable, but I cant add a new field where I can put different data. So my question is how to propper connect the data with FDQuery and add extra column in FDMemTable.
procedure TWorkflowDM.Temp;
var
Error: string;
Temp: string;
begin
try
FDQuery1.Open;
FDQuery1.FetchAll;
FDMemTable1.Data:= FDQuery1.Data;
FDMemTable1.FieldDefs.Add('Test', ftString, 20, False); <-ERROR (Error 'FDMemTable1: Field ''Test'' not found')
FDMemTable1.Open;
FDMemTable1.First;
while not FDMemTable1.Eof do
begin
Temp:= FDMemTable1.FieldByName('Test').AsString;
FDMemTable1.Next;
end;
except
on E: Exception do
Error:= E.Message;
end;
end;
We copy the field definitions from the source DataSet and append the additional fields. Then we call CreateDataset or optionally set Active to true. This creates all the necessary fields and opens the FDMemTable. Then we populate it by CopyDataset method. This code works:
procedure TWorkflowDM.Temp;
var
Error: string;
Temp: string;
begin
try
FDQuery1.Open;
// FDQuery1.FetchAll;
FDMemTable1.FieldDefs := FDQuery1.FieldDefs;
FDMemTable1.FieldDefs.Add('Test', ftString, 20{, False}); // default parameter
FDMemTable1.CreateDataSet;//or just Open that sets Active to true;
FDMemTable1.CopyDataSet(FDQuery1);
FDMemTable1.First;
while not FDMemTable1.Eof do
begin
Temp := FDMemTable1.FieldByName('Test').AsString;
FDMemTable1.Next;
end;
except
on E: Exception do
Error := E.Message;
end;
end;

Multiple parameterized Delphi SQL updates within a transaction

I am trying to update two different SQL tables in the same loop using parameterized queries in Delphi XE8. I also want to wrap the whole thing in a transaction, so that if anything in the loop fails, neither table gets updated.
I don't really know what I'm doing, would appreciate some help.
The code below is a simplified version of what I'm trying to achieve, and my best guess as to how to go about it. But I'm not really sure of it at all, particularly the use of two datasets connected to the 'SQL connection' component.
SQL_transaction.TransactionID :=1;
SQL_transaction.IsolationLevel:=xilREADCOMMITTED;
SQL_connection.BeginTransaction;
Try
{ Create connections }
SQL_dataset1 :=TSQLDataSet.Create(nil);
SQL_dataset1.SQLConnection:=SQL_connection;
SQL_dataset2 :=TSQLDataSet.Create(nil);
SQL_dataset2.SQLConnection:=SQL_connection;
{ Create queries }
SQL_dataset1.CommandType:=ctQuery;
SQL_dataset1.CommandText:={ some parameterized query updating table A }
SQL_dataset2.CommandType:=ctQuery;
SQL_dataset2.CommandText:={ some parameterized query updating table B }
{ Populate parameters and execute }
For I:=0 to whatever do
begin
SQL_dataset1.ParamByName('Table A Field 1').AsString:='Value';
SQL_dataset1.ExecSQL;
SQL_dataset2.ParamByName('Table B Field 1').AsString:='Value';
SQL_dataset2.ExecSQL;
end;
SQL_connection.Commit(SQL_transaction);
except
SQL_connection.Rollback(SQL_transaction);
end;
I am using Delphi XE8, and the database can be either SQL server or SQLite.
The logic of your transaction handling is correct (except the missing exception re-raise mentioned by #whosrdaddy). What is wrong are missing try..finally blocks for your dataset instances. Except that you should stop using TSQLConnection deprecated methods that are using the TTransactinDesc record (always check the compiler warnings when you're building your app.). And you can also switch to TSQLQuery component. Try something like this instead:
var
I: Integer;
Query1: TSQLQuery;
Query2: TSQLQuery;
Connection: TSQLConnection;
Transaction: TDBXTransaction;
begin
...
Query1 := TSQLQuery.Create(nil);
try
Query1.SQLConnection := Connection;
Query1.SQL.Text := '...';
Query2 := TSQLQuery.Create(nil);
try
Query2.SQLConnection := Connection;
Query2.SQL.Text := '...';
Transaction := Connection.BeginTransaction;
try
// fill params here and execute the commands
for I := 0 to 42 to
begin
Query1.ExecSQL;
Query2.ExecSQL;
end;
// commit if everything went right
Connection.CommitFreeAndNil(Transaction);
except
// rollback at failure, and re-raise the exception
Connection.RollbackFreeAndNil(Transaction);
raise;
end;
finally
Query2.Free;
end;
finally
Query1.Free;
end;
end;
I prefer try finally over try except
here's how to make it work in a try finally block
var
a_Error: boolean;
begin
a_Error := True;//set in error state...
SQL_dataset1 := nil;
SQL_dataset2 := nil;
SQL_transaction.TransactionID :=1;
SQL_transaction.IsolationLevel:=xilREADCOMMITTED;
SQL_connection.BeginTransaction;
Try
{ Create connections }
SQL_dataset1 :=TSQLDataSet.Create(nil);
SQL_dataset1.SQLConnection:=SQL_connection;
SQL_dataset2 :=TSQLDataSet.Create(nil);
SQL_dataset2.SQLConnection:=SQL_connection;
{ Create queries }
SQL_dataset1.CommandType:=ctQuery;
SQL_dataset1.CommandText:={ some parameterized query updating table A }
SQL_dataset2.CommandType:=ctQuery;
SQL_dataset2.CommandText:={ some parameterized query updating table B }
{ Populate parameters and execute }
For I:=0 to whatever do
begin
SQL_dataset1.ParamByName('Table A Field 1').AsString:='Value';
SQL_dataset1.ExecSQL;
SQL_dataset2.ParamByName('Table B Field 1').AsString:='Value';
SQL_dataset2.ExecSQL;
end;
a_Error := False;//if you don't get here you had a problem
finally
if a_Error then
SQL_connection.Rollback(SQL_transaction)
else
SQL_connection.Commit(SQL_transaction);
SQL_dataset1.Free;
SQL_dataset2.Free;
end;
end;
I added some code on how Try Finally works with init objects to nil
TMyObject = class(TObject)
Name: string;
end;
procedure TForm11.Button1Click(Sender: TObject);
var
a_MyObject1, a_MyObject2: TMyObject;
begin
a_MyObject1 := nil;
a_MyObject2 := nil;
try
a_MyObject1 := TMyObject.Create;
a_MyObject1.Name := 'Object1';
if Sender = Button1 then
raise exception.Create('Object 2 not created');
ShowMessage('We will not see this');
a_MyObject2 := TMyObject.Create;
a_MyObject2.Name := 'Object2';
finally
a_MyObject2.Free;
ShowMessage('We will see this even though we called a_MyObject2.free on a nil object');
a_MyObject1.Free;
end;
end;

How to change DBLookupComboBox value in Delphi?

I'm trying to change the visible text (value) of a DBLookupComboBox, using :
DBLookupComboBox1.KeyValue:=S;
and
DBLookupComboBox2.KeyValue:=X;
If the KeyValue and S are strings, then everything works fine, and I can set the value.
But when the KeyValue is an integer/Int64 (UID in the DB), and X is an Int64 variable - it doesn't work, and nothing changes.
So for an example, i need to set "Amsterdam" in DBLookupComboBox1, and 1500 in DBLookupComboBox2.
"Amsterdam" from the "City" field of the users, and 1500 as the UID.
What am I doing wrong please?
Thanks
This code is Important for you:
lkcbbArzSource.KeyValue:=2;//c(or another number);
lkcbbArzSource is a dbLookupComboBox object and you can insert only a number in this!why? because is numeric and another side of that there is a field that show a text or String of that numeric in combo.
in below sample code you can see a dbLookupComboBox how to menage and fill with Data-set and add in a grid :
rzSource.Enabled:= True;
lkcbbArzSource.Font.Size:=8;
lkcbbArzSource.Tag := V_Counter;
lkcbbArzSource.Visible:= True;
lkcbbArzSource.Enabled:= True;
lkcbbArzSource.Font.Name:='tahoma';
lkcbbArzSource.ListSource := myDsrSource;
lkcbbArzSource.KeyField := 'Code';
lkcbbArzSource.ListField := 'Des';
lkcbbArzSource.KeyValue:= IntToStr(FieldByName('P_ARZSOURCE').AsInteger);
lkcbbArzSource.OnChange := myBicLookUpChange;
SGrid2.Objects[look_col,lkcbbArzSource.Tag]:= lkcbbArzSource;
SGRID2.Objects[look_col,V_Counter] := nil;
Setting the KeyValue calls the SetKeyValue of the TDBLookupControl which in Delphi 7 appears as:
procedure TDBLookupControl.SetKeyValue(const Value: Variant);
begin
if not VarEquals(FKeyValue, Value) then
begin
FKeyValue := Value;
KeyValueChanged;
end;
end;
procedure TDBLookupComboBox.KeyValueChanged;
begin
if FLookupMode then
begin
FText := FDataField.DisplayText;
FAlignment := FDataField.Alignment;
end else
if ListActive and LocateKey then
begin
FText := FListField.DisplayText;
FAlignment := FListField.Alignment;
end else
begin
FText := '';
FAlignment := taLeftJustify;
end;
Invalidate;
end;
As you can see, your variable x is used as part of a LocateKey.
function TDBLookupControl.LocateKey: Boolean;
var
KeySave: Variant;
begin
Result := False;
try
KeySave := FKeyValue;
if not VarIsNull(FKeyValue) and FListLink.DataSet.Active and
FListLink.DataSet.Locate(FKeyFieldName, FKeyValue, []) then // << ---here
begin
Result := True;
FKeyValue := KeySave;
end;
except
end;
end;
Stepping into these procedures and functions should help you to debug your issue. All are located in the DbCtrls unit..
I have used this and solved my problem.
Use this lines of code at Form1.OnShow :
DBLookupComboBox1.ListSource.DataSet.Locate('City', 'Amsterdam', []);
DBLookupComboBox1.ListSource.DataSet.FieldByName(DBLookupComboBox1.KeyField).Value;
DBLookupComboBox2.ListSource.DataSet.Locate('UID', '1500', []);
DBLookupComboBox2.ListSource.DataSet.FieldByName(DBLookupComboBox2.KeyField).Value;

Login display error message

I need my program to log a user in from a database. This entails a diver number (like a username) and a password which is already in the database. Unfortunately, I don't know SQL right now and would rather use a technique similar to the one I've done here. I get an error message in run time that says: adotblDiversInfo: Cannot perform this operation on a closed dataset. Thank you so much for your help in advance (:
This is my code:
procedure TfrmHomeScreen.btnLogInClick(Sender: TObject);
var
iDiverNumber : Integer;
sPassword, sKnownPassword : String;
bFlagDiverNumber, bFlagPassword, Result : Boolean;
begin
iDiverNumber := StrToInt(ledDiverNumber.Text);
sPassword := ledPassword.Text;
with frmDM do
adotblDiversInfo.Filtered := False;
frmDM.adotblDiversInfo.Filter := 'Diver Number' + IntToStr(iDiverNumber);
frmDM.adotblDiversInfo.Filtered := True;
if frmDM.adotblDiversInfo.RecordCount = 0 then
ShowMessage(IntToStr(iDiverNumber) + ' cannot be found')
else
begin
sKnownPassword := frmDM.adotblDiversInfo['Password'];
if sKnownPassword = sPassword then
ShowMessage('Login successful')
else
ShowMessage('Incorrect password. Please try again');
end;
end;
The error you're getting is because you've forgotten to open the dataset before attempting to access it. Use frmDM.adoTblDiversInfo.Open; or frmDM.adoTblDiversInfo.Active := True; to do so before trying to use the table.
Your code could be much simpler (and faster) if you change it somewhat. Instead of filtering the entire dataset, simply see if you can Locate the proper record.
procedure TfrmHomeScreen.btnLogInClick(Sender: TObject);
var
iDiverNumber : Integer;
begin
if not frmDM.adoTblDiversInfo.Active then
frmDM.adoTblDiversInfo.Open;
iDiverNumber := StrToInt(ledDiverNumber.Text);
sPassword := ledPassword.Text;
if frmDM.adoTblDiversInfo.Locate('Diver Number', iDiverNumber, []) the
begin
if frmDM.adoTblDiversInfo['Password'] = ledPassword.Text then
ShowMessage('Login successful')
else
ShowMessage('Invalid password. Please try again.');
end
else
ShowMessage(ledDiverNumber.Text);
end;

Resources