In drools I can do something like this
rule "rule1"
dialect "java"
no-loop true
when
$order: Order( $cust: customer )
Customer(id == "213123") from $cust
then
end
class Order {
private String areaCode;
private Customer customer;
}
class Customer {
private String id;
}
I want rule to identify if there are more than 3 different customers that ordered from same areaCode within an hour. Suppose a new order came in and I want to checkout if there is 3 or more orders from different customers to the same area within an hour.
rule "rule2"
dialect "java"
no-loop true
when
$order: Order( $cust: customer, $areaCode: areaCode)
Customer( $custId: id) from $cust
Set( size >= 3 ) from accumulate (
Order( $id: id, areaCode == $areaCode, customer.id != $custId ) over window:time( 1h ),
collectSet( $id ) )
then
end
Can I access customer.id the way that I use in rule 1 within from accumulate?
I'm a little unsure about what exactly you're trying to do in your example "rule3", but in general yes you can have a "from" clause inside of an accumulate.
Here's an example. Assume these models (getters and setters are implied but omitted for brevity):
class Student {
private String name;
private List<Course> courses;
}
class Course {
private Double currentGrade; // [0.0, 100.0]
}
Let's say we want to write a rule where we identify students who have 3 or more classes with a grade < 70.0.
rule "Students with three or more classes with less than a 70"
when
$student: Student($courses: courses != null)
$failingCourses: List( size >= 3 ) from accumulate (
$course: Course( currentGrade < 70.0 ) from $courses,
collectList( $course )
)
then
System.out.println("Student '" + $student.getName() + "' is failing " + $failingCourses.size() + " courses.");
end
In your accumulate you can use a 'from' clause to indicate the source of the objects you're accumulating. In my case it's a list, but you can use a window or temporal operations as well.
Related
I have the following classes, where there's a 1 - n relationship between Customer and Order, i.e. each Customer has many Orders
class Customer {
string identifier;
string country;
Collection orders;
}
class Order {
string identifier;
float amount;
}
class Report {
string identifier;
string country;
float amount;
}
I want to write the following in the form of one or more Drools decision tables.
For each Customer c
if c.country == US then
for each Order o
if o.amount > $10 then
create Report r
r.country = c.country
r.amount = o.amount
How possible is this using Drools decision tables?
When a Customer object meets the Customer condition, I then need to run each instance in the collection of Orders through the Order condition. If the order meets the condition, I need to create a report object that has values taken from the Customer and from the Order.
Drools will naturally iterate through collections.
This what your rule would look like in DRL:
rule "US Customer - Create Reports"
when
$customer: Customer( country == "US", $orders: orders != null )
$order: Order( amount > 10 ) from $orders
then
Report r = new Report();
r.country = $customer.country;
r.amount = $order.amount;
// TODO: do something with Report r here
end
This flattens naturally into a decision table in a very straight-forward fashion. You can even sub out the "US" (country) and 10 (amount) to variables.
I am solving an employee rostering problem. One of the constraints there is that Employee from each "type" should be present on every day. Type is defined as an enum.
I have right now configured this rule as follows:
rule "All employee types must be covered"
when
not Shift(employeeId != null, $employee: getEmployee(), $employee.getType() == "Developer")
then
scoreHolder.addHardConstraintMatch(kcontext, -100);
end
This works fine. However I would have to configure a similar rule for all possible employee types.
To generalize it, I tried this:
rule "All employee types must be covered"
when
$type: Constants.EmployeeType()
not Shift(employeeId != null, $employee: getEmployee(), $employee.getType() == $type.getValue())
then
scoreHolder.addHardConstraintMatch(kcontext, -100);
end
However, this rule doesn't get executed. Below is my enum defined in Constants file
public enum EmployeeType {
Developer("Developer"),
Manager("Manager");
private String value;
Cuisine(String value) {
this.value = value;
}
public String getValue() {
return value;
}
}
What am I doing wrong?
I guess the problem is that you are never inserting the enums in your session (they are not facts).
One way to solve it is to manually insert them:
for(EmployeeType type : Constants.EmployeeType.values()){
ksession.insert(type);
}
Another way is to make your rule fetch all the possible values from the enum:
rule "All employee types must be covered"
when
$type: Constants.EmployeeType() from Constants.EmployeeType.values()
not Shift(employeeId != null, $employee: getEmployee(), $employee.getType() == $type.getValue())
then
scoreHolder.addHardConstraintMatch(kcontext, -100);
end
Hope it helps,
I have student class which has list of departments associated.
public class Student {
private String name;
private String desc;
private List<Department> department = new ArrayList<Department>();
}
public class Department {
private String name;
private String desc;
}
I am trying write rule in Drools. Rule is like - If a student belong to certain departments then take an action. I am not able to figure out the right way to do it. One i had written below does not work. I understand it works if its List of String. Is there way to do the contains on custom object.
rule "Language"
when
$student : Student(department contains "English" && department contains "French")
then
System.out.println("Belongs to Language");
end
Code to invoke rules
Student student = new Student();
student.setName("John");
Department a1 = new Department();
a1.setName("English");
student.addDepartment(a1);
Department a2 = new Department();
a2.setName("French");
student.addDepartment(a2);
System.out.println("Student :" + student);
ksession.insert(student);
ksession.fireAllRules();
I am using Drools 6.2 version.
Looking for a String in a List<Department> isn't going to work. You'll have to look for a member where the name field is one of these two values.
rule "Language"
when
$student: Student( $dep: department )
Department( name == "French" || == "English",
this memberOf $dep )
then
System.out.println("Belongs to Language");
end
Edit: You need to insert the DEpartment objects as facts as well.
If the student has to have both languages, use
rule "Language"
when
$student: Student( $dep: department )
Department( name == "French", this memberOf $dep )
Department( name == "English", this memberOf $dep )
then
System.out.println("Belongs to Language");
end
I have a problem getting the following scenario to work. A student can take tests. A student have over time taken a few tests and got a score for each test. Each student entity have a list of tests that they have completed mapped as #OneToMany.
Now I want to select all students that have completed tests on a range of grouped criterions. I want for example to search for all students that have:
Group 1: Completed "Test 1" and got a score "between 75 and 100"
and/or
Group 2: Completed "Test 2" and got a score "between 50 and 80"
This is what I have so far but it does not do what I need (cannot search by multiple parameters meaning that I have to perform the query multiple times):
SELECT s FROM Student s JOIN s.tests t WHERE t.score BETWEEN :minScore AND :maxScore AND t.testName = :testName
Is there a way to use a single NamedQuery to achieve what I want? To retrieve all Students that have completed a test that matches at least one of the parameter groups above? I've been experimenting with joins but keep running into the wall.
I made a sample code skeleton below to illustrate what I'm trying to do.
#Entity
#NamedQueries({
#NamedQuery(name="Student.findStudentByParams", query="????????") // What should this query look like to satisfy the criteria? (see below for more detail)
})
public class Student {
// .. Some other variables that are not relevant for this example
#Id
private String name;
#OneToMany(fetch=FetchType.EAGER, mappedBy = "student")
private List<Test> tests;
// Setters and getters
}
#Entity
public class Test {
private double score;
private String testName;
// .. Some other variables that are not relevant for this example
#ManyToOne(cascade=CascadeType.ALL)
private Student student;
// Setters and getters
}
public class SearchParameters {
private double minScore;
private double maxScore;
private String testName;
public SearchParameters(String minScore, String maxScore, String testName) {
this.minScore = minScore;
this.maxScore = maxScore;
this.testName = testName;
}
// Setters and getters
}
public class MainClass {
public static List<Student> getStudents(List<SearchParameters> searchParams) {
// Database initialization stuff
// What should the query look like to find all students that match any of the combined requirements in the searchParams list?
// Is it possible to do in a single query or should i make multiple ones?
// What parameters should i set? Is it possible to put in the entire array and do some sort of join?
// Retrieve all students which matches any of these search parameters:
// Have either:
// Completed "Test 1" and got a score between 75 and 100
// and/or:
// Completed "Test 2" and got a score between 50 and 80
Query namedQuery = em.createNamedQuery("Student.findStudentByParams");
namedQuery.setParameter(??);
return (List<Student>)namedQuery.getResultList();
}
public static void main() {
List<SearchParams> searchParams = new ArrayList<SearchParams();
searchParams.add(new SearchParameters(75,100, "Test 1"));
searchParams.add(new SearchParameters(50,80, "Test 2"));
// Retrieve all students which matches any of these search parameters:
// Have either:
// Completed "Test 1" and got a score between 75 and 100
// and/or:
// Completed "Test 2" and got a score between 50 and 80
ArrayList<Student> students = getStudents(searchParams);
for(Student s: students) // Print all user that match the criteria
{
System.out.println("Name: " + s.getName());
}
}
}
You need to use Criteria Builder (and eventually the canonical Metamodel).
Try something like this (code not tested):
EntityManager em; // put here your EntityManager instance
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Student> cq = cb.createQuery(Student.class);
Root<Student> student = cq.from(Student.class);
Predicate predicate = cb.disjunction();
for (SearchParams param : searchParams) {
ListJoin<Student, Test> tests = student.join(Student_.tests);
Predicate tempPredicate1 = cb.equal(tests.get(Test_.testName), param.getTestName());
Predicate tempPredicate2 = cb.ge(tests.get(Test_.score), param.getMinScore());
Predicate tempPredicate3 = cb.le(tests.get(Test_.score), param.getMaxScore());
Predicate tempPredicate = cb.and(tempPredicate1, tempPredicate2, tempPredicate3);
predicate = cb.or(predicate, tempPredicate);
}
cq.where(predicate);
TypedQuery<Student> tq = em.createQuery(cq);
return tq.getResultList();
I don't see how it would be possible without composing the query dynamically. Consider using the Criteria API to create it.
I would design the query like this:
select s from Student s where
exists (select t.id from Test t where t.student.id = s.id and ...)
or
exists (select t.id from Test t where t.student.id = s.id and ...)
or
exists (...)
As you see, there's a repeating pattern, and all these subqueries are similar an are combined into a disjunction.
I have a drools question which has been troubling me for some time. I want to find out the min and max price from a list of Item objects (contains price) using accumulate. A Member object (which contails list of Item objects) is inserted which contains the list of Items.
groovy/java source pseudo code
-------------------------------
class Item {
BigDecimal price
}
class Member {
List<Item>> items
}
...
droolsStatefulSession.insert(member)
session.fireAllRules()
...
rule.drl
---------
rule "rule1"
when
member : Member ($itemList : items)
/*
*/
then
System.out.println("condition met...")
end
Now the questions is in the above rule is it possible to if so how do I find out the item with the minimum Price and maximum price using drools accumulate feature. I do not want to use an java/groovy utility functions.
I see the "collect" feature allows to use "from" and then a datasource. I wonder if "accumulate" is similar to collect.
No need to use accumulate, just do something like
Item($lowestPrice : price, $id : id)
not Item(price > $lowestPrice, id < $id)
That's if your Items are inserted into the working memory.
I'm new to the drool rule and I'm trying to solve this issue.
You can find out min and max price of item simply - you have to write some rules and need to add two variable in the Order class:
Class Order
{
private List<Item> itemList;
private int highPrice;
private int lowPrice;
}
Using the following rule you can calculate min and max value of an item:
package com.sample
import com.sample.DroolsTest.Message;
rule "rule1"
when
$order : Order($itemList:itemList)
$item:Item() from $order.itemList
then
insertLogical($item);
end
rule "highPriceRule"
when
$order : Order()
$item:Item($price:price,price>=$order.getHighPrice())
then
$order.setHighPrice($item.getPrice());
end
rule "lowPriceRule"
when
$order : Order()
$item:Item($price:price,price<=$order.getLowPrice()||$order.getLowPrice()==0)
then
$order.setLowPrice($item.getPrice());
end
rule "highPrice"
salience -1
when
$order : Order()
then
System.out.println( "higher Item Price is "+$order.getHighPrice());
end
rule "LowPrice"
salience -1
when
$order : Order()
then
System.out.println( "Lower Item Price is "+$order.getLowPrice());
end
in main method you have to write this code and run it
List<Item> items=new ArrayList<Item>();
Item item1=new Item();
item1.setPrice(10);
Item item2=new Item();
item2.setPrice(20);
Item item3=new Item();
item3.setPrice(10);
Item item4=new Item();
item4.setPrice(5);
items.add(item1);
items.add(item2);
items.add(item3);
items.add(item4);
Order order=new Order();
order.setItemList(items);
ksession.insert(order);
ksession.fireAllRules();
output:---
Lower Item Price is 5
higher Item Price is 20
As I'm new to the drool rule I would like to ask if this is the correct procedure? Or is there another way to solve this problem?