FastReport Master/Detail in Delphi XE2 - oracle

I need some help creating a Master/Detail report in Fast Reports for Delphi XE2.
I have a simple form which accepts 2 dates and 2 times from a user. I then have 2 Oracle Datasets on the form with which to retrieve my data. When the user presses the print button, the program accepts the values from the user and sends the values to the first oracle dataset, which then in turn retrieves the first value, and then sends this value along with the user accepted values to the second dataset to print the detail pertaining to the value retrieved.
For each dataset I do have a corresponding frxDBDataset component which is then assigned to the frxReport1 component. With in the report, I have created a Master Band which is assigned to dataset1, and a Detail Band assigned to datset2. When I run my report, dataset 1 brings back all the records, but dataset 2 only brings back the records for the first value and duplicates it for every record in dataset1.
Below is the code I am trying to execute:
opr_operator_ods.Close;
opr_operator_ods.SetVariable('DATEFROM', opr_datefrom_dtp.Date);
opr_operator_ods.SetVariable('DATETO', opr_dateto_dtp.Date);
opr_operator_ods.SetVariable('TIMEFROM', opr_timefrom_dtp.Text);
opr_operator_ods.SetVariable('TIMETO', opr_timeto_dtp.Text);
opr_operator_ods.Open;
if opr_operator_ods.RecordCount > 0 then
begin
while not opr_operator_ods.Eof do
begin
opr_operatorcount_ods.Close;
opr_operatorcount_ods.SetVariable('DATEFROM', opr_datefrom_dtp.Date);
opr_operatorcount_ods.SetVariable('DATETO', opr_dateto_dtp.Date);
opr_operatorcount_ods.SetVariable('TIMEFROM', opr_timefrom_dtp.Text);
opr_operatorcount_ods.SetVariable('TIMETO', opr_timeto_dtp.Text);
opr_operatorcount_ods.SetVariable('OPERATOR',
opr_operator_ods.FieldByName('opr_code').AsString);
opr_operatorcount_ods.Open;
while not opr_operatorcount_ods.Eof do
begin
frxReport1.PrepareReport(false);
opr_operatorcount_ods.Next;
end;
frxReport1.PrepareReport(true);
opr_operator_ods.Next;
end;
DecodeDate(opr_datefrom_dtp.Date, tyear, tmonth, tday);
StartDate := '''' + IntToStr(tday) + '/' + IntToStr(tmonth) + '/' + IntToStr(tyear) + '''';
DecodeDate(opr_dateto_dtp.Date, tyear, tmonth, tday);
EndDate := '''' + IntToStr(tday) + '/' + IntToStr(tmonth) + '/' + IntToStr(tyear) + '''';
frxReport1.Variables['StartDate'] := StartDate;
frxReport1.Variables['EndDate'] := EndDate;
//frxReport1.PrepareReport(True);
frxReport1.ShowPreparedReport;
How do I get the second dataset to move on to the next record's values?
This report used to work perfectly in Delphi 2005 with RaveReports6, but there we used code based form development which was easier to manipulate with a 'writeln' and not visual like with Fast Reports.

When creating the preview FastReport does something like this code:
while not MasterBand.DataSet.Eof do
begin
...Do special FastReport's work :)
while not DetailBand.DataSet.eof do
begin
...Do special FastReport's work :)
DetailBand.DataSet.Next;
end;
MasterBand.DataSet.Next;
end;
In your code:
while not opr_operatorcount_ods.Eof do
begin
frxReport1.PrepareReport(false);
opr_operatorcount_ods.Next; <-- here opr_operatorcount_ods is in the last position from PrepareReport
end;
Data bands may be a master or detail type, but they only control the positioning of data of the output page
(order and number of times displayed).
Data displayed by the objects in the bands depends on relationship between the two (or more) datasets.
So you should made relation
Relationship can be done in several ways.
If you want to use parameters you can do this as follows:
Place DataSource component.
Connect it to dataset1(opr_operator_ods) using DataSet property DataSet = opr_operator_ods;
in DataSource.OnDataChange event write :
opr_operatorcount_ods.Close;
......
//Set parameter(relation between opr_operator(Master) and opr_operatorcount(Detail)
opr_operatorcount_ods.Params.ParamByName('opr_code').asString := opr_operator_ods.FieldByName('opr_code').AsString);
opr_operatorcount_ods.Open;
And then prepare and print report as:
procedure Print;
begin
//Prepare Master dataset ( parameters, close open etc.) like :
opr_operator_ods.Close;
opr_operator_ods.SetVariable('DATEFROM', opr_datefrom_dtp.Date);
opr_operator_ods.SetVariable('DATETO', opr_dateto_dtp.Date);
opr_operator_ods.SetVariable('TIMEFROM', opr_timefrom_dtp.Text);
opr_operator_ods.SetVariable('TIMETO', opr_timeto_dtp.Text);
opr_operator_ods.Open;
...
frxReport1.PrepareReport;
frxReport1.ShowPreparedReport;
end;

