How can i append record to TAdoQuery without clearing its fields? - delphi

I am using TAdoQuery with BatchOptimistic lock type. If the select command has some fields that are calculated on database server the returned fields has property ReadOnly = true, so i must change them to false so i can modify them in my query.
I am actually never attempting to post to database, but i must use TAdoQuery.
Its all good to the point I append or insert some record, set its fields, and then call for example TAdoQuery.Last, Next or First.. The appended records fields change to null. Please, is there a way that these records could stay as they were?
I am attaching a simple code here, where the problem is presented:
// .. lockType = Batchoptimistic so TAdoQuery.first or TAdoQuery.last do NOT post do database
ADOQuery1.LockType := ltBatchOptimistic;
ADOQuery1.SQL.Text := 'SELECT 10 AS id, 20 AS sid ';
ADOQuery1.Open;
// .. readOnly = false so i can modify these two fields in appended record
ADOQuery1.FieldByName('id').ReadOnly := false;
ADOQuery1.FieldByName('sid').ReadOnly := false;
ADOQuery1.Append;
ADOQuery1.FieldByName('id').AsInteger := 5;
ADOQuery1.FieldByName('sId').AsInteger := 5;
// if use last, first etc. the appended record fields will change to 0 (null)
ADOQuery1.Last;

Related

Can't query a string from an SQL table

