Conditions in FOR EACH which is TRUE regardless of database records? - progress-4gl

FOR EACH loan WHERE /* Condition1 */ loan.date = SomeDate
AND /* Condition2 */ loan.type = SomeType
AND /* Condition3 */ (IF SpecMode THEN TRUE ELSE loan.spec = SomeSpec)
Condition3 = TRUE when SpecMode = TRUE. So I wonder if code above and code below would work exact the same (incl. speed) when SpecMode = TRUE?
FOR EACH loan WHERE Condition1
AND Condition2
AND TRUE
Or even like this?
FOR EACH loan WHERE Condition1
AND Condition2
Generally speaking question is how Progress manages conditions which can be evaluated regardless of database records. Does it take more time? Also links about how Progress works in more deep view would be appreciated.
Add 08/16/13:
Code I'm working with initially was:
IF SpecMode THEN
FOR EACH loan WHERE Condition1 AND Condition2:
RUN Proc.
ELSE
FOR EACH loan WHERE Condition1 AND Condition2 AND Condition3:
RUN Proc.
Dynamic queries was first idea came to my mind, but I realized that it mean rewriting of all nested code with dynamic queries style (which is imo has worse readabilty, I moved this to separate question). I want to go with modern approach so I will do it if it's most reasonable solution.
There is third way which keeps code in "static style". It is using include files with parameters. But it means another .i file in already huge codebase. And I generally hate this approach (as well as "cheating" with preprocessor constants containing code). Our system is big and old and full of this kind of things, which is harder to analyze and basically seems to be just not right.

The ( IF ... THEN ... ELSE ... ) embedded in the WHERE clause is treated as a function.
In general it is preferable to avoid having function calls in a WHERE clause. Some, but not all, can be resolved on the server side. (User-defined functions and CAN-DO() are two examples that must be evaluated on the client.)
In your case I don't know that the IF is always true. If specMode is true then it is but if it is false the ELSE portion is evaluated and I can't know how that works out without looking at the data. So it would not necessarily be correct to replace it as you suggest.
If specMode is always true then, yes, replacing it as you suggest (both versions) will work.
From an efficiency point of view the use of the IF function eliminates the ability to bracket on the loan.spec field. If loan.date and loan.type are leading components of an index then they will be used to bracket. If loan.spec is also an index component it cannot, however, be used to improve the selection and the whole subset of records carved out by the other criteria will need to be examined individually.
I think that the code you are showing is probably being used to provide a "clever" option of showing either ALL records with date and type, or just a subset of date, type and spec. This might have been an attractive (but hard to read and inefficient) approach back in the old days. It would have allowed a single block of code to handle whatever is inside the loop body.
In today's world you don't need to do that. You would just write a dynamic query and populate a proper WHERE clause as a string that you then use QUERY-PREPARE on. That way if loan.spec is part of an index it can be used to help support the query and those cases will run much faster.

As an addon to Toms excellent answer, a tip is to compile with xref whenever you are unsure of what indices are used.
COMPILE file.p [SAVE] XREF file.txt.
That will genereate a file containing index usage (amongst other things). Look for rows like this:
c:\temp\program.p c:\temp\include.i 21 SEARCH db.webkonv tkn WHOLE-INDEX
SEARCH indicates that index "tkn" of the table "webkonv" is used in this example. WHOLE-INDEX indicating that the entire index has been scanned (in this case that was expected - this code was used before the TABLE-SCAN directive). Check the ABL Reference on the COMPILE statement.

for your case:
EACH loan WHERE /* Condition1 */ loan.date = SomeDate
AND /* Condition2 */ loan.type = SomeType
AND /* Condition3 */ (IF SpecMode THEN TRUE ELSE loan.spec = SomeSpec)
You can try like this:
your_loop:
EACH loan WHERE /* Condition1 */ loan.date = SomeDate
AND /* Condition2 */ loan.type = SomeType
:
/* Condition3 */
IF not SpecMode THEN DO:
If loan.spec <> SomeSpec THEN
NEXT your_loop. /* both false, next loop */
END.
/* ........ do your work ...... */
END.

I can't see any sense in use a for each that does not mention any field of the table..... the whole table will be selected regardless if the condition is true or false, since there is no inquiry to the table.
In any case Progress evaluates the full condition and retrieve the set of record that matches that condition.
Example 1.
def var as logical init false.
for each items where a:
display item.code. /* Will display nothing but read all records of the table */
end.
Example 2:
def var a as logical init true.
for each items where a:
display item.code. /* Will display all records of the table */
end.
Example 3:
Define variable v_user_can_read_items init false.
For each item where item.code begins "4" and
v_user_can_read_items = true:
display item.code. /* will display nothing but read all items starting by 4 */
End.
The time taken will depend on the where condition, on the first 2 cases will be slow, because is reading ALL the records. In teh third case will be fast, because is first selecting the set of items starting by 4 and then don't displying because the condition return false.
The shortest tilme is when is using an appripiate index (where condition does) and the retrieved set is a short set of data.