Related

how I can control text of selected item in list item in oracle forms?

I am a beginner in PL/SQL and Oracle Forms .
I am working in an Oracle form. Created a push button and updated it to item type: list item. I want to choose between items when I click on it. I should redirect with if according to the item chose. How can I do that?
NOTE: I'am using forms 6i ,
item_type : list item
list style : popup list
elements in list : S_SATIS(value : item24) - S_MARKA(value : item25)
trigger : when-list-changed
This is how I got list item name and values:
l_count := GET_LIST_ELEMENT_COUNT('list_item');
FOR i in 1..l_count LOOP
IF GET_LIST_ELEMENT_VALUE('list-item',i) = :list_item THEN
l_text := GET_LIST_ELEMENT_LABEL('list_item',i);
l_value := Name_In('list_item');
I want to redirect according to the item selected with if. This is the method I think is correct. But it is not working correctly. I used message to see if it goes inside the if . but it didn't print the message. How can I fix?
ELSIF l_value = '<list item value name>' THEN
message(l_value);
GO_BLOCK('<block_name>');
EXECUTE_QUERY
ELSIF l_text = '<list item name>' THEN
message(l_text );
GO_BLOCK('<block_name>');
EXECUTE_QUERY
this way the problem is solved

Get current cell value in DBGrid

