Drools: Default value for queries in case of no match - drools

I'm writing a Drools rule set processing events of type A and B; and in many of the rules I need to compare A's attribute timestamp to maximum of a subset of B's attribute windowStart to qualify that A event. If that subset of B is an empty set, I assume there is no windowStart, and so the value 0 is desired. Any A's with the a timestamp greater than windowStart will qualify.
Consider the following pseudo code for elaboration:
long findMaxWindowStartOrZero(int bID)
{
Set bSubset = getAllBWithID(bID);
if(bSubset is empty) return 0;
return max(bSubset, B::windowStart);
}
without such query, LHS of every rule concerning this comparison needs to be duplicated, once to account for absence of any B and once to find the actual maximum when the subset is non-empty.
Having such query as the pseudo code above makes this task substantially easier, and removes the need to branch the LHS. Is it possible to do that? Is there any drawback or benefit for doing that instead of branching LHS of mentioned rules?

rule qualify_A
when
accumulate( B( id == "bID", $ws: windowStart ); $mws: max( $ws ) )
$a: A( timestamp > $mws )
then
...process $a...
end
I think that this rule does not fire if there are no matching Bs. To work around this, insert a B with windowStart set to 0. This dummy could also be used to define the value for matching B's id:
rule qualify_A
when
B( $id: id, windowStart == 0 ) // the dummy, defines id
accumulate( B( id == $id, $ws: windowStart ); $mws: max( $ws ) )
$a: A( timestamp > $mws )
then
...process $a...
end

Related

Prettify a CodeEffect rule clause automatically

