ADOQuery Get Result (Delphi) - delphi

I Tried to get result from ADOQuery in Delphi. I wrote this function for Get a Name from table according custom ID.
function GetNameByID(Id : Integer) : string;
var query : string;
Begin
ShowMessage(GetDBGridViewIndex().ToString);
query := 'SELECT Name FROM Table1 WHERE ID=' + IntToStr(Id);
With ADOQuery do
Begin
try
SQL.Clear;
SQL.Add(query);
Open;
First;
Result:= // Need Get Result;
finally
Close;
end;
End;
ShowMessage(result);
End;
But I don't know how can return Result from ADOQuery.

TADOQuery is a descendant of TDataset.
You can iterate through the result records with the First, Next, Prior, and Last methods and find out if you've reached the end with Eof.
Within each result record you can access the fields with:
Fields[Index].AsString
Fields[Index].AsInteger
...
or
FieldByName(FieldName).AsString
FieldByName(FieldName).AsInteger
...
In this case you can access to the result using:
Result := Fields[0].AsString;
Result := FieldByName('Name').AsString;
After you Open the query, the cursor is pointing the First record. You don't need to call the First method after Open.

Related

generate a number increases with a specific format ex 'PRT-00000'

I want to create auto numbering to my access database in delphi
example :
I have a database with part names , i want to create an auto id that counts number of these records and generates a name with number as this 'PRT-00000' and increase it with one each time i add a record and keeps this format of five digits , like this 'PRT-00001'
help me please and thanks a lot .
sorry for my poor english
Let's assume your Access table is named 'Parts' and has an AutoNumber
field named 'ID' and a Short Text field named 'PartNumber'. One way of generating the
PartNumber value would be to get Access to calculate it for you, but since you have asked about Delphi, I'm going to explain a way to do it in Delphi.
Please start a new, very simple project with just the following items on the main form:
A TAdoConnection configured to connect to your database;
A TAdoQuery configured to use the TAdoConnection with its SQL.Text property set
to 'select * from Parts'
A TDataSource and TDBGrid configured to display the contents of the TAdoQuery.
A TButton
Then, add the following code to the form's unit:
procedure TForm2.Button1Click(Sender: TObject);
begin
NewPart;
end;
procedure TForm2.NewPart;
const
sSelect = 'select * from Parts';
sPrefix = 'PRT-';
iDigits = 5;
var
PartNumber : String;
ID : Integer;
begin
qryParts.Insert;
try
// First, set the new record's PartNumber field to a temporary value
qryParts.FieldByName('PartNumber').AsString := 'xxxx';
// save the record so that we can then read the ID value Access has allocated to the record
qryParts.Post;
// read the ID value
ID := qryParts.FieldByName('ID').AsInteger;
// next, construct the desired value for the PartNumber field based on the ID
PartNumber := qryParts.FieldByName('ID').AsString;
// left-pad the PartNumber with zeroes
while Length(PartNumber) < iDigits do
PartNumber := '0' + PartNumber;
// pre-pend the PRT- prefix
PartNumber := sPrefix + PartNumber;
// put qryParts into its dsEdit state
qryParts.Edit;
qryParts.FieldByName('PartNumber').AsString := PartNumber;
finally
// post the record back to the Parts table
qryParts.Post;
end;
end;
procedure TForm2.FormCreate(Sender: TObject);
begin
qryParts.Open;
end;
Update I've managed to get the new Part's autonumber ID in advance of the new Part being posted to the db. To use this, add the NewAutoNumber function and modify the NewPart method as shown below.
function TForm2.NewAutoNumber(ATable, AColumn: String): Integer;
var
vCat : OleVariant;
vTable : OleVariant;
vColumn : OleVariant;
begin
vCat := CreateOleObject('ADOX.Catalog');
vCat.ActiveConnection := AdoConnection1.ConnectionString;
vTable := vCat.Tables[ATable];
vColumn := vTable.Columns[AColumn];
Result := vColumn.Properties['Seed'].Value;
end;
procedure TForm2.NewPart;
const
sSelect = 'select * from Parts';
sPrefix = 'PRT-';
iDigits = 5;
var
PrvSql : String;
PartNumber : String;
ID : Integer;
begin
ID := NewAutoNumber('Parts', 'ID');
try
qryParts.Insert;
qryParts.FieldByName('PartNumber').AsString := 'xxxx';
qryParts.Post;
if not qryParts.Locate('ID', ID, []) then begin
raise exception.CreateFmt('Failed to create new Parts record with ID = %d', [ID]);
end;
PartNumber := qryParts.FieldByName('ID').AsString;
while Length(PartNumber) < iDigits do
PartNumber := '0' + PartNumber;
PartNumber := sPrefix + PartNumber;
qryParts.Edit;
qryParts.FieldByName('PartNumber').AsString := PartNumber;
finally
qryParts.Post;
end;
end;
Update #2 As an alternative to getting the ID value for a newly-added Parts record using
either of the methods above, it can be obtained by using the 'select ##identity' method. The simplest
way to do this is to add another TAdoQuery, qryAutoNumber to the form and to add this function to
get the AutoNumber value:
function TForm2.NewAutoNumberFromIdentity : Integer;
begin
if qryAutoNumber.Active then
qryAutoNumber.Close;
qryAutoNumber.SQL.Text := 'select ##identity';
qryAutoNumber.Open;
Result := qryAutoNumber.Fields[0].AsInteger;
end;
Note that to obtain the correct ID value, this function should be called immediately after calling qryParts.Post. However, I have included this
only for completeness but as far as I can see, it is largely pointless
because once the new Parts record has been posted, the ID AutoNumber value
can be read directly from the ID field of qryParts.