In my Delphi application, I use lookup fields, but in unusual way. Actually, I wanna update field in underlying data set, just like if it was in the same table.
Existing guides tell that there is no problem, just join the table and voila... I envy if they really succeeded this task with such simple solution. I do not. BTW I think I'm getting close to reach my goal. I have one question left: how the hell I can get value I just entered into DBGrid Cell?
I tried DBGrid[FieldName].EditValue and .DisplayText, but they show the same value as Field.Value, which doesn't change after exiting the column, because it is lookup field. Sender.NewValue is null. I'm using this function to update lookup table:
procedure TKDGridForm.LookupFieldChange(Sender: TField);
begin
if not Assigned(Sender) then
Exit;
Sender.OnChange := nil;
if not Assigned(Sender.LookupDataSet) then
Exit;
if Sender.LookupDataSet.Locate(Sender.LookupKeyFields, Sender.DataSet[Sender.KeyFields], []) then
Sender.LookupDataSet.Edit
else
Sender.LookupDataSet.Append;
// how do I get the value I just entered?
Sender.Value := KDGrid3[Sender.FieldName].DisplayText;
Sender.LookupDataSet.FieldValues[Sender.LookupResultField] := Sender.Value;
Sender.LookupDataSet.Post;
Sender.OnChange := LookupFieldChange;
end;
Here is SQL I used before I ended up with lookup fields:
select det.*,
od1.T_EQ T_SHABLON_EQ,
od1.T_NV T_SHABLON_NV,
od1.T_PRIM T_SHABLON_PRIM,
od2.T_EQ T_PRAVKA_EQ,
od2.T_NV T_PRAVKA_NV,
od2.T_PRIM T_PRAVKA_PRIM,
od3.T_EQ T_VALCOV_EQ,
od3.T_NV T_VALCOV_NV,
od3.T_PRIM T_VALCOV_PRIM,
od4.T_EQ T_REZKA2_EQ,
od4.T_NV T_REZKA2_NV,
od4.T_PRIM T_REZKA2_PRIM
from CMKNEW.details det
left join CMKNEW.OperDetails od1
ON det.nrec = od1.cdetail
and 81 = od1.coper
left join CMKNEW.OperDetails od2
ON det.nrec = od2.cdetail
and 82 = od2.coper
left join CMKNEW.OperDetails od3
ON det.nrec = od3.cdetail
and 83 = od3.coper
left join CMKNEW.OperDetails od4
ON det.nrec = od4.cdetail
and 84 = od4.coper
where det.ckd=:CKD order by det.NREC
Hope it will explain my task clearer. If you wanna mcve, I can extend this, though I think it's not essential.
My database is Oracle, connected through ADO. I'd like the solution to be as simple as possible.
I assume you're talking about a standard TDBGrid and that what you're asking is how to get the text which is displayed in a cell of the grid when you type into it, but before the grid's dataset is updated. At that point, the current row indicator in the LH column will have changed from the default right-pointing triangle to an I-beam
If so, the snippet below shows you how to do get this text value. The point is, in the condition I've described, what's in the cell hasn't yet been posted back to the underlying dataset field. What happens is that when you start editing, an InplaceEditor (TCustomMaskEdit descendant) is dynamically created, and it's this which holds the text value which is being edited.
Add a TTimer and a TMemo to your form and then run the code below to see what I mean.
type
TMyGrid = Class(TDBGrid);
procedure TMyForm.Timer1Timer(Sender: TObject);
var
S : String;
Grid : TmyGrid;
begin
Grid := TmyGrid(DBGrid1);
if Grid.InplaceEditor <> Nil then
S := Grid.InplaceEditor.Text
else
S := IntToStr(Grid.Col) + ':' + IntToStr(Grid.Row);
Grid.Invalidate;
Memo1.Lines.Insert(0, S);
end;

Set select list value via PLSQL

