Searching an unorderd list of descendants - data-structures

In my Delphi 2007 database application, I have a list of Generations. Each Generation has a list of pricing templates, each of which is a set of values. In the application, the user can create a line item and assign a pricing template from any generation. A new generation is created by cloning an existing generation. So the user only has to create the first generation, then clone it and only change the required pricing template values in the new generation. These generations(and pricing templates) are connected by originids.ie, Generation1's originid will be zero and generation2 will have originid pointing to generation1' id etc. Also, the user can create a new generation from any of the existing ones.ie, generation3's origin can be generation1. Now, the user wants a refresh functionality where the user can update the pricing templates from a generation to any other generation if they are "connected". ie, If they connected by origin ids (Not necessarily by a direct connection.ie,If the source generation's origin id is pointing directly to the target's id or any of the direct children of target, or any of the children of the target's children..etc). Please see the description below.
What is the best way to search (algorithm /data structure) for a target from a source in this case?
Thank you all in advance,
Pradeep
GenerationName --Id -- OriginID
Generation 1 -- 100-- 0
Generation 2 --101 --100 (Cloned from 1)
Generation 3 --102 --100 (Cloned from 1)
Generation 4 --103 --102 (Cloned from 3)
Generation 5 --104 --101 (Cloned from 2)
Here,the user can update from Generation 1 to Generation 3(Generation 1Generation 3) or Generation 4(Generation 1Generation 3Generation 4) or Generation 5((Generation 1Generation 2Generation 5) because they are connected. But Generation 3 to generation 5 is not allowed because there is no link between them.

In my view using tree data structure is the best way for these sort of problems.
Coming to Delphi IDE, you can use TTreeView component for this purpose.
Put the data in the tree structure by using TTreeView component. Click here for tutorials regarding TTreeView.
For searching in TTreeView you go through this link.

To determine if an generation can be upgraded to another I would use a function similar to this. Here gen is a structure where you have loaded your generations and can lookup them by Id.
function canUpgrade(fromId, toId: integer) : boolean;
var
id : integer;
begin
Result := false;
id := toId;
while id<>0 do
begin
if gen[id].originId=fromId then
begin
Result := true;
Break;
end
else
begin
id := gen[id].originId;
end;
end;
end;

Related

In oracle apex how to navigate to different pages from link column depending on another column data

I have a table with multiple columns in that one column have data like text field in that data is either A or B and another column is of link type link is getting fetched by id column, Now I have a situation by comparing the data in column1 if it's 'A' I want to navigate to page 3 and If it's 'B' I want to navigate to page 4 by click of link column. How to achieve this?
It would probably be easier for us if you actually shared table and its contents instead of describing it; moreover, as you wrote it as a single sentence with no punctuation, it is difficult to understand what you want to say.
The way I understood it, table is used as a source for some kind of a report (interactive or classic?). One of its (reports) columns is used as a link to another page. As link depends on column's value, I suggest you use a case expression and compose link to target page.
I don't know how you are creating the link now, but - if you already don't use it - have a look at GET_URL function which does most of the dirty job for you. In its simplest appearance, you'd just
select id,
apex_page_get_url(p_page => case when column1 = 'A' then 1
when column2 = 'B' then 2
end) as link
from ...
You need to create Dynamic action on Button.
Button -> True -> Action := Execute server-side-code
Settings -> language := PLSQL
FOR PLSQL QUERY
3)Items to Submit = P7_NEW,P7_NEW_1
4) Items to Return = P31_URL
This will create URL and Trigger the URL to next page

Oracle Apex - Links in report row that gets a value from the row and uses it to generate another report on a different page

Oracle Apex 5.1
I have a report (Report A) that has a table of values generated from a SQL query.
How do I get a link column to get a value (e.g. employee_ID) of the current row then send that value to another page that has a report (Report B) that is generated using the value from report A.
New to Apex and a lot of the guide or tutorials seem very convoluted for something that seems like quite a standard thing.
Thanks for any help!
This is a pretty basic thing and there are many examples of it online.
The simplest way is to create a column link (in attributes) which links to your target page containing report B. On the page containing report B have a hidden item (e.g. employee_id) and in your link use the set items area to set PX_employee_id to value #employee_id#. Then on report B have a where clause - where employee_id = :PX_EMPLOYEE_ID (replace X with the relevant page number).

