The difference between using DataSet and Table on a Master/details relation? - delphi

Are the results the same? because I tried and I don't see anything that changes except in DataSet, I can write a query/command while on Table I can select the table only.

I tried and I don't see anything that changes except in DataSet, I can write a
query/command while on Table I can select the table only.
Your observation is broadly correct because you need to know whether a particular Table type supports being used on the Detail side of a Master-Detail relationship and, if it does, how to configure it.
Delphi has Table-like descendants for various
database types, e.g. TTable for the (obsolete) BDE, TADOTable for ADO, etc. Whether they support being on the Detail side of a Master-Detail
relationship is up to the author of the particular table type and so is
what you need to do to get a given Table type to handle Detail dataset behaviour.
Taking Delphi'ssupplied ADO components as an example, if you are using a TADOQuery
as the Detail dataset, you write the SQL for it to include a WHERE clause which links
the dataset to the Master, as in
`where masterid = :masterid`
adnd set its DataSource property to the datasource connected to the Master table.
To use a TADODataSet as the detail, there is the problem that you can only (easily) set
the (detail) table name, not which records from it are retrieved to match the Master row.
The way TADOTable's authors chose to address this is to
give the TADOTable MasterFields and MasterSource properties which you use to
link the ADOTable to the Master dataset
use the Filter property of the underlying RecordSet object to a filter expression
which filters out all but the matching Detail records. See procedure TCustomADODataSet.ActivateTextFilter in ADODB.Pas.
Example project:
type
TForm1 = class(TForm)
ADOConnection1: TADOConnection;
qMaster: TADOQuery;
qDetail: TADOQuery;
dsMaster: TDataSource;
tDetail: TADOTable;
procedure FormCreate(Sender: TObject);
public
end;
[...]
procedure TForm1.FormCreate(Sender: TObject);
begin
qMaster.Connection := AdoConnection1;
qMaster.SQL.Text := 'select * from master';
qDetail.Connection := AdoConnection1;
qDetail.SQL.Text := 'select * from detail where masterid = :masterid';
qDetail.DataSource := dsMaster;
// tDetail is a TADOTable
tDetail.Connection := AdoConnection1;
tDetail.TableName := 'detail';
tDetail.MasterSource := dsMaster;
tDetail.MasterFields := 'MasterID';
qMaster.Open;
qDetail.Open;
tDetail.Open;
end;

Related

How to limit number of records in TFDMemTable?

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. *)

procedure to explore an analytic view in SAP HANA

I am creating a procedure that can explore an analytic view given one dimension, one measure and a filter (where clause)
drop procedure dynamicExploration;
create procedure dynamicExploration(in currentMeasure double, in filter_string
varchar(100), out dataSubset dataExplorationOutputType)
language sqlscript as
begin
dataSplitby = select CITY as ID, SUM(:currentMeasure) as SUM_MEASURE from
_SYS_BIC."package/analyticView" Group by CITY;
--dataSubset = APPLY_FILTER(:dataSplitby, :filter_string);
dataSubset = select * from :dataSplitBy;
end;
where dataSubset is a data type defined as follows:
drop type dataExplorationOutputType;
create type dataExplorationOutputType as table("ID" varchar(100), "SUM_MEASURE" double);
but I am getting this error, could your please check what's wrong;
Could not execute 'create procedure dynamicExploration(in currentMeasure double, in
filter_string varchar(100), out ...' in 166 ms 8 µs .
SAP DBTech JDBC: [266] (at 200): inconsistent datatype: only numeric type is available
for aggregation function: line 4 col 36 (at pos 200)
I also tried to define currentMeasure as varchar but still getting the same error.
What I am trying to achieve eventually is to create a stored procedure that can help another procedure to select a data subset based on a set of given parameters defined by the user: dimension, measure and filters.
drop procedure dynamicExploration;
create procedure dynamicExploration(in currentDimension varchar(100), in currentMeasure double, in filter_string
varchar(100), out dataSubset dataExplorationOutputType)
language sqlscript as
begin
dataSplitby = select :currentDimension as ID, SUM(:currentMeasure) as SUM_MEASURE from
_SYS_BIC."package/analyticView" Group by :currentDimension;
dataSubset = APPLY_FILTER(:dataSplitby, :filter_string);
--dataSubset = select * from :dataSplitBy;
end;
I have already created a procedure to do this kind of dynamic exploration based on dynamic SQL, a feature that is not recommended. What I am looking for is a better solution/idea to do this kind of dynamic exploration of an analytic view (data cube).
thanks
You will have to construct a dynamic SQL and execute it with the EXECUTE IMMEDIATE command. I know it's not recommended, but your use case requires it. Make sure to protect yourself from SQL injection, e.g. by checking the name of the dimension that is passed into your wrapper procedure against a list of "allowed" dimensions

Sort DBGrid by clicking column's title