I am calling an apex page via URL and pass all item values in the request like
f?p=&APP_ID.:44:&SESSION.:INSERT:&DEBUG.:44:P44_NAME,P44_DESCRIPTION,P44_PARENT_PK_ID:#NAME#,#DESCRIPTION#,#PARENT_PK_ID#_#PK_ID#
In my case I have to check if the row has a parent key reference value. If yes I have to set the parent key reference value to P44_PARENT_PK_ID. Otherwise, I have to set the key reference value (#PK_ID#) to P44_PARENT_PK_ID. That's why I am passing both values split with "_" in the URL.
On page 44 I have a process on the "Before Regions" process point:
DECLARE
v_demilitedstring varchar2(100);
BEGIN
v_demilitedstring := v('P44_PARENT_PK_ID');
IF nvl(to_number(substr(v_demilitedstring, 1, instr(v_demilitedstring, '_', 1, 1) -1)), 0) = 0 then
:P44_PARENT_PK_ID := substr(v_demilitedstring, instr(v_demilitedstring, '_', -1, 1) +1);
ELSE
:P44_PARENT_PK_ID := substr(v_demilitedstring, 1, instr(v_demilitedstring, '_', 1, 1) -1);
end if;
END;
I set the success message as &P44_PARENT_PK_ID. to check if the right value is assigned to it. The procedure is working fine and the correct value is set to P44_PARENT_PK_ID. However, the assigned value is not selected in select list (Meaning the display name of value is not displayed).
How can I trigger the select list item to change it's display value?
So it sounds like you have a select list generating correctly and you you want to change the value that is selected when the page loads. For that, go to the Source section of the select list item. You can drive that based on another page item, like P44_PARENT_PK_ID, a query, or a number of other options.
Make sure that the list of values for your select list will have the source value in it.

Delphi ADO dataset Filter

In an application I am building at work, I have a large database with a table say "People" with 100,000 + rows. Furthermore the entries in this table contain two types of data :
Parent type and Child type, where each Child type entry has the database id of its parent in a special "Child_OF" column.
In memory, both db entry types are represented by corresponding classes "TParent" and "TChild", where each parent class has the field "children : TList".
Which is the fastest way, using ADO, to:
- create a list of Parents and correctly assign to them their children...
The way I see it... one can go about the problem by
1) retrieve in a bulk( by one sql query) all parents from the table and create the parents list with empty children lists.
2) retrieve in a bulk all children and for each parent try to find his/her children from the corresponding dataset.
Here is an example of what I have in mind for the assignment stage of the program...
procedure assignParentsTheirChildren(parentList: TList<TParent>;
ma_people: TADOTable);
var
i: Integer;
qry: TADOQuery;
aChild: TChild;
aParent: TParent;
begin
// create the query
qry := TADOQuery.Create(nil);
qry.Connection := ma_people.Connection;
// set the sql statement to fetch all children ...
qry.SQL.Clear;
qry.SQL.Add('Select * from ' + ma_people.TableName + ' WHERE ChildOF <> ' +
QuotedStr(''));
// supposedly do some optimization---
qry.CursorLocation := clUseClient; // load whole recordset in memory
qry.DisableControls;
// disable controls ensures that no dataset bound control will be updated while iterating the recordset
qry.CursorType := ctStatic; // set cursor to static
// open dataset
qry.Open;
// ***EDIT*** for completeness I add the suggestion made by Agustin Seifert below
qry.RecordSet.Fields['ChildOf'].Properties.Item['Optimize'].value := true;
for i := 0 to parentList.count - 1 do
begin
// get daddy
aParent := parentList[i];
qry.Filter := 'ChildOF = ' + QuotedStr(IntToStr(aParent.parentID));
qry.Filtered := true;
while (not qry.EOF) do
begin
aChild := TChild.Create;
getChildFromQuery(aChild, qry); // fills in the fields of TChild class...
aParent.children.Add(aChild);
qry.Next;
end;
end;
qry.Free;
end;
I guess the biggest bottleneck of the above code is that I am filtering the data for every new parent. Is there a faster rework using seek() or locate/find...? Basically one can assume that my dataset is static (during the time of creating the parents list) and network latency infinite:) (that is, I first want to do the child to parent assignment from memory).
Many thanks!
btw I am using Microsoft SQL Server 2012.
If you don't wanna change your code/logic, there's a way to optimize filter, find, sort operations in ADO.
Access the recordset an optimize the involved fields:
var
qry: TADOQuery;
rs: _Recordset;
...
begin
...
//after qry.Open;
rs := qry.Recordset;
rs.Fields['YourField'].Properties.Item['Optimize'].Value := True; //YourField = ChildOF in your case
This will create an index for the field. It takes a small amount of time vs the time it takes to filter lot of times without an index.
msdn: Optimize Property-Dynamic (ADO)

Picking query based on parameter in Oracle PL/SQL