ServiceNow-How to get a record (INCIDENT/REQUEST/CHANGE)

Is there any common URL to navigate to serviceNow portal with number (INC/REQ/CHG)?
I have an common URL (INC/REQ/CHG) which can be navigated to ServiceNow portal by sys_id:
https://XXXXdev.service-now.com/nav_to.do?uri=task.do?sys_id=XXXXXXXXXXXXXXXXXXXXX
I have tried below URLs for incident but it is creating a new record
XXXXdev.service-now.com/nav_to.do?uri=incident.do?sysparm_order=INC0XXX
OR
XXXXdev.service-now.com/nav_to.do?uri=incident.do?number=INC00XXXX
No problem if there are different URLs for INC, REQ or CHG, I want a URL to see record by giving a number but not with sys_id.
From what you have described I believe you want:
https://[instance].service-now.com/incident.do?sysparm_query=number=INC1234567
https://[instance].service-now.com/sc_request.do?sysparm_query=number=REQ1234567
https://[instance].service-now.com/change_request.do?sysparm_query=number=CHG1234567
Or the catch all
https://[instance].service-now.com/task.do?sysparm_query=number=INC1234567
https://[instance].service-now.com/task.do?sysparm_query=number=REQ1234567
https://[instance].service-now.com/task.do?sysparm_query=number=CHG1234567
(see https://docs.servicenow.com/bundle/helsinki-servicenow-platform/page/use/navigation/concept/c_NavigatingByURL.html)
If you want to keep the outer frames of SN then use the nav_to.do?uri= version e.g.
https://[instance].service-now.com/nav_to.do?uri=sc_request.do?sysparm_query=number=REQ1234567
which is what #kirk has described.
You have a couple of options depending on what you're trying to do exactly.
Email
If you're trying to send this link in an email notification, you can use ${URI_REF} for the table that the notification is generated on.
This will automatically generate a link to the record.
You can also dot walk to another table, for example
Catalog Task up to the Requested Item ${request_item.URI_REF}
Current task up top the parent record ${parent.URI_REF}
Calculated
If you are looking to generate this based on some calculated method you can do this with the table name and the record number.
https://instance.service-now.com/nav_to.do?uri=/table_name.do?sysparm_que‌​ry=number=RECORD_NUMBER
For instance for a Change record with record number CHG0000123
https://instance.service-now.com/nav_to.do?uri=/change.do?sysparm_que‌​ry=number=CHG0000123
You may also reference a record by using a parent table, though this is more for an interesting note rather than a good practice. INC, REQ, and CHG all inherit from the Task table so you could do this.
https://instance.service-now.com/nav_to.do?uri=/task.do?sysparm_que‌​ry=number=CHG0000123
The drawback to using the parent table is that it won't have all fields and won't have the proper form layout. Mostly just an interesting exercise.
You may also leverage the text search and the system will pull up a record that matches the record number.
https://instance.service-now.com/nav_to.do?uri=/textsearch.do?sysparm_que‌​ry=number=CHG0000123
Note that this works out of the box and you could have configured search to not behave that way.
Business Rule
Another method inside of a Business Rule is to use the current.getLink() method. This will return a URL to the record. If you would like to add to a journal entry, you could use this
var currentLink = "[code]<a href='" + current.getLink() + "'>" + current.number + "</a>[/code]";
var journal = gs.getMessage("The new record is {0}", [currentLink]);
gr.work_notes = journalEntry;
Your initial URI will actually work with a NUMBER (like INC000001), assuming that number is actually the display field for the table (as it is by default):
https://XXXX.service-now.com/incident.do?sys_id=NUMBER
When you do a sys_id= lookup, we first lookup by the sys_id column. If it's not found there, we try a lookup on the display value (number), which will load the form exactly as you expect.
{yourinstance}.service-now.com/nav_to.do?uri=incident.do?sys_id={incidentNumber}
Replace incidentNumber with your incident number .

SSRS VS 2013 - Grouping and page breaks multiple different datasets

I am looking to have a report repeat its data across multiple pages, group by / page break dependent on the project ID. This is simple enough with 1 tablix.
My issue is that I have 3 tablixes, each with their own data source due to different table structures. I am unable to union / join all 3 sets of data into 1 table. All 3 data sources can be linked by an project ID that is shared across all of the data for that project ID. What I am looking to do is repeat this report structure across multiple pages as defined by the project ID.
[Report Header]
[Tablix 1]
[Tablix 2]
[Tablix 3]
[Report Footer]
My current implementation uses a Rectangle that encases the 3 tablixes as they make up the "Report Data", however, there is no context to pass any sort of grouping parameter to children of the Rectangle.
Another thread recommends the potential use of sub-reports (How to apply parent group for multiple datasets in SSRS VS2008). I would like to avoid this design if possible due to the pitfalls it presents.

Delphi Performance: Reading all values under a field in a dataset

We're trying to find out some performance fixes reading from a TADOQuery. Currently, we loop through the records using 'while not Q.eof do begin ... Q.next method. For each, we read ID and Value of each record, and add each to a combobox list.
Is there a way to convert all values of a specified field into a list in one shot? Rather than looping through the dataset? It would be really handy if I can do something like...
TStrings(MyList).Assign(Q.ValuesOfField['Val']);
I know that's not a real command, but that's the concept I'm looking for. Looking for a fast response and solution (as always but this is to fix a really urgent performance issue).
Looking at your comment, here are a few suggestions:
There are a few things that are likely to be a bottleneck in this situation. The first is looking up the fields repeatedly. If you're calling FieldByName or FindField inside your loop, you're wasting CPU time recomputing a value that's not going to change. Call FieldByName once for each field you're reading from and assign them to local variables instead.
When retrieving values from the fields, call AsString or AsInteger, or other methods that return the data type you're looking for. If you're reading from the TField.Value property, you're wasting time on variant conversions.
If you're adding a bunch of items to a Delphi combo box, you're probably dealing with a string list in the form of the Items property. Set the list's Capacity property and make sure to call BeginUpdate before you start updating, and call EndUpdate at the end. That can enable some internal optimizations that makes loading large amounts of data faster.
Depending on the combo box you're using, it could have some trouble dealing with large numbers of items in its internal list. See if it has a "virtual" mode, where instead of you loading everything up-front, you simply tell it how many items it needs, and when it gets dropped down, it calls an event handler for each item that's supposed to be shown on screen, and you give it the right text to display. This can really speed up certain UI controls.
Also, you should make sure your database query itself is fast, of course, but SQL optimization is beyond the scope of this question.
And finally, Mikael Eriksson's comment is definitely worthy of attention!
You can use Getrows. You specify the column(s) you are interested in and it will return an array with values. The time it takes to add 22.000 rows to a combo box goes from 7 seconds with the while not ADOQuery1.Eof ... loop to 1.3 seconds in my tests.
Sample code:
var
V: Variant;
I: Integer;
begin
V := ADOQuery1.Recordset.GetRows(adGetRowsRest, EmptyParam, 'ColumnName');
for I:= VarArrayLowBound(V, 2) to VarArrayHighBound(V, 2) do
ComboBox1.Items.Add(V[0, I]));
end;
If you want more than one column in the array you should use a variant array as the third parameter.
V := ADOQuery1.Recordset.GetRows(adGetRowsRest, EmptyParam,
VarArrayOf(['ColumnName1', 'ColumnName2']);
There are some great performance suggestions made by other folks that you should implement in Delphi. You should consider them. I will focus on ADO.
You haven't specified what the back end database server is, so I can't be too specific, but there are some things that you should know about ADO.
ADO RecordSet
In ADO, there is a RecordSet object. That RecordSet object is basically your ResultSet in this case. The interesting thing about iterating through the RecordSet is that it's still coupled with the provider.
Cursor Type
If your cursor type is Dynamic or Delphi's default Keyset, then each time the RecordSet requests a new row from the provider, the provider will check to see if there were any changes before it returns the record.
So, for the TADOQuery where all you're doing is reading the result set to populate the combobox, and it's not likely to have changed, you should use the Static cursor type to avoid checking for updated records.
In case you don't know what a cursor is, when you call a function like Next, you are moving the cursor, which represents the current record.
Not every provider supports all of the cursor types.
CacheSize
Delphi's and ADO's default cache size for a RecordSet is 1. That's 1 record. This works in combination with the cursor type. The cachesize tells the RecordSet how many records to fetch and store at a time.
When you issue a command like Next (really MoveNext in ADO) with a cache size of 1, the RecordSet only has the current record in memory, so when it fetches that next record, it must request it from the provider again. If the cursor is not Static, that gives the provider the opportunity to get the latest data before returning the next record. So, a size of 1 makes sense for Keyset or Dynamic, because you want the provider to be able to get you the updated data.
Obviously, with a value of 1, there's communication between the provider and RecordSet each time move the cursor. Well, that's overhead that we don't want if the cursor type is static. So, increasing your cache size will reduce the number of round trips between the RecordSet and the provider. This also increases your memory requirements, but it should be faster.
Also note that with a cache size greater than 1 for Keyset cursors, if the record that you want is in the cache, it won't request it from the provider again, which means that you won't see the updates.
You can't avoid looping. "Very long time" is relative but if retrieving 20000 records takes too long there's something wrong.
Check your query; perhaps the SQL can be improved (missing index?)
Show the code of your loop where you add items to the combobox. Maybe it can be optimized. (calling FieldByName repeatedly in a loop? using variants to retrieve field values?)
Make sure to call ComboBox.Items.BeginUpdate; before the loop and ComboBox.Items.EndUpdate after.
Use a profiler to find the bottleneck.
You could try pushing all data into a ClientDataSet and iterating this, but I think copying the data to the CDS does exactly what you are currently doing - looping and assigning.
What I did once was concatenating values on the server, transmitting it in one bulk to the client and splitting it again. This actually made the system faster because it reduced the communication necessary between client and server.
You have to look careful where the performance bottleneck is. It could as well be the combobox if you don't block GUI updates while adding values (ecpecially when we are talking about 20K values - that's a lot to scroll).
Edit: When you cannot change the communication then you perhaps could make it asynchronous. Request new data in a thread, keep the GUI responsive, fill the combobox when the data is there. It means the user sees an empty combobox for 5 seconds, but at least he can do something else in the meantime. Doesn't change the amount of time needed, though.
Is your query also tied to some data aware controls or a TDataSource? If so, do your looping inside an DisableControls and EnableControls block so your visual controls don't update each time you move to a new record.
Is the list of items fairly static? If so, consider creating a non-visual instance of a combobox at application startup, maybe inside a separate thread, and then assign your non-visual combobox to the visual combobox when your form is created.
try use DisableControls and EnableControls for increase performance of linear process in dataset.
var
SL: TStringList;
Fld: TField;
begin
SL := TStringList.Create;
AdoQuery1.DisableControls;
Fld := AdoQuery1.FieldByName('ListFieldName');
try
SL.Sorted := False; // Sort in the query itself first
SL.Capacity := 25000; // Some amount estimate + fudge factor
SL.BeginUpdate;
try
while not AdoQuery1.Eof do
begin
SL.Append(Fld.AsString);
AdoQuery1.Next;
end;
finally
SL.EndUpdate;
end;
YourComboBox.Items.AddStrings(SL);
finally
SL.Free;
AdoQuery1.EnableControls;
end;
end;
Not sure if this will help, but my suggestion would be not to add directly to the ComboBox. Load to a local TStringList instead, make that as fast as possible, and then use TComboBox.Items.AddStrings to add them all at once:
var
SL: TStringList;
Fld: TField;
begin
SL := TStringList.Create;
Fld := AdoQuery1.FieldByName('ListFieldName');
try
SL.Sorted := False; // Sort in the query itself first
SL.Capacity := 25000; // Some amount estimate + fudge factor
SL.BeginUpdate;
try
while not AdoQuery1.Eof do
begin
SL.Append(Fld.AsString);
AdoQuery1.Next;
end;
finally
SL.EndUpdate;
end;
YourComboBox.Items.BeginUpdate;
try
YourComboBox.Items.AddStrings(SL);
finally
YourComboBox.Items.EndUpdate;
end;
finally
SL.Free;
end;
end;

Resources