Well, this seems a little tricky (if not imposible). I'm trying to make my DBGrid sort its data by clicking on column's title.
The thing is that I'm (sadly) working with Delphi 3, I'm not using ADO DataSets and the query gets a lot of rows, thus I can't reopen my TQuery changing the order by clause on clicks.
Someone has implemented something like this?
This is actually done by sorting the dataset, and then the grid reflects the change. It can be done easily enough by creating an index on the dataset field for that column. Of course, this can only be done on a dataset that supports index sorting, such as TClientDataset.
On the TDBGrid's OnTitleClick method you can do something like...
procedure TfmForm1.DBGrid1TitleClick(Column: TColumn);
var
i: Integer;
begin
// apply grid formatting changes here e.g. title styling
with DBGrid1 do
for i := 0 to Columns.Count - 1 do
Columns[i].Title.Font.Style := Columns[i].Title.Font.Style - [fsBold];
Column.Title.Font.Style := Column.Title.Font.Style + [fsBold];
with nxQuery1 do // the DBGrid's query component (a [NexusDB] TnxQuery)
begin
DisableControls;
if Active then Close;
for i := 0 to SQL.Count - 1 do
if (Pos('order by', LowerCase(SQL[i])) > 0) then
//NOTE: ' desc' The [space] is important
if (Pos(' desc',LowerCase(SQL[i])) > 0) then
SQL[i] := newOrderBySQL
else
SQL[i] := newOrderBySQL +' desc';
// re-add params here if necessary
if not Active then Open;
EnableControls;
end;
end;
There are plenty of ways in which you could optimise this I'm sure however it depends on the capabilities of the components you use. The example above uses a query component though if you used a table component you'd change the index used instead of the 'order by' clause.
The handling of the SQL here is a very basic version. It does not handle things like SQL batch statements, resulting in possible multiple 'order by..' clauses or commented SQL statements i.e. ignoring bracketed comments "{..}" or single line comments "//"
Regards
Delphi 3 have TClientDataset. And TQuery can use explicitly created indexes on the database to order data on the IndexName property.
Here are some examples of how to do this: Sorting records in Delphi DBGrid by Clicking on Column Title .
As already mentioned, sorting is quite easy if you are using a TClientDataSet (cds.IndexFieldNames := Column.FieldName in the OnTitleClick of the TDBGrid). However if you are not able to do this you can either regenerate your query (which you have stated you don't want to do) or obtain a more advanced data grid such as Express Quantum Grid (which I think allows you to sort).
On the TDBGrid's OnTitleClick method you can write this simple code:
procedure TForm1.DBGrid3TitleClick(Column: TColumn);
var
cFieldName:string;
begin
cFieldName:= DBGrid3.SelectedField.FieldName;
AdoDataset1.Sort:=cFieldName;
end;
example: (https://www.thoughtco.com/sort-records-in-delphi-dbgrid-4077301)
procedure TForm1.DBGrid1MouseMove(Sender: TObject; Shift: TShiftState; X,
Y: Integer);
var
pt: TGridcoord;
begin
pt:= DBGrid1.MouseCoord(x, y);
if pt.y=0 then
DBGrid1.Cursor:=crHandPoint
else
DBGrid1.Cursor:=crDefault;
end;
procedure TForm1.DBGrid1TitleClick(Column: TColumn);
var
cFieldName:string;
begin
Adotable1.Sort := Column.Field.FieldName;
end;
If you are using a combination of TFDQuery, TDataSource and TDBGrid you can order with this easy way!
procedure TFrmGer.DBGridTitleClick(Column: TColumn);
begin
OrderByTitle(MyFDQuery, Column);
end;
Put it in a helper file so you can use it latter again.
procedure OrderByTitle(AQuery: TFDQuery; Column: TColumn);
begin
AQuery.IndexFieldNames := Column.DisplayName;
end;
Ascending and Descending Mode
if Pos('DESC',PChar(Q2.Sort))>0 then
Q2.Sort:=Column.FieldName + ' ASC'
else
Q2.Sort:=Column.FieldName + ' DESC';

Delphi: Reading number of columns + names from dataset?

Since Embarcadero's NNTP server stopped responding since yesterday, I figured I could ask here: I work with a non-DB-aware grid, and I need to loop through a dataset to extract the number of columns, their name, the number of rows and the value of each fields in each row.
I know to read the values for all the fields in each row, but I don't know how to extract column-related information. Does someone have some code handy?
procedure TForm1.FormCreate(Sender: TObject);
var
index : Integer;
begin
With ASQLite3DB1 do begin
DefaultDir := ExtractFileDir(Application.ExeName);
Database := 'test.sqlite';
CharacterEncoding := 'STANDARD';
Open;
end;
With ASQLite3Query1 do begin
ASQLite3Query1.Connection := ASQLite3DB1;
SQL.Text := 'CREATE TABLE IF NOT EXISTS mytable (id INTEGER PRIMARY KEY, label VARCHAR)';
ExecSQL;
SQL.Text := 'INSERT INTO mytable (label) VALUES ("dummy label")';
ExecSQL;
SQL.Text := 'SELECT id AS Identification, label AS Label FROM mytable';
Open;
//How to get column numbers + names to initialized grid object?
for index := 0 to ASQLite3Query1. - 1 do begin
end;
for index := 0 to FieldCount - 1 do begin
ShowMessage(Fields[index].AsString);
end;
end;
end;
Thank you.
Number of fields and their names could be acquired as follows:
procedure TForm1.Button1Click(Sender: TObject);
begin
with Query1 do
begin
ShowMessage(IntToStr(FieldCount));
ShowMessage(Fields[0].FieldName);
end;
end;
You can checkout TFieldDef for more detail info about the field.
dataset.FieldDefs[0] has properties like DataType and Size.
If what you're looking for is a list of field names, try creating a TStringList and passing it to the TDataset.Fields.GetFieldNames procedure.
If you want more information about fields, the TFields object (ASQLite3Query1.Fields) has a default property and a Count property, so you can use it like an array, and an enumerator, both of which can be used to loop over each TField object and retrieve its metadata.

Delphi: Displaying a subset of a data set in data-aware controls

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.

Resources