I have a TADODataset executed with (only for example):
SELECT id, name, lastname, name + ' ' + lastname as fullname
FROM persons
ORDER BY lastname
After I open the dataset, I can modify "name" and "lastname" fields, but can't modify"fullname", because it's calculated.
I try to open TADODataset to TClientDataset via DataProvider, but it takes too long (there are about 100K records in source dataset):
SrcDS.FieldDefs.Update;
for i := 0 to Pred(SrcDS.FieldDefs.Count) do
SrcDS.FieldDefs[i].CreateField(SrcDS).ReadOnly := false;
DestDS := TClientDataset.Create(nil);
DestDS.SetProvider(SrcDS);
DestDS.Open;
DestDS.SetProvider(nil);
All in all, i want to have an independent dataset with changeable fields.
How can i modify calculated fields in the dataset?
You have to calculated the field in Delphi. Create a new field by rightclicking on the TADODataset component, select New Field, give it a name and set it's type to 'calculated'.
In the OnCalculateFields-Event simply write:
Procedure TMyDataModule.MyDatasetCalculate(Sender : TDataset);
Begin
MyDataSetFullName.AsString := MyDatasetFirstName.AsString+' '+MyDataSetLastName.AsString;
End;
Update: Regarding your second problem (100.000 records): If you load them into your ADODataset using LockType = ltBatchOptimistic, it will be fast enough and nothing is saved to the database, unless you call the UpdateBatch Method.
If this is still too slow, try using the async load feature (See the ExecuteOptions)
Related
I have a TFDMemTable filled with thousands of records. Is there a way to limit result records for only the first 50 ?
I've tried to use:
FDMemTable.FetchOptions.RecsSkip := 0;
FDMemTable.FetchOptions.RecsMax := 50;
FDMemTable.Open;
But it did not work, data remained unchanged.
I expect #Victoria will be able to show you a better and more general
way, but there are at least two ways to do this:
Use FD's FDLocalSQL feature to copy the first X rows of the FDMemTable into, say,
an FDQuery and then copy them back into your FDMemTable.
Apply a filter to the FDMemTable to filter out the other records, use an FDBatchMove
to copy the X records into a second FDMemTable and then copy them back into the original
FDMemTable.
To implement the first of these, add the following components to your form/datamodule:
FDLocalSQL1: TFDLocalSQL;
FDConnection1: TFDConnection;
FDQuery1: TFDQuery;
FDPhysSQLiteDriverLink1: TFDPhysSQLiteDriverLink;
and then execute code like this:
procedure TForm3.CopyData1;
begin
FDConnection1.DriverName := 'SQLite';
FDConnection1.Connected := True;
FDLocalSQL1.Connection := FDConnection1;
FDLocalSQL1.DataSets.Add(FDMemTable1); // this is the source dataset
FDLocalSQL1.Active := True;
FDQuery1.SQL.Text := 'select * from FDMemTable1 order by ID limit 5'; // ID being an Integer field of the FDMemTable
FDQuery1.Active := True;
FDMemTable1.Close;
FDMemTable1.Data := FDQuery1.Data; // Re-opens FDMemTable 1, which now contains only the first X records
end;
FD's LocalSQL uses Sqlite to do its stuff. The functional equivalent in Sqlite's SQL
to "Select Top X ..." is its limit clause.
An advantage of using LocalSQL for your task, of course, is that because LocalSQL
supports order by, you can it to determine which (top) X records are retained.
The batchmove method requires a bit less code but requires you to have a way of identifying
the first X records using a filter expression. An example using an ID field might be
procedure TForm3.CopyData2;
begin
FDMemTable1.Filter := 'ID <=50';
FDMemTable1.Filtered := True;
FDBatchMove1.Execute; // move data from FDMemTable1 to FDMemTable2;
FDMemTable1.Close;
FDMemTable1.Data := FDMemTable2.Data; // Re-opens FDMemTable 1, which now contains only the first X records
end;
Btw, you say
I have a TFDMemTable filled with thousands of records. I
I think the problem with the method you've tried is probably that by the time you have the records in the FDMemTable, it's too late to try and limit the number of them in the way you're attempting. *)
I'm using a TDataSet where the CommandText property is set to an SQL query. I have also made the following function which creates part of an SQL query based on the fields of TDataSet. It is however incomplete. As you can see I still need to get the name of the table that a TField is from. How do I achieve this?
function GetDataSetFieldsMSSQL(Dataset: TDataSet): String;
var
I, L: Integer;
TableName: String;
begin
Result := '';
L := Dataset.Fields.Count;
if (L > 0) then
begin
TableName := ... // Name of the table for the Dataset.Fields[0] field.
Result := '[' + TableName + '].[' + Dataset.Fields[0].FieldName + ']';
I := 1;
while (I < L) do
begin
TableName := ... // Name of the table for the Dataset.Fields[I] field.
Result := Result + ',[' + TableName + '].[' + Dataset.Fields[I].FieldName + ']';
Inc(I);
end;
end;
end;
You can use the Delphi Function GetTableNameFromQuery(SQL : String):String; from the DBCommon unit. Just Add The DBCommon on the uses. =)
Maybe there is no solution at all for a simple TDataSet?
I believe not. Because an TDataset can source its' data not only from RDBMS' tables.
It can be:
an RSS feed
An XML file. Example: TCliendataset is an TDataset descendant that can read XML from its'
own format or using an XMLTransformProvider.
It can be an SQL for reading an Excel spreadsheet or a text file if you have an ODBC driver for
that and configured the datasource.
Sky (and the imagination of Delphi's programmers around the world) is the limit for what a field can represent in an TDataset.
You have some alternatives, since you are using an ADODataset:
Parsing the commandText of ADOCommand
Using the BASETABLENAME property of ADORecordSet (as in kobik's comment)
Guessing by convention ( Abelisto's answer )
As I know there is no any way to get the name of the table from the SQL query component.
However you can give aliases for fields, for example: "select foo_field as foo_dot_foo_field from foo" and then replace them to the correct syntax: "Result := '[' + StringReplace(DataSet.Fields[0].FieldName, 'dot', '].[', [rfReplaceAll]) + ']'"
What you are trying to do is impossible if you have no knowledge or control over the SQL used in the query.
The query could contain calculated/computed fields or could be returning fields from a view etc. Furthermore the database might have several tables that contain the same field names.
If possible you can query the SQL server view INFORMATION_SCHEMA.COLUMNS and that way try to figure out what table a fieldname is from. However if the field names are not unique this might also prove impossible.
I want the result set of a database query to have a certain order. The information I want to order by is not contained in the database, but dynamically generated in code (so I cannot use ORDER BY).
Is there a way to sort a dataset after executing the database query? (I don't need indexed access but only want to iterate over all records.)
With a ClientDataset you are able to change the order after executing.
Settings IndexFieldNames sorts the dataset.
You can find information here how to connect a clientdataset to another dataset in the same application.
object DataSetProvider1: TDataSetProvider
DataSet = MyAdsQuery
Left = 208
Top = 88
end
object ClientDataSet1: TClientDataSet
Aggregates = <>
Params = <>
ProviderName = 'DataSetProvider1'
Left = 296
Top = 88
end
There is a possibility that shares similarities with Jens' answer (+1) but gets to the result in a slightly different fashion.
Given an existing table:
create table somedata (id integer, name char(20));
insert into somedata values ( 1, 'Tim' );
insert into somedata values ( 2, 'Bob' );
insert into somedata values ( 3, 'Joe' );
If you know the desired short order (either by processing the table or some query result from it), create a temp table that has some key value to match the desired rows from the original table and then the sort order data:
create table #sortorder( id integer, sortvalue integer );
Set the sortvalue field in the temp table to contain the desired order (it could be any sortable data type - doesn't have to be integer):
insert into #sortorder values ( 1, 15 );
insert into #sortorder values ( 2, 12 );
insert into #sortorder values ( 3, 5 );
Then generate the results with a join against the table that provides the sort order:
select sd.* from somedata sd, #sortorder so
where sd.id = so.id
order by so.sortvalue;
AFAIK the only reliable way to sort a dataset is to use ORDER BY.
I would:
Add a dummy order_tag field to your query.
Dump the results to temporary table.
Declare a cursor to iterate over the temporary table and set the order_tag using your custom logic and UPDATE #temp_table statements.
Select the data from the temporary table and order by the tag field.
The main trick here would be to use an Internal calc field (FieldKind = fkInternalCalc) if they are supported by your TDataset sub-class. If they aren't, use a TClientDataset as an intermediate.
DFM:
object ClientDataSet1SortField: TIntegerField
FieldKind = fkInternalCalc
FieldName = 'SortField'
end
pas:
procedure TForm1.FormCreate(Sender: TObject);
begin
ADOConnection1.Open('dbuser', 'Hunter2');
ClientDataSet1.SetProvider(ADOQuery1); // set ClientDataset provider. This will create a TLocalAppServer provider "in the background"
ClientDataSet1.Open;
randomize;
while not ClientDataSet1.Eof do
begin
ClientDataSet1.edit;
ClientDataSet1SortField.AsInteger := random(100);
// as ClientDataSet1SortField is fkInternalCalc it doesn't need to be in the query result set, but can be assigned and used for sorting
ClientDataSet1.Post;
ClientDataSet1.Next;
end;
clientdataset1.IndexFieldNames := 'SortField';
end;
I've got a dataset that I need a lookup field for. Problem is, this dataset's structure is defined by the result of a query. I can't add the field as a TFieldDef before setting .Active = true; because it gets overwritten, and I can't add it after running the query because you can't alter the structure of an open dataset.
There has to be some way to do this. Does anyone know how?
EDIT: There seems to be some confusion about what I'm looking for. I'm not looking for a lookup at query time. I'm looking for a lookup field, a TField object where FieldKind = fkLookup, so that it can be used with a data-aware lookup combo box, for editing the data after the query has returned its result. This has nothing whatsoever to do with the SQL and everything to do with Delphi's dataset model and data-aware controls.
The easiest way is to define persistent fields at design time.
You could also modify your SQL statement to get the calculated values from the server.
You need to create the fields yourself before you open the dataset.
First get all your field definitions from the database
DataSet.FieldDefs.Update;
Then loop through each fielddef and create the normal field, and also create the lookup field where appropriate. Simplified version of the code like this;
for I := 0 to DataSet.FieldDefs.Count - 1 do
begin
// Allocate a normal field
AField := DataSet.FieldDefs[I].CreateField(DataSet);
// Add lookup field if we have another table to look it up from
if (??? this is the key field of a lookup table) then
begin
AField := TStringField.Create(DataSet.Owner);
AField.FieldName := ???;
AField.DataSet := DataSet;
AField.FieldKind := fkLookup;
AField.KeyFields := ???;
AField.LookupKeyFields := ???;
AField.LookupDataSet := ???;
AField.LookupResultField := ???;
end;
end;
Then you can open the dataset.
You have two datasets on your form (say tblOrder,tblCustomer)
One field in the order is a foreign key to the customer table customerId
The Key field of the customer table is Id, Name = Name
Add all fields (right click on the datasets, fields editor , add all fields.
Then right click on the order table and choose fields editor then right click new field.
Name = myLookup,Type is string,Size is xx, FieldType = Lookup.
Key field = customerid,dataset = tblCustomer, lookup Key = Id,Result field = Name.
Now your lookup field is defined.
To make it work in an editor (say in a TDBLookupCombo)
Add a datasources to the form dsOrder
Connect it to tblOrder.
Now set datasource = dsOrder,Field = myLookup
You don't have to set the lookup source...
Let you have table Orders with field (among others) CustomerId (of type Integer), and table Customers with fields CustomerId (of type Integer) and CustomerName (of type String). Then, in Delphi IDE, drop on the form datasets table_main and table_lookup (for tables Orders and Customers respectively). Open fields editor for table_main, add (among others) field CustomerId, then create new field with field properties:
name: CustomerName (for example)
type: integer
field type: lookup
and with lookup properties:
dataset: table_lookup
Key fields: CustomerId
Lookup Keys: CustomerId
Result Fields: CustomerName
Hope it is still relevant now.
I've got an in-memory dataset with several fields, one of which is a primary key that another dataset references as a foreign key. Thing is, the master dataset can have multiple references to the detail dataset. (This is modeling an object that contains a dynamic array of other objects.)
If there was only one of each sub-object, I could make the right association with the KeyFields and LookupKeyFields properties of the reference field in the master dataset, but that's only designed to return one result. I want to load all the records whose primary key matches the right ID key and display them in a listbox.
I thought a TDBListBox would help with this, but it turns out that's not what they do. So how would I populate a listbox or similar control with the result set of a multiple-match check like that for further editing? I want something similar to the result of a SQL query like this:
select field1, field2, field3
from client_dataset
where client_dataset.primary_key = master_dataset.id
Only thing is, this is done entirely with in-memory datasets. No real databases are being used here. Does anyone know how this can be done?
The dataset has a Filter property which can be set with a condition. You also have to set the filtered flag on true. And with he datacontrols you can select which fields are visible.
So:
var
c : TColumn;
begin
clientdataset.Filter := Format('primary_key = %d', [master_dataset.id]);
clientdataset.Filtered := True;
c := DBGrid1.Columns.Add;
c.FieldName := 'field1';
c := DBGrid1.Columns.Add;
c.FieldName := 'field2';
c := DBGrid1.Columns.Add;
c.FieldName := 'field3';
end;
Should do the trick.