Get name of table that TField is from - delphi

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.

Related

TADOQuery Including blank rows

When using the 'while not TADOQuery.Eof' with an microsoft Excel Workbook, it's including rows which are completely empty. Is there a way to stop including any rows that are completely blank as I don't need them?
You could exclude blank lines in the SQL used to open the spreadsheet. If the first row contains column headings like 'Column1', 'Column2', etc then the following SQL will not return rows where the value in the first column is blank
select * from [sheet1$]
where Column1 <> ''
Obviously the SQL could be a bit more specific (in terms of column values) about what you regard as constituting a blank row.
You'll have gathered that there are various ways to deal with variations in the contents of the column headers, but as the other answer shows, these are likely to be far more verbose than simply skipping blank rows inside the body of your main while not EOF loop to read the table contents, so I can't really see any benefit to not doing it by just skipping the blank rows.
Btw, ime the Excel data accessible via SQL behaves as though the query is automatically restricted to the UsedRange range in the Excel COM interface.
Original answer:
If I understand you correctly and you want to exclude empty rows after the query is opened, then next approach may help (but I think, that you should exclude these rows with SQL statement, as in #MartynA's answer). Here, empty rows are all rows, which have Null value for all fields.
procedure TForm1.btnDataClick(Sender: TObject);
var
i: Integer;
empty: Boolean;
begin
qry.First;
while not qry.Eof do begin
// Check for empty row. Row is empty if all fields have NUull value.
empty := True;
for i := 0 to qry.FieldCount - 1 do begin
if not qry.Fields[i].IsNull then begin
empty := False;
Break;
end{if};
end{for};
// Read record data if record is not empty
if not empty then begin
// Your code here ...
end{if};
// Next record
qry.Next;
end{while};
end;
Update:
It's an attempt to improve my answer. If the table structure is not known, you can query the table with always false WHERE clause to get this structure and generate an SQL statement dynamically:
procedure TForm1.btnDataClick(Sender: TObject);
var
i: Integer;
where: string;
begin
// Get column names
qry.Close;
qry.SQL.Clear;
qry.SQL('SELECT * FROM [SheetName] WHERE 1 = 0');
try
qry.Open;
except
ShowMessage('Error');
end{try};
where := '';
for i := 0 to qry.FieldCount - 1 do begin
where := where + '(' + qry.Fields[i].FieldName + ' <> '''') AND ';
end{for};
where := 'WHERE ' + Copy(where, 1, Length(where) - 5);
// Read data without "empty" rows
qry.Close;
qry.SQL.Clear;
qry.SQL('SELECT * FROM [SheetName] ' + where);
try
qry.Open;
except
ShowMessage('Error');
end{try};
end;

provide column data type in DBSMS_SQL.DEFINE_COLUMN oracle 12C

I have to copy data from one table to another with below two conditions
table names will be known at run time
records need to be copied one at a time so that modifications can be done in column values when required
I have created a procedure to to do this through dynamic query. Since the column list is not known already I am not able to declare a rowtype variable. I saw an example of DBMS_SQL where you can define the columns for select clause. Below is the format
DBMS_SQL.DEFINE_COLUMN(cursor_var,position,column_var);
Problem here is that in all the examples I found the column_var were already declared. However in my case I will get to know the no of columns that will be in cursor sql and their data type at run time. so I need to find a way to pass the data type of "column_var" as part of DBMS_SQL.DEFINE_COLUMN. Is there a way to do that? Is there a better way?
Below is just a sample code
CREATE OR REPLACE PROCEDURE pr_test (P_TABLE_NAME IN VARCHAR2)
IS
V_SQL VARCHAR2(500);
SRC_CUR INT;
DEST_CUR INT;
TYPE COL_DTL_TYPE IS RECORD
(
COLUMN_ID INT,
COLUMN_NAME VARCHAR2(250),
DATA_TYPE VARCHAR2(250),
DATA_LENGTH INT
);
COL_DTL_REC COL_DTL_TYPE;
TYPE TBL_COL_LIST_TYPE IS TABLE OF COL_DTL_TYPE;
TBL_COL_LIST TBL_COL_LIST_TYPE;
V_CNT INT := 0;
BEGIN
V_SQL := 'SELECT * FROM ' || P_TABLE_NAME;
SRC_CUR := DBMS_SQL.OPEN_CURSOR;
DBMS_SQL.PARSE(SRC_CUR,V_SQL,DBMS_SQL.NATIVE);
TBL_COL_LIST := TBL_COL_LIST_TYPE();
FOR COL_DTL_REC IN (
SELECT COLUMN_ID,COLUMN_NAME,DATA_TYPE,DATA_LENGTH
FROM ALL_TAB_COLUMNS WHERE TABLE_NAME =P_TABLE_NAME
)
LOOP
V_CNT := V_CNT + 1;
TBL_COL_LIST.EXTEND;
TBL_COL_LIST(V_CNT) := COL_DTL_REC;
-- Here is where I am stuck and not able to give column data type
DBMS_SQL.DEFINE_COLUMN(SRC_CUR,V_CNT,COL_DTL_REC.COLUMN_NAME COL_DTL_REC.DATA_TYPE , COL_DTL_REC.DATA_LENGTH)
END LOOP;
END;
copying to destination table will come later.

How to modify calculated field with TADODataSet?

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)

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: 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