Ok, say I have a query:
SELECT * FROM TABLE_AWESOME WHERE YEAR = :AMAZINGYEAR;
Which works very nicely. But say I want to be able to return either just those results or all results based on a drop down. (e.g., the drop down would have 2008, 2009, ALL YEARS)
I decided to tackle said problem with PL/SQL with the following format:
DECLARE
the_year VARCHAR(20) := &AMAZINGYEAR;
BEGIN
IF the_year = 'ALL' THEN
SELECT * FROM TABLE_AWESOME;
ELSE
SELECT * FROM TABLE_AWESOME WHERE YEAR = the_year;
END IF;
END;
Unfortunately, this fails. I get errors like "an INTO clause is expected in this SELECT statement".
I'm completely new to PL/SQL so I think I'm just expecting too much of it. I have looked over the documentation but haven't found any reason why this wouldn't work the way I have it. The query I'm actually using is much much more complicated than this but I want to keep this simple so I'll get answer quickly.
Thanks in advance :)
There is a real danger in the queries offered by Jim and Alex.
Assumption, you have 20 years of data in there, so a query on YEAR = return 5% of the blocks. I say blocks and not rows because I assume the data is being added on that date so the clustering factor is high.
If you want 1 year, you want the optimizer to use an index on year to find those 5% of rows.
If you want all years, you want the optimizer to use a full table scan to get every row.
Are we good so far?
Once you put this into production, the first time Oracle loads the query it peaks at the bind variable and formulates a plan based on that.
SO let's say the first load is 'All'.
Great, the plan is a Full table scan (FTS) and that plan is cached and you get all the rows back in 5 minutes. No big deal.
The next run you say 1999. But the plan is cached and so it uses a FTS to get just 5% of the rows and it takes 5 minutes. "Hmmm... the user says, that was many fewer rows and the same time." But that's fine... it's just a 5 minute report... life is a little slow when it doesn't have to be but no one is yelling.
That night the batch jobs blow that query out of the cache and in the morning the first user asks for 2001. Oracle checks the cache, not there, peeks at the variable, 2001. Ah, the best plan for that is an index scan. and THAT plan is cached. The results come back in 10 seconds and blows the user away. The next person, who is normally first, does the morning "ALL" report and the query never returns.
WHY?
Because it's getting every single row by looking through the index.... horrible nested loops. The 5 minute report is now at 30 and counting.
Your original post has the best answer. Two queries, that way both will ALWAYS get the best plan, bind variable peeking won't kill you.
The problem you're having is just a fundamental Oracle issue. You run a query from a tool and get the results back INTO the tool. If you put a select statement into a pl/sql block you have to do something with it. You have to load it into a cursor, or array, or variable. It's nothing to do with you being wrong and them being right... it's just a lack of pl/sql skills.
You could do it with one query, something like:
SELECT * FROM TABLE_AWESOME WHERE (? = 'ALL' OR YEAR = ?)
and pass it the argument twice.
In PL/SQL you have to SELECT ... INTO something, which you need to be able to return to the client; that could be a ref cursor as tanging demonstrates. This can complicate the client.
You can do this in SQL instead with something like:
SELECT * FROM TABLE_AWESOME WHERE :AMAZING_YEAR = 'ALL' OR YEAR = :AMAZINGYEAR;
... although you may need to take care about indexes; I'd look at the execution plan with both argument types to check it isn't doing something unexpected.
Not sure about using a SqlDataSource, but you can definately do this via the system.data.oracle or the oracle clients.
You would do this via an anonymous block in asp.net
VAR SYS1 REFCURSOR;
VAR SYS2 REFCURSOR;
DECLARE
FUNCTION CURSORCHOICE(ITEM IN VARCHAR2) RETURN SYS_REFCURSOR IS
L_REFCUR SYS_REFCURSOR;
returnNum VARCHAR2(50);
BEGIN
IF upper(item) = 'ALL' THEN
OPEN L_REFCUR FOR
SELECT level FROM DUAL
CONNECT BY LEVEL < 15 ;
ELSE
OPEN L_REFCUR FOR
SELECT 'NONE' FROM DUAL ;
END IF;
RETURN L_REFCUR;
END ;
BEGIN
:SYS1 := CURSORCHOICE('ALL');
:SYS2 := CURSORCHOICE('NOT ALL');
end ;
/
PRINT :SYS1 ;
PRINT :SYS2 ;
whereas you would simply create an output param (of type refcursor) -- instead of the var sys# refcursors) and pretty much just amend the above code.
I answered a similar question about getting an anonymous block refcuror here
How to return a RefCursor from Oracle function?
This kind of parameter shall be processed from within your code so that your OracleCommand object only executes either queries.
using (var connection = new OracleConnection(connString)) {
connection.Open();
string sql = "select * from table_awesome";
sql = string.Concat(sql, theYear.Equals(#"ALL") ? string.Empty : " where year = :pYear")
using (var command = connection.CreateCommand()) {
command.CommancText = sql;
command.CommandType = CommandType.Text;
var parameter = command.CreateParameter();
parameter.Name = #":yearParam";
parameter.Direction = ParameterDirection.Input;
parameter.Value = theYear;
var reader = command.ExecuteQuery();
if (!reader.HasRows) return;
while (reader.Read()) {
// Extract your data from the OracleDataReader instance here.
}
}
}

Resources