Define variable v_user_can_read_items init false.
For each item where item.code begins "4" and
v_user_can_read_items = true:
display item.code. /* will display nothing but read all items starting by 4 */
End.

Related

Getting value from WHEN part while a condition meets using EXISTS keyword in drools

rule "attaching AV and impact rating"
agenda-group "evaluate likelihood"
dialect "java"
when
Application($threatList:getThreatList())
$av:AttackVector()
exists $threat:Application.Threats(impact == "Disclose Information")from $threatList
exists AttackVector($av == AttackVector.REQUEST_MANIPULATION)
then
RiskRating riskRating=new RiskRating($threat.getImpactRating(),$av.getLikelihood(),$av.getName());
insertLogical(riskRating);
end
I am working on getting the object $threat in THEN part of the above-mentioned rule. If I run the above rule, it says:
Rule Compilation error : [Rule name='attaching AV and impact rating']
referee/security/attack/Rule_attaching_AV_and_impact_rating1426933818.java (7:1053) : $threat cannot be resolved
If I loop through it and get the value in the THEN part, it causes a CARTESIAN product and inserts the values a number of times in the session. My rule looks like this when I get the cartesian product.
rule "attaching AV and impact rating"
agenda-group "evaluate likelihood"
dialect "java"
when
Application($threatList:getThreatList())
$av:AttackVector()
exists $threat:Application.Threats(impact == "Disclose Information")from $threatList
$threat:Application.Threats(impact == "Disclose Information")from $threatList
exists AttackVector($av == AttackVector.REQUEST_MANIPULATION)
then
RiskRating riskRating=new RiskRating($threat.getImpactRating(),$av.getLikelihood(),$av.getName());
insertLogical(riskRating);
end
How do I get the value of $threat in THEN part without having the cartesian product?
Remove the exists operation entirely.
rule "attaching AV and impact rating"
agenda-group "evaluate likelihood"
dialect "java"
when
Application($threatList:getThreatList())
$av: AttackVector()
$threat: Application.Threats(impact == "Disclose Information")from $threatList
exists(AttackVector($av == AttackVector.REQUEST_MANIPULATION))
then
RiskRating riskRating=new RiskRating($threat.getImpactRating(),$av.getLikelihood(),$av.getName());
insertLogical(riskRating);
end
exists means "there is a thing in working memory that matches these conditions/looks like this". It's not used to actually extract or provide a reference to that matching instance. Simply remove the operator and it works as you need -- if there is an Application.Threats that matches your conditions, the rule triggers and the matching value is assigned to the $threat variable.
What you're running into is the fact that you have multiple threats that mean your condition, which is why you're having multiple triggers of the rule -- it will trigger once per matching Application.Threats. The exists keyword mitigates this because it only cares that at least one match exists, but you don't get a reference (because if there's four matches which one will be assigned to the variable? it doesn't make sense, logically.)
So you need to change your rule so that it won't fire multiple times and will instead only fire once when it finds a match. Usually you'd do this by making the consequences do something to working memory that makes the rule no longer eligible to be fired. In your example, you insert a RiskRating object; you could, then, check that no risk rating exists in your conditions:
not( RiskRating( /* insert criteria here or leave empty */ ) )
Alternatively you could retract something from working memory that your rule relies on to be present or a match. For example, if you don't need it for anything later on, you could retract the attack vector:
retract( $av )
Yet another option might be to try and update your getThreatList() implementation to return a Set instead so you don't have duplicates (assuming threats are considered duplicates based on the 'impact' field.) Or you could try to remove all Application.Threats instances that match the criteria from the threatlist being returned.
We simply don't know enough about your use case or rule set to know what data you need or what it looks like, but at the end of the day you simply need the rule to fire once and only once, so to do this you need to somehow update the rule to know that it's no longer valid.

LHS Make Empty List Trigger Rule