Recursive procedure for getting child records in hierarchical dataset

I have a data table (MDTasks) that has records represented in a tree view. Each record has a unique field, 'ID', and a field 'Parent_ID', which refers to the ID of the parent. I am trying to make a list of all the children at any level of a record with a given ID. I have the following, which gets the first child at each level, but doesn't go back up to get the siblings at any level. I would be grateful for any help. Thank you.
procedure TfmList.GetChildren(TaskID: integer);
var
iChildID: integer;
begin
with MDTasks do
begin
first;
while not EOF do
begin
if FieldByName('Parent_ID').AsInteger = TaskID then
begin
iChildID := FieldByName('ID').AsInteger;
Memo1.Lines.Add(IntToStr(iChildID));
GetChildren(iChildID);
end;
next;
end;
end;
end;
The code below should do what you want, if I understand you correctly. It uses a ClientDataSet so that my answer is self contained and so that the test data can easily be set up.
It uses a call to CloneCursor to do the recursive search for the children of the specified parent node ID.
procedure TForm1.FormCreate(Sender: TObject);
begin
CDS1.CreateDataSet;
CDS1.InsertRecord([1, -1]);
CDS1.InsertRecord([2, -1]);
CDS1.InsertRecord([3, -1]);
CDS1.InsertRecord([4, 2]); // This and the following rows are all children of ID = 2
CDS1.InsertRecord([5, 2]);
CDS1.InsertRecord([6, 4]);
CDS1.InsertRecord([7, 4]);
CDS1.InsertRecord([8, 7]);
FindChildren(2);
end;
procedure TForm1.FindChildren(ParentID : Integer);
procedure FindChildrenInner(ParentID : Integer);
var
TempCDS : TClientDataSet;
ID : Integer;
begin
TempCDS := TClientDataSet.Create(Nil);
try
TempCDS.CloneCursor(CDS1, False, True);
TempCDS.First;
while not TempCDS.Eof do begin
if TempCDS.FieldByName('Parent_ID').AsInteger = ParentID then begin
ID := TempCDS.FieldByName('ID').AsInteger;
Memo1.Lines.Add(Format('ID: %d, Parent: %d', [ID, ParentID]));
FindChildrenInner(ID);
end;
TempCDS.Next;
end;
finally
TempCDS.Free;
end;
end;
begin
Memo1.Lines.BeginUpdate;
try
Memo1.Lines.Clear;
Assert(CDS1.Locate('ID', ParentID, []));
FindChildrenInner(ParentID);
finally
Memo1.Lines.EndUpdate;
end;
end;
Looking at the code, I would say that it gets all the childs at the last level (not only first one). The problem is that you "share" the dataset (MDTasks) between levels - so when the last level iterates to the end of the table and returns to the caller the while not EOF do evaluates to false (on the parent level now) and loop ends, thus only the first child is gotten on levels above the last one.
One solution would be to iterate each level fully, logging Parent_IDs into local array, then use that array to get the next level below it.
Another solution is probably to use bookmarks in the table MDTasks. Let your procedure set a bookmark at the beginning and return to this bookmark at the end so the position/cursor in MDTasks is not changed from GetChildren.
your problem is that you are calling recursive function against the same data. you can use filtering, for example:
procedure GoGoGo (Id: integer);
var
OldFilter: string;
begin
OldFilter := Table.Filter;
Table.Filter := 'ParentId = ' + IntToStr (Id);
try
Table.First;
while not Table.Eof do begin
Memo.Lines.Add (Format('ID: %d, Parent: %d',
[Table.FieldByName ('I'd).AsInteger,
Table.FieldByName ('ParentID').AsInteger]));
GoGoGo (Table.FieldByName ('Id').AsInteger);
if Table.Locate ('Id', Id, []) then
Table.Next;
end;
finally
Table.Filter := OldFilter;
end;
end;
<...>
Memo.Lines.Clear;
Table.Filtered := TRUE;
Table.First;
GoGoGo (Table.FieldByName ('Id').AsInteger);
haven't tested this code, it may contain errors, but hopefully, idea is quite clear. with the same idea you can use another approaches, like create new TQuery with dynamically generated where clause, etc. I'm not a fan of such things just because in a really big tree, creating new objects in every iteration of recursive function may result in a stack overflow.
gl there ;)