Is there a way to prettify the rule clause automatically?
For example, automatically format a rule from this:
Check if A is equal to B or ( B contains abc and C is not equal to A ) or ( C has no value and ( A starts with xyz or A doesn't end with opq ))
To this:
Check if
A is equal to B
or ( B contains abc
and C is not equal to A
)
or ( C has no value
and ( A starts with xyz
or A doesn't end with opq
)
)
You can't do rule formatting automatically in the current version. But rule authors can use the included keyboard and gestures support; it's quite intuitive and easy to use. Details can be found here

Grouping + aggregation of itab with table comprehensions

Rather typical task but I'm stuck on doing it in a beautiful way.
For example, I need to find the last shipment for each vendor, i.e. to find delivery with the max date for the each vendor
VENDOR DELIVERY DATE
10 00055 01/01/2019
20 00070 01/19/2019
20 00088 01/20/2019
20 00120 11/22/2019
40 00150 04/01/2019
40 00200 04/10/2019
The result table to be populated
VENDOR DELIVERY DATE
10 00055 01/01/2019
20 00120 11/22/2019
40 00200 04/10/2019
I implemented this in a following way, via DESCENDING, which I find very ugly
LOOP AT itab ASSIGNING <wa> GROUP BY ( ven_no = <wa>-ven_no ) REFERENCE INTO DATA(vendor).
LOOP AT GROUP vendor ASSIGNING <ven> GROUP BY ( date = <vendor>-date ) DESCENDING.
CHECK NOT line_exists( it_vend_max[ ven_no = <ven>-ven_no ] ).
it_vend_max = VALUE #( BASE it_vend_max ( <ven> ) ).
ENDLOOP.
ENDLOOP.
Is there more elegant way to do this?
I also tried REDUCE
result = REDUCE #( vend_line = value ty_s_vend()
MEMBERS = VALUE ty_t_vend( )
FOR GROUPS <group_key> OF <wa> IN itab
GROUP BY ( key = <wa>-ven_no count = GROUP SIZE
ASCENDING
NEXT vend_line = VALUE #(
ven_no = <wa>-ven_no
date = REDUCE i( INIT max = 0
FOR m IN GROUP <group_key>
NEXT max = nmax( val1 = m-date
val2 = <wa>-date ) )
deliv_no = <wa>-deliv_no
MEMBERS = VALUE ty_s_vend( FOR m IN GROUP <group_key> ( m ) ) ).
but REDUCE selects max date from the whole table and it selects only flat structure, which is not what I want. However, in ABAP examples I saw samples where table-to-table reductions are also possible. Am I wrong?
Another thing I tried is finding uniques with WITHOUT MEMBERS but this syntax doesn't work:
it_vend_max = VALUE ty_t_vend( FOR GROUPS value OF <line> IN itab
GROUP BY ( <line>-ven_no <line>-ship_no )
WITHOUT MEMBERS ( lifnr = value
date = nmax( val1 = <line>-date
val2 = value-date ) ) ).
Any suggestion of what is wrong here or own elegant solution is appreciated.
If not too complex, I think it's best to use one construction expression, which shows that the goal of the expression is to initialize one variable and nothing else.
The best I could do to be the most performing and the shortest possible, but I can't make it elegant:
TYPES ty_ref_s_vend TYPE REF TO ty_s_vend.
result = VALUE ty_t_vend(
FOR GROUPS <group_key> OF <wa> IN itab
GROUP BY ( ven_no = <wa>-ven_no ) ASCENDING
LET max2 = REDUCE #(
INIT max TYPE ty_ref_s_vend
FOR <m> IN GROUP <group_key>
NEXT max = COND #( WHEN max IS NOT BOUND
OR <m>-date > max->*-date
THEN REF #( <m> ) ELSE max ) )
IN ( max2->* ) ).
As you can see I use a data reference (aux_ref_s_vend2) for a better performance, to point to the line which has the most recent date. It's theoretically faster than copying the bytes of the whole line, but it's less readable. If you don't have a huge table, there won't be a big difference between using an auxiliary data reference or an auxiliary data object.
PS: I could not test it because the question does not provide a MCVE.
Here is another solution if you really want to use REDUCE in the primary constructor expression (but it's not needed):
result = REDUCE ty_t_vend(
INIT vend_lines TYPE ty_t_vend
FOR GROUPS <group_key> OF <wa> IN itab
GROUP BY ( ven_no = <wa>-ven_no ) ASCENDING
NEXT vend_lines = VALUE #(
LET max2 = REDUCE ty_ref_s_vend(
INIT max TYPE ty_ref_s_vend
FOR <m> IN GROUP <group_key>
NEXT max = COND #( WHEN max IS NOT BOUND
OR <m>-date > max->*-date
THEN REF #( <m> ) ELSE max ) )
IN BASE vend_lines
( max2->* ) ) ).
what do you mean by elegant solution? Using GROUP or REDUCE with the "new" abap syntax is not making it elegant in any way, at least for me...
For me, coding that is easily understandable for everyone is elegant:
SORT itab BY vendor date DESCENDING.
DELETE ADJACENT DUPLICATES from itab COMPARING vendor.
Or if the example is more complex, a simple LOOP AT with IF or AT in it APPENDING aggregated lines to a new itab, will also solve it. Example here.

Drools: How to get return values from Condition in Decision tables?

I have a Decision table in Drools and I'm trying to retrieve the return values of the Conditions (columns) that evaluates to see if a particular Rule (rows) is executed. May I know how if it's possible? Please find below for a simple example of my problem.
Condition 1 | Condition 2 | Condition 3 | Condition 4
Age < 60 Employed=Yes Owns a house=Yes Single=Yes
Rule 1: YES YES
Rule 2: YES NO YES
Rule 3: NO YES
Let's say if Rule 2 should be ideally executed and yet Rule 1 is executed, I would like to know the reason why Rule 1 was executed by obtaining the return values of the Condition 1 and 3 (whether it is true or false). Is there a way to do so?
The DRL rule for your Rule 1 would be something like
rule "Rule 1"
when
Person( $age: age < 60, $owner: owner == true )
then
...( $age, $owner )...
end
and on the right hand side you have binding variables $age and $owner set to the actual values as contained in the Person fact. You can use Java code in an ACTION column in a decision table to do whatever you want with those values.
Edit If you need the values for the negative case, you'll have to use another rule
rule "Rule 2"
when
Person( $age: age, $owner: owner,
$age >= 60 || $owner == false )
then
another_action...( $age, $owner )...
end
Most likely, you'll need to do some other action anyway.
Of course, individual rules for all four cases are also possible. Note that decision tables let you write rules for all four cases TT, TF, FT, FF. You can combine the action cells so you can define the action for TF FT FF in a single cell.

Sorting a list of objects using Drools rules engine

I am trying to sort a list of objects using a set of rules defined in drools rules engine.
The sample object structure is as follows
public class A {
String name;
Date createdDate;
}
I want to
Define a set of rules for sorting a list of objects .
Ex : rule 1 : "Sort objects using name ascending"
       rule 2 : "Sort objects using createdDate descending"
Define the order which the rules needs to be executed .
Ex : set order 1 to rule 1
       set order 2 to rule 2
So the objects will be sorted by name ascending and createdDate descending.
Can I achieve this using the drools engine ?
I thought of using the compareTo() for the sorting but since the sorting criteria can be changed
at runtime the logic is becoming complex and hard to maintain.
Thanks,
Kolitha.
Drools does not sort objects the way quicksort or some similar sorting algorithm rearranges objects within an array or some other aggregate. What you can do is to have a rule fire, repeatedly, once for each fact from a set of facts (such as your class A objects) and with constraints guaranteeing this happening in a certain order. Also, you need to keep track of
facts that have already been processed.
Therefore, the question to be answered first is: why do you need the objects in a certain order?
If the facts need to be processed in this order, you don't have to sort them in the ususual sense of the word, and the aforementioned rule would be sufficient:
declare ListOfA
listOfA: List
end
rule noListOfA
when
not ListOfA()
then
ListOfA loa = new ListOfA();
loa.setListOfA( new ArrayList() );
insert( loa );
end
rule sortA
when
$a: A( $name: name, $createdDate: createdDate )
$loa: ListOfA( $listOfA: listOfA not contains $a )
not A( this != $a, this not memberOf $listOfA,
name < $name ||
name == $name && createdDate > $createdDate )
then
System.out.println( $a );
modify( $loa ){ getListOfA().add( $a ) }
end
This is the way to sort in drools.
rule "Rule 03"
when
$number : Number( )
not Number( intValue < $number.intValue )
then
System.out.println("Number found with value: " + $number.intValue() );
retract( $number );
end

SQL Order By NEWID in a CASE statement

I'm trying to figure out how to involve NEWID() (so I can return the results in a random order) in a CASE statement with in the ORDER BY clause, like so:
ORDER BY CASE WHEN #RankingMethod = 1 THEN intFoo ELSE NEWID() END DESC
Obviously this doesn't work, as it throws "Operand type clash: uniqueidentifier is incompatible with int"
Is there a way to construct this ORDER BY to have it either sort randomly or by a specified column?
You could do
ORDER BY CASE WHEN #RankingMethod = 1 THEN intFoo ELSE -1 END DESC, newid()
If needed, choose another "magic value" instead of -1