I am trying to pass the value of a group from the database into a TEdit when an item is selected in a TComboBox. However, the value returned should be a string, not an integer. In the text field for the group, the value returned is 0. Can anyone help me with this?
This is the code for the text field that should return the group data based on the item selected in a TComboBox:
ADOQuery1.SQL.Clear;
rf := ADOQuery1.SQL.Add('SELECT grouppp FROM f3_sheet WHERE holder = "' +cb1.Text +'"');
gpp.Text := rf;
The TADOQuery.SQL property is a TStrings object. Its Add() method returns the index of the string you just added to the list. That is why the return value is an integer 0 in your example.
But that is not what you want in this situation. After you fill in the SQL statement as needed, you need to then actually execute that SQL on the database by calling the TADOQuery.Open() method, and then you can read the retrieved field value from the TADOQuery.Fields collection, eg:
ADOQuery1.SQL.Text := 'SELECT grouppp FROM f3_sheet WHERE holder = ' + AnsiQuotedStr(cb1.Text, '"');
ADOQuery1.Open;
try
if not ADOQuery1.Eof then
gpp.Text := ADOQuery1.Fields[0].AsString
else
gpp.Text := '';
finally
ADOQuery1.Close;
end;
That being said, notice how I changed your SQL to use AnsiQuotedStr() instead of wrapping cb1.Text with quotation marks manually. Your original code suffers from a potential SQL Injection Attack, if the user is allowed to enter arbitrary text into the TComboBox.
For example, if the user were to enter something like "; DELETE FROM f3_sheet; -- into the TComboBox, your original code would end up executing this SQL:
SELECT grouppp FROM f3_sheet WHERE holder = ""; DELETE FROM f3_sheet; --"
And the contents of your database table would go bye-bye!
Making the TComboBox read-only is one way to mitigate that attack, so that only your code is allowed to specify valid strings that won't corrupt the SQL.
Using AnsiQuotedStr() is another way, by escaping embedded quotation marks in the user's text, eg:
SELECT grouppp FROM f3_sheet WHERE holder = """; DELETE FROM f3_sheet; --"
Now the SQL will search the holder field for the literal string "; DELETE FROM f3_sheet; -- and not find any result.
However, the best way to avoid such an attack is to simply not create SQL statements by hand in the first place, use Parameterized Queries or Stored Procedures instead. For example, the above example can be re-written to use Parameters like this:
// make sure to set ADOQuery1.ParamCheeck=true beforehand...
ADOQuery1.SQL.Text := 'SELECT grouppp FROM f3_sheet WHERE holder = :PHolder';
ADOQuery1.Parameters.ParamByName('PHolder').Value := cb1.Text;
ADOQuery1.Open;
try
if not ADOQuery1.Eof then
gpp.Text := ADOQuery1.Fields[0].AsString
else
gpp.Text := '';
finally
ADOQuery1.Close;
end;
Let the database handle any quoting and escaping requirements for you.

TADOQuery returns empty recordset in second execution

I have quite an incredible situation using a TADOQuery against an MS Access database.
In the following code (just a test case), the first execution of the query returns the correct record, the second execution instead returns an "empty" record (i.e. the codFormula variable first time is 'E0275', second time is '').
Obviously the three parameters value are the same
QryDosaggioTestata.Parameters[0].Value := idBatchRottura;
QryDosaggioTestata.Parameters[1].Value := nrMiscelataRottura;
QryDosaggioTestata.Parameters[2].Value := dataBatchRottura;
QryDosaggioTestata.Open;
// Here, QryDosaggioTestata's RecordCount is 1 and Eof is False
codFormula := trim(QryDosaggioTestataCodiceFormula.Value);
//now codFormula = 'E0275'
QryDosaggioTestata.Close;
QryDosaggioTestata.Parameters[0].Value := idBatchRottura;
QryDosaggioTestata.Parameters[1].Value := nrMiscelataRottura;
QryDosaggioTestata.Parameters[2].Value := dataBatchRottura;
QryDosaggioTestata.Open;
// Here, QryDosaggioTestata's RecordCount is 0 and Eof is True
codFormula := trim(QryDosaggioTestataCodiceFormula.Value);
// now codFormula = ''
Ora := QryDosaggioTestataOra.Value;
QryDosaggioTestata.Close;
The query text is in the designer object:
Select * from LOG_FINE_DOSAGGIO
WHERE
idBatch = :parIdBatch
AND nrMiscelata = :parNrMiscelata
AND Data = :parData
Obviously the query is syntactically correct, otherwise it would not execute well the first time.
Thanks a lot.
After many attempts I got the clue: the Microsoft JET OLEDB 4.0 provider deals horribly with date parameters: the only way to make it work is, for date parameters, to set the parameter datatype to ftString and to pass the value as DateToStr(yourDate).
My impression is that after first query.Close, the query parameters are re-prepared in the wrong way by the provider.
Hope this helps anyone.
Everything works well with other parameters type (i.e. integer, string...) and with SQL Server provider.

How can I use a DBLookupComboBox to select a foreign key value without immediately changing the target table? (Delphi 5 Pro)

Basic Scenario:
User clicks 'edit item'
Edit dialog opens
A combobox 'item type' should be populated with items form a table 'item_type'. The combobox should display 'item_type.name', but also know about 'item_type.id'
User edits other item stuff and selects an item type, then clicks ok
My program does some input validation
if everything is ok, get the 'item_type.id' from the selected combo item and save it to the foreign key column of the item table ('item.fk_item_type').
If I understand this component correctly, I should set DataSource to point to the destination table 'item' and DataField to 'item.fk_item_type'. But that would immediately edit my item table before I get a chance the validate all the other input.
It feels like I am missing something here. Somewhere I read that I need to use a classic ComboBox and fill it manually. But then I don't understand how to get to the id of the selected item.
Thank you for any pointers.
Edit:
I am starting to suspect that maybe I am missing a fundamental thing. All these DB* components, do they load values from that database automatically, but I have to call Post() myself? Meaning they do not automatically change values in the database?
If I understand you correctly, you want to use a DBLookupComboBox. You have to supply values for the following properties
datasource - linked to the table which you are editing, presumably 'items'
datafield - the name of the field in the table which you are editing, presumably 'item_type'
listsource - linked to the table which populated the combobox, presumably 'item_types'
list field - the name of the field from 'item_types' which you want to display, presumably 'name'
key field - the name of the field from 'item_types' which will be inserted into the items record, presumably 'item_type'
The table which populated the combobox is never edited.
You can validate the values before posting the new/edited 'items' record.
I can show you how to use a non-data aware combobox if necessary, but it's easier to use the data aware version.
Regarding validation, I use the following code template in edit dialogs.
Function TEditQuestion.Execute (n: longint): boolean;
var
gen: longint;
begin
sdsEditQuestion.params[0].asinteger:= n; // TSQLDataSet
with qEditQuestion do // TClientDataSet
begin
open;
if n = -1 then // new record
begin
caption:= 'New record';
insert;
fieldbyname ('alive').asinteger:= 1;
// initialise necessary fields in the dataset
end
else caption:= 'Editing record ' + inttostr (n);
edit;
if showmodal = mrOK then
begin
// validation code comes here. Set 'result' to true if everything is ok
if result then
begin
if n = -1 then
begin
with qGenID do
begin
open;
gen:= fields[0].asinteger; // get new value from generator
close
end;
FieldByName ('id').asinteger:= gen;
end;
post;
applyupdates (0)
end
else cancel // showmodal = OK, result = false
else // showmodal cancelled
begin
cancel;
result:= false
end;
end // with qEditQuestion
end;

how to show Only relevant information in dbgrid delphi

information:
I have an order form.
With "keuze" and "aantal" it wright a new line. The Orderline gets an OrderID.
But the user may only see the orderline from his OrderID.
How can i make it work that it only shows, for example the OrderID "47" ?
procedure TfmOrder.btInvoerenClick(Sender: TObject);
begin
dm.atOrder.open;
dm.atOrder.Append;
dm.atOrder ['OrderStatus'] := ('Aangemeld');
dm.atOrder ['klantID'] := fminloggen.userid;
dm.atOrder ['OrderDatum'] := Kalender.date;
dm.atOrder ['Opmerkingen'] := leOpmerkingen.text;
dm.atOrder.post;
cbkeuze.Visible := true;
dbRegel.Visible := true;
leAantal.visible := true;
btOpslaan.Visible:= true;
end;
This is the code for making a new Order
procedure TfmOrder.btOpslaanClick(Sender: TObject);
var orderid:string;
begin
dm.atOrderregel.Open;
dm.atDier.open;
dm.atorderregel.Append;
dm.atOrderregel ['AantalDieren'] := leAantal.text;
dm.atOrderregel ['OrderID'] := dm.atOrder ['OrderID'];
dm.atOrderregel ['Diernaam'] := cbKeuze.Text;
dm.atOrderregel.Post;
leaantal.clear;
cbkeuze.ClearSelection;
end;
And this for a new orderline
thanks in advance
I know got a different error using this code:
begin
dm.atorder.Open;
dm.atorder.filter := 'KlantID = ' + (fminloggen.userid);
dm.atorder.filtered := true;
while not dm.atorder.Eof do
begin
cbOrder.Items.Add (dm.atorder['OrderID']);
dm.atOrder.Next;
end;
dm.atOrder.Close;
end;
It gives an error: The arguments are from the wrong type, or doesn't have right reach or are in conflict with each other.
here is userid declared.
var Gevonden: boolean;
userid : string;
begin
dm.atInlog.open;
Gevonden := false;
while (not Gevonden) and (not dm.atInlog.eof) do
begin
if dm.atInlog['email'] = leUser.Text
then
begin
Gevonden := true ;
inlognaam := dm.atInlog['email'];
userid := dm.atInlog['KlantID'];
end
else
dm.atInlog.Next
end;
this is obviously in another form
You can use the Filter property of the data set:
atOrderregel.Filter := 'OrderID = 47';
atOrderregel.Filtered := True;
You can add the grid's columns property statically in the object inspector, showing only the fields you need. If the columns list is empty (default) it is filled with all available fields.
Just add as many columns as you need and link each column to the corresponding field. You can reorder the columns and set the widths and titles individually. There are still some more properties available which are worth to explore.
Im assuming your grid is bound to a datasource component. This datasource is then linked with a TDataset descendant. There are a couple of ways you could acheive the desired filtering of the dataset to display only orderid 47.
Firstly, you could set the Datasets SQL property to contain a (server side) SQL query such as:
SELECT * from table WHERE OrderID = #OrderID
You would also need to create a parameter in the dataset to pass the (changing) value for the required OrderID. So add a new Parameter to the dataset (#OrderID), and then at runtime you can set this parameter value in code, something like:
DataSet.Parameters['#OrderID'].Value := ParameterValue;
Alternatively, you could also FILTER the dataset (client side) to just show the correct data:
Set your SQL property of the dataset to retrive the entire table, something like:
SELECT * FROM table
And then at runtime you could set the Filter property of the dataset to only get OrderID 47:
Dataset.Filter := 'OrderID = '+InttoStr(ParameterValue);
Depending on your needs one method may suit better (performance/memory) wise.
As Najem has commented, there is also a third method - using a Master-Detail dataset relationship. This method works using two datasets, one is the master of the other. When the master table record is changed, the detail dataset is then filtered using the value defined in the Key or MasterFields property of the M-D relatioship.
If you are connected to some datasource you could always create a SQL Query. Something like:
SELECT * FROM YourDBTable WHERE OrderID=47

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.

Resources