Fast Report displaying incorrect data from ADOQuery

I have a problem with Fast Report displaying incorrect data from an ADOquery. I use the following sql.text
SELECT * FROM JOB_DATA
INNER JOIN CUSTOMER ON JOB_DATA.CUST_CODE = CUSTOMER.CUST_CODE
WHERE JOB_DATA.SHIP_DATE Between [Date1] And [Date2]
ORDER by SHIP_DATE
Fast Report only shows the data where SHIP_DATE = null.
If I throw up a TDBgrid and attach it to a data source attached to the same ADOquery, then the dbgrid shows exactly the correct information.
I'm out of ideas, any suggestions?
To answer questions about where the dates come from:
var
date1:string;
date2:string;
sql_str:string;
begin
date1:=inputbox('Date Range','Enter Beginning Date','');
Try
StrToDate(date1);
Except
On EConvertError Do
Begin
MessageDlg('Please enter a valid date. Format xx/xx/xx',
mtError, [mbOK], 0);
//ShowMessage('Please enter a valid date. Format `enter code here`xx/xx/xx');
Exit;
End;
End;
date2:=inputbox('Date Range','Enter Ending Date','');
Try
StrToDate(date2);
Except
On EConvertError Do
Begin
MessageDlg('Please enter a valid date. Format xx/xx/xx',
mtError, [mbOK], 0);
//ShowMessage('Please enter a valid date. Format `enter code here`xx/xx/xx');
Exit;
End;
End;
sql_str:= 'SELECT * FROM JOB_DATA INNER JOIN CUSTOMER ON ' +
'JOB_DATA.CUST_CODE = CUSTOMER.CUST_CODE ' +
'WHERE JOB_DATA.SHIP_DATE Between ';
sql_str:= sql_str+ ''' ';
sql_st:=sql_str + date1;
sql_str:= sql_str+ '''';
sql_str:= sql_str+ ' AND ';
sql_str:= sql_str+ ''' ';
sql_str:= sql_str+ date2;
sql_str:= sql_str+ ' ''';
with ADOQuery5 do
begin
Close;
SQL.Clear;
SQL.text:= sql_str;
Open;
end;
frxreport2.ShowReport();
end;
The ADOquery is attached to frxDBDataset2 which is attached to frxReport2. I am doing nothing to alter the results in the query.
No, I have no code in the report, it was all generated from the wizard.
FastReport cannot display records only where SHIP_DATE is NULL, because your query shouldn't be returning them based on your WHERE clause if Date1 and Date2 are properly assigned. This means that either your dataset and the FastReport aren't connected properly or that something in your code assigning the date values for the BETWEEN clause is wrong, and the dates aren't being provided to the query correctly.
The first place to start looking is to make sure that all of the report columns are correctly assigned the proper TfrxDataSet and the proper database column. (Click on the report item (text object or whatever it might be), and check its DataSet and DataField properties to ensure they are correct.)
If that's not the problem, it may be the way you're building your query, which probably isn't correctly formatting the dates for ADO. (You're just using whatever format happens to pass the StrToDate calls without raising an exception.)
The way you're setting up your SQL is really unadviseable. It's unreadable and unmaintainable when you try to manage quoting yourself in code.
You should use parameters, which first and foremost protects you against SQL injection, but also allows the database driver to properly format quoted values and dates for you and keeps things readable. (You can also use readable names for the parameters, so that when you see them six months from now you'll know what they mean.)
var
// Your other variable declarations here
StartDate, EndDate: TDateTime;
begin
Date1 := InputBox(Whatever);
try
StartDate := StrToDate(Date1);
except
// Handle EConvertError
end;
Date2 := InputBox(Whatever);
try
EndDate := StrToDate(Date2);
except
// Handle EConvertError
end;
sql_str := 'SELECT * FROM JOB_DATA J'#13 +
'INNER JOIN CUSTOMER C'#13 +
'ON J.CUST_CODE = C.CUST_CODE'#13 +
'WHERE J.SHIP_DATE BETWEEN :StartDate AND :EndDate';
with ADOQuery5 do
begin
Close;
// No need to clear. If you're using the same query more than once,
// move the SQL assignment and the Parameter.DataType somewhere
// else, and don't set them here.
// The query can be reused just by closing, changing parameter values,
// and reopening.
SQL.Text := sql_str;
with Parameters.ParamByName('StartDate') do
begin
DataType := ftDate;
Value := StartDate;
end;
with Parameters.ParamByName('EndDate') do
begin
DataType := ftDate;
Value := EndDate;
end;
Open;
end;
frxReport2.ShowReport;
end;
When I start having problems with ADO, I log the information.
You'll need to create your own logger...but here's the jest of it...Note it will log the parameter values that are being passed to the query, including the SQL.
procedure TLogger.SetUpConnectionLogging(aParent: TComponent);
var
a_Index: integer;
begin
for a_Index := 0 to aParent.ComponentCount - 1 do
if aParent.Components[a_Index] is TAdoConnection then
begin
TAdoConnection(aParent.Components[a_Index]).OnWillExecute := WillExecute;
TAdoConnection(aParent.Components[a_Index]).OnExecuteComplete := ExecuteComplete;
end;
end;
procedure TLogger.ExecuteComplete(Connection: TADOConnection;
RecordsAffected: Integer; const Error: Error; var EventStatus: TEventStatus;
const Command: _Command; const Recordset: _Recordset);
var
a_Index: integer;
begin
AddLog('AdoConnection ExecuteComplete', True);
AddLog('Execution In MilliSeconds', IntToStr(MilliSecondsBetween(Time, FDif)));
AddLog('Execution In Seconds', IntToStr(SecondsBetween (Time, FDif)));
AddLog('Execution In Minutes', IntToStr(MinutesBetween (Time, FDif)));
AddLog('CommandText', Command.CommandText);
if Assigned(Command) then
begin
AddLog('Param Count', IntToStr(Command.Parameters.Count));
for a_Index := 0 to Command.Parameters.Count - 1 do
begin
AddLog(Command.Parameters.Item[a_Index].Name, VarToWideStr(Command.Parameters.Item[a_Index].Value));
end;
AddLog('CommandType', GetEnumName(TypeInfo(TCommandType),Integer(Command.CommandType)));
end;
AddLog('EventStatus', GetEnumName(TypeInfo(TEventStatus),Integer(EventStatus)));
if Assigned(RecordSet) then
begin
AddLog('CursorType', GetEnumName(TypeInfo(TCursorType),Integer(Recordset.CursorType)));
AddLog('LockType', GetEnumName(TypeInfo(TADOLockType),Integer(Recordset.LockType)));
end;
AddLog('RecordsAffected', IntToStr(RecordsAffected));
AddLog('AdoConnection ExecuteComplete', False);
end;
procedure TLogger.WillExecute(Connection: TADOConnection;
var CommandText: WideString; var CursorType: TCursorType;
var LockType: TADOLockType; var CommandType: TCommandType;
var ExecuteOptions: TExecuteOptions; var EventStatus: TEventStatus;
const Command: _Command; const Recordset: _Recordset);
begin
AddLog('Connection WillExecute', True);
AddLog('Connection Name', Connection.Name);
AddLog('CommandText', CommandText);
AddLog('CommandType', GetEnumName(TypeInfo(TCommandType),Integer(CommandType)));
AddLog('EventStatus', GetEnumName(TypeInfo(TEventStatus),Integer(EventStatus)));
AddLog('CursorType', GetEnumName(TypeInfo(TCursorType),Integer(CursorType)));
AddLog('Connection WillExecute', False);
FDif := Time;
end;

Data from ListBox to DataSet

I have DBGrid where I load names from DataSetPeople associated to table_people. I create a Drag and Drop procedure, where the user can drag a name and drop into a ListBox. The user can drop into a listbox_employees or listbox_manager.
MY PROBLEM
I want to create a relationship between people from table_people using the names that user drop in both listbox. I create table_relationship where employeeID match with managerID.
So, how can I get the name from each listbox, associate this name to an ID, and put this ID into a employeeID or managerID from DataSetRelationship associated to table_relationship ?
If your IDs are numeric (integer) values, you can use the TListBox.Items.Objects to store it when you fill it (in your drop event handler on the form):
var
PersonName: string;
PersonID: Integer;
begin
with YourDataModule do // Gives access to the tables in the data module
begin
PersonName := table_people.FieldByName('employeeName').AsString;
PersonID := table_people.FieldByName('employeeID').AsInteger);
end;
listbox_employees.Items.AddObject(PersonName, TObject(PersonID);
end;
For the manager, just change to the listbox for the manager and the field values for the manager.
To get the ID back out to use for an INSERT statement:
// Make sure both listboxes have the same number of items, of course.
var
EmployeeID: Integer;
ManagerID: Integer;
i: Integer;
begin
for i := 0 to ListBox_Employees.Items.Count - 1 do
begin
EmployeeID := Integer(ListBox_Employees.Items.Objects[i]);
ManagerID := Integer(ListBox_Manager.Items.Objects[i]);
if not YourDataModule.MakeRelationship(EmployeeID, ManagerID) then
ShowMessage('Unable to relate this employee and manager!');
end;
end;
// If you're using a SQL query, the `INSERT` should be created like this
// somewhere, like in your datamodule's OnCreate event
// qryRelationShips.SQL.Clear;
// qryRelationShips.SQL.Add('INSERT INTO table_relationship (employeeID, managerID)');
// qryRelationShips.SQL.Add('VALUES (:employeeID, :managerID)';
//
// Or you can type it into the SQL property in the Object Inspector at designtime
// INSERT INTO table_relationship (employeeID, managerID) VALUES (:employeeID, :managerID)
function TYourDataModule.MakeRelationship(const EmpID, MgrID: Integer): Boolean;
begin
Result := False;
// INSERT into your dataset:
qryRelationShips.ParamByName('employeeID').AsInteger := EmpID;
qryRelationShips.ParamByName('managerID').AsInteger := MgrID;
try
qryRelationShips.ExecSQL;
Result := qryRelationShips.RowsAffected;
finally
qryRelationShips.Close;
end;
end;
// If you're using a table instead of a query, getting a result is harder
function TYourDataModule.MakeRelationship(const EmpID, MgrID: Integer): Boolean;
begin
Result := True;
try
tblRelationShips.Insert;
tblRelationShips.FieldByName('employeeID').AsInteger := EmpID;
tblRelationShips.FieldByName('managerID').AsInteger := MgrID;
tblRelationShips.Post;
except
Result := False;
end;
end;
I hope you have made tables using SQLite database. So what you want to do is possible by using a query to insert the rows. Use db.execSQL();
You can keep a counter for incrementong the id values.

Help with Parameterized Query (using Delphi 7 and BDE)

Can someone help me and explain to my why this code crashes on the ExecSQL statement?
function UpdateLastBankResponsesId(ADatabase: TDatabase; AValue: Integer): String;
var
AQuery2: TQuery;
begin
result:= '';
AQuery2:= TQuery.Create(nil);
AQuery2.DatabaseName:= ADatabase.DatabaseName;
with AQuery2 do
begin
SQL.Text:= 'UPDATE last_id Set TABLENAME =:ATableName, LASTID=:ALastId';
ParamByName('ATableName').AsString:= 'responses';
ParamByName('ALastId').AsInteger:= AValue;
try
ExecSql; //***** CRASHES HERE *****
except
begin
ExitCode:= 16;
raise ECustomException.create('Error Updating Last Id table!');
end;//except
end; //try
end; //with
AQuery2.Free;
end;
I was trying to set a value that already existed
Set TABLENAME =:ATableName
I actually only needed to set the LASTID
UPDATE last_id SET LASTID=:ALastId WHERE TABLENAME =:ATableName
I can't believe I did that

Resources