I am new to Drools so please bear over with the terminology!
Can I make rule trigger even though an empty list is involved in the LHS?
I have the code below.
There are two rules. One rule that insert facts and another rule that work on facts.
Part of fact B is to hold a list of facts A.
If this list is not empty then I am able to work on fact B.
If this list is empty then I am not able to work on fact B.
How can I write the code so I am able to work on fact B even though the list is empty.
If I activate the line below "b.As.add(a);" then I can work on fact B.
If I deactivate the line below "b.As.add(a);" then I can work not on fact B.
declare A
nameA : String
end
declare B
nameB : String
As : java.util.ArrayList
end
rule "insertfacts"
when
then
A a = new A();
a.setNameA("A");
B b = new B();
b.setNameB("B");
b.As = new java.util.ArrayList();
b.As.add(a); // Only with this line rule checkfacts fires
insert( b );
end
rule "checkfacts"
when
$b : B();
$a : A() from $b.As;
then
// take action based on object $b
end
Thanks
Thomas S
To check if an object is present in working memory, you should use not() or exists() (depending on what you're trying to do.)
So your insert-facts rule should be like this:
rule "insertfacts"
when
not(B())
then
A a = new A();
a.setNameA("A");
B b = new B();
b.setNameB("B");
b.As = new java.util.ArrayList();
b.As.add(a); // Only with this line rule checkfacts fires
insert( b );
end
This way you won't end up with extra B instances because insertfacts will only trigger when B doesn't exist (so, once.)
To check for an empty list, you can do size == 0 as a check:
rule "As list is empty"
when
$b: B()
ArrayList( size == 0 ) from $b.As
then
// At this point you know that As is an empty list,
// so you can insert stuff if you want
end
Alternatively, if you want to check that a specific object is not in the list, you can use not memberOf.
rule "Some instance A is not in the As list"
when
$a: A() // the A instance we're trying to check
$b: B( $a not memberOf As )
then
// at this point As is a list of any size which doesn't contain the $a instance
end
Similarly there's memberOf for checking that the specific object is in the list:
rule "Some instance A is in the As list"
when
$a: A()
$b: B( $a memberOf As )
then
// As is a list of any size which DOES contain $a
end
Note that there are two complementary operators you could be using to check that something is or is not part of a list: memberOf and contains. They're roughly the same, just with the arguments in different orders. See this other question for more information about those two operators.
Of course, if you don't care at all about the contents of the As ArrayList, then just don't check anything against it in the "when" clause. If you're sure you'll never have a situation where it's undefined, you can even skip the null check.
rule "We don't care whether there's something in As"
when
$b: B( $as: As )
then
// at this point we have access to the variable $as which will be
// the arraylist; it may be empty, it may have stuff in it
end
... but you could always do a null check if you want to be safe:
$b: B( $as: As != null)
It was mentioned in a comment that you plan to write regular code in the "then" clause and loop through the list. Usually you don't want to do that -- if you just want to do work against the members of the list or a subset of the list (eg items of the list which meet certain criteria), Drools will implement the iteration out of the box for you.
As a simple example, let's say that you want to find all of the A instances inside of the list which have the name "PURCHASE" and update the name to be "VERIFIED_PURCHASE".
rule "Update PURCHASE to VERIFIED_PURCHASE"
when
B( $as: As != null )
$a: A( name == "PURCHASE" ) from $as
then
$a.name = "VERIFIED_PURCHASE"
end
(You could add an update call on the right hand side if it's important to reevaluate rule matches, but in this simple example it's not needed.)
What will happen here, and what's usually a little difficult for people new to Drools to wrap their heads around, is that Drools will internally iterate through the As array list and check each item to see if it matches the requested criteria (name == "PURCHASE"). For each item it finds that matches the criteria, it will trigger the right hand side.
So for this example, if As has 10 items and 3 are named "PURCHASE", this rule's consequences ("then" clause) will trigger 3 times, once per matched item.
If you need a collection of these matches on the right hand side, you can use collect or accumulate depending on how complex your use case is.
rule "Get all purchases"
when
B( $as: As != null )
$purchases: ArrayList() from collect( A( name == "PURCHASE" ) from $as )
then
// do something with $purchases here
end
Of course, you shouldn't then turn around and iterate through $purchases on the right hand side -- use the built-in way I showed previously.
The Drools engine is extremely good at optimizing performance; anything you put in the "when" clause gets optimized by Drools, which is why you want to do the iteration there (and let Drools leverage its own internal capabilities at that.)
The right hand side ("then" clause) is not optimized, so by putting the iteration and other logic on that side is going to make your rules perform worse in comparison. You may not notice the difference in a toy example like the ones we're working on in this question, but once you start processing hundreds or thousands of requests per minute, it will definitely start showing.
(I spent 10 years of my career supporting an embarrassingly huge collection of rules (scale is millions of rules) and I've never had the need to actually write a for-loop or any sort of iteration in the "then" clause. And the few places where I did find them as added by other engineers, I was able to remove them for non-negligible performance improvements. Let the framework do what it's good at.)

Drools - Running a rule with an empty object

I'm trying to write a rule to calculate prices for an insurance product based on conditions. In the 'when' I'm using an object called AdditionalDriver, which contains the details for drivers other than the policy holder. From this, different prices can be calculated based on whether the additional driver is a parent, friend, spouse etc. See below:
when
AdditionalDriver($relToProp : relationToProposer)
then
String relToProp = $relToProp;
if(!relToProp.equals("P"))
{
//prices
}
end
"P" = parent.
This rule works when an additional driver has been added. However, if there is no additional driver, then the object is empty, and so the rule does not run. What do I need to do to get this rule to run, even when the object is empty?
Thanks in advance.
You should write one rule for each of the relative or acquaintance classes:
when
PolicyHolder( $phid: id )
AdditionalDriver( relationToProposer == "P", belongsTo == $phid )
then
//prices
end
For no additional driver being requested, write a rule
when
PolicyHolder( $phid: id )
not AdditionalDriver( belongsTo == $phid )
then
// cheaper prices
end
Don't use conditional statement in your consequences to further distinguish facts. This is a code smell.

Play/Scala Template Block Statement HTML Output Syntax with Local Variable

Ok, I've been stuggling with this one for a while, and have spent a lot of time trying different things to do something that I have done very easily using PHP.
I am trying to iterate over a list while keeping track of a variable locally, while spitting out HTML attempting to populate a table.
Attempt #1:
#{
var curDate : Date = null
for(ind <- indicators){
if(curDate == null || !curDate.equals(ind.getFirstFound())){
curDate = ind.getFirstFound()
<tr><th colspan='5' class='day'>#(ind.getFirstFound())</th></tr>
<tr><th>Document ID</th><th>Value</th><th>Owner</th><th>Document Title / Comment</th></tr>
}
}
}
I attempt too user a scala block statement to allow me to keep curDate as a variable within the created scope. This block correctly maintains curDate state, but does not allow me to output anything to the DOM. I did not actually expect this to compile, due to my unescaped, randomly thrown in HTML, but it does. this loop simply places nothing on the DOM, although the decision structure is correctly executed on the server.
I tried escaping using #Html('...'), but that produced compile errors.
Attempt #2:
A lot of google searches led me to the "for comprehension":
#for(ind <- indicators; curDate = ind.getFirstFound()){
#if(curDate == null || !curDate.equals(ind.getFirstFound())){
#(curDate = ind.getFirstFound())
}
<tr><th colspan='5' class='day'>#(ind.getFirstFound())</th></tr>
<tr><th>Document ID</th><th>Value</th><th>Owner</th><th>Document Title / Comment</th></tr>
}
Without the if statement in this block, this is the closest I got to doing what I actually wanted, but apparently I am not allowed to reassign a non-reference type, which is why I was hoping attempt #1's reference declaration of curDate : Date = null would work. This attempt gets me the HTML on the page (again, if i remove the nested if statement) but doesn't get me the
My question is, how do i implement this intention? I am very painfully aware of my lack of Scala knowledge, which is being exacerbated by Play templating syntax. I am not sure what to do.
Thanks in advance!
Play's template language is very geared towards functional programming. It might be possible to achieve what you want to achieve using mutable state, but you'll probably be best going with the flow, and using a functional solution.
If you want to maintain state between iterations of a loop in functional programming, that can be done by doing a fold - you start with some state, and on each iteration, you get the previous state and the next element, and you then return the new state based on those two things.
So, looking at your first solution, it looks like what you're trying to do is only print an element out if it's date is different from the previous one, is that correct? Another way of putting this is you want to filter out all the elements that have a date that's the same date as the previous one. Expressing that in terms of a fold, we're going to fold the elements into a sequence (our initial state), and if the last element of the folded sequence has a different date to the current one, we add it, otherwise we ignore it.
Our fold looks like this:
indicators.foldLeft(Vector.empty[Indicator]) { (collected, next) =>
if (collected.lastOption.forall(_.getFirstFound != next.getFirstFound)) {
collected :+ next
} else {
collected
}
}
Just to explain the above, we're folding into a Vector because Vector has constant time append and last, List has n time. The forall will return true if there is no last element in collected, otherwise if there is, it will return true if the passed in lambda evaluates to true. And in Scala, == invokes .equals (after doing a null check), so you don't need to use .equals in Scala.
So, putting this in a template:
#for(ind <- indicators.foldLeft(Vector.empty[Indicator]) { (collected, next) =>
if (collected.lastOption.forall(_.getFirstFound != next.getFirstFound)) {
collected :+ next
} else {
collected
}
}){
...
}

Assigning a whole DataStructure its nullind array

Some context before the question.
Imagine file FileA having around 50 fields of different types. Instead of all programs using the file, I tried having a service program, so the file could only be accessed by that service program. The programs calling the service would then receive a DataStructure based on the file structure, as an ExtName. I use SQL to recover the information, so, basically, the procedure would go like this :
Datastructure shared by service program :
D FileADS E DS ExtName(FileA) Qualified
Procedure called by programs :
P getFileADS B Export
D PI N
D PI_IDKey 9B 0 Const
D PO_DS LikeDS(FileADS)
D LocalDS E DS ExtName(FileA) Qualified
D NullInd S 5i 0 Array(50) <-- Since 50 fields in fileA
//Code
Clear LocalDS;
Clear PO_DS;
exec sql
SELECT *
INTO :LocalDS :nullind
FROM FileA
WHERE FileA.ID = :PI_IDKey;
If SqlCod <> 0;
Return *Off;
EndIf;
PO_DS = LocalDS;
Return *On;
P getFileADS E
So, that procedure will return a datastructure filled with a record from FileA if it finds it.
Now my question : Is there any way I can assign the %nullind(field) = *On without specifying EACH 50 fields of my file?
Something like a loop
i = 1;
DoW (i <= 50);
if nullind(i) = -1;
%nullind(datastructure.field) = *On;
endif;
i++;
EndDo;
Cause let's face it, it'd be a pain to look each fields of each file every time.
I know a simple chain(n) could do the trick
chain(n) PI_IDKey FileA FileADS;
but I really was looking to do it with SQL.
Thank you for your advices!
OS Version : 7.1
First, you'll be better off in the long run by eliminating SELECT * and supplying a SELECT list of the 50 field names.
Next, consider these two web pages -- Meaningful Names for Null Indicators and Embedded SQL and null indicators. The first shows an example of assigning names to each null indicator to match the associated field names. It's just a matter of declaring a based DS with names, based on the address of your null indicator array. The second points out how a null indicator array can be larger than needed, so future database changes won't affect results. (Bear in mind that the page shows a null array of 1000 elements, and the memory is actually relatively tiny even at that size. You can declare it smaller if you think it's necessary for some reason.)
You're creating a proc that you'll only write once. It's not worth saving the effort of listing the 50 fields. Maybe if you had many programs using this proc and you had to create the list each time it'd be a slight help to use SELECT *, but even then it's not a great idea.
A matching template DS for the 50 data fields can be defined in the /COPY member that will hold the proc prototype. The template DS will be available in any program that brings the proc prototype in. Any program that needs to call the proc can simply specify LIKEDS referencing the template to define its version in memory. The template DS should probably include the QUALIFIED keyword, and programs would then use their own DS names as the qualifying prefix. The null indicator array can be handled similarly.
However, it's not completely clear what your actual question is. You show an example loop and ask if it'll work, but you don't say if you had a problem with it. It's an array, so a loop can be used much like you show. But it depends on what you're actually trying to accomplish with it.
for old school rpg just include the nulls in the data structure populated with the select statement.
select col1, ifnull(col1), col2, ifnull(col2), etc. into :dsfilewithnull where f.id = :id;
for old school rpg that can't handle nulls remove them with the select statement.
select coalesce(col1,0), coalesce(col2,' '), coalesce(col3, :lowdate) into :dsfile where f.id = :id;
The second method would be easier to use in a legacy environment.
pass the key by value to the procedure so you can use it like a built in function.
One answer to your question would be to make the array part of a data structure, and assign *all'0' to the data structure.
dcl-ds nullIndDs;
nullInd Ind Dim(50);
end-ds;
nullIndDs = *all'0';
The answer by jmarkmurphy is an example of assigning all zeros to an array of indicators. For the example that you show in your question, you can do it this way:
D NullInd S 5i 0 dim(50)
/free
NullInd(*) = 1 ;
Nullind(*) = 0 ;
*inlr = *on ;
return ;
/end-free
That's a complete program that you can compile and test. Run it in debug and stop at the first statement. Display NullInd to see the initial value of its elements. Step through the first statement and display it again to see how the elements changed. Step through the next statement to see how things changed again.
As for "how to do it in SQL", that part doesn't make sense. SQL sets the values automatically when you FETCH a row. Other than that, the array is used by the host language (RPG in this case) to communicate values back to SQL. When a SQL statement runs, it again automatically uses whatever values were set. So, it either is used automatically by SQL for input or output, or is set by your host language statements. There is nothing useful that you can do 'in SQL' with that array.