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
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.
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.
I'm new in Grails. I have a problem with generation association many to one and one to many between two tables. I'm using postgresql database.
Employee.groovy
class Employee {
String firstName
String lastName
int hoursLimit
Contact contact
Account account
Unit unit
char isBoss
static hasMany = [positionTypes:PositionType, employeePlans: EmployeePlan]
}
EmployeePlan.groovy
class EmployeePlan {
AcademicYear academicYear
HourType hourType
int hours
float weightOfSubject
Employee employee
static belongsTo = [SubjectPlan]
}
I'd like to have access from employee to list of employeePlans and access from EmployeePlan to Employee instance. Unfortunately GORM generates only two tables Employee and EmployeePlan with employee_id. I don't have third table which should have two columns employee_id and employee_plan_id. Could you help me ?
I think your setup is correct, as you write from Employee class you can access to a collection of EmployeePlan (take care, that if you don't explicitly define EmployeePlan like a List, it will be a Set by default) and from EmployeePlan you can access Employee.
If you need List, you can define it like that:
class Employee {
String firstName
String lastName
int hoursLimit
Contact contact
Account account
Unit unit
char isBoss
//explicitly define List
List<EmployeePlan> employeePlans
static hasMany = [positionTypes:PositionType, employeePlans: EmployeePlan]
}
But back to your question. You'd like to have join table between Employee and employeePlan, but why? Its not necessary, since you have bidirectional mapping with sets (unordered), grails will not create a join table. Can you explain why do you need it? In grails the references will be auto-populated, so I don't see any issue here.
If need to preserve order of employeePlans, then define it as List, shown above, and grails will create a join table with corresponding indexes.
have you read the ref-doc? it gives you the answer immediately:
class Person {
String firstName
static hasMany = [addresses: Address]
static mapping = {
table 'people'
firstName column: 'First_Name'
addresses joinTable: [name: 'Person_Addresses',
key: 'Person_Id',
column: 'Address_Id']
}
}
I'm very new to JPA (based on eclipselink) so be patient with me and my question. I found JPA is an elegant way to handle simple DB statements. But, now I'd like to do some more complicated task.
So let me explain what I want: I've got a table holding person data (lets say the persons name). In an other table I'd like to keep address lists. Because a person way part of several adresslists and one adress list may keep several persons I need an ref-table jo join these collections.
So my DB-tables look like this:
SQL (not exact DDL syntax but it helps to understand!) --without constraints etc
--the person keeps data of persons
CREATE TABLE a_person
(
oid serial PrimaryKey NOT NULL,
vorname character varying(50),
nachname character varying(50) NOT NULL
--
)
--the adresslist is quite simple: just a name of the list is storred
CREATE TABLE a_adressliste (
(
oid serial PrimaryKey NOT NULL,
label character varying(25) NOT NULL
--<more collumns here>
)
--the table refering the list the the containing persons
CREATE TABLE a_listen_person
(
fk_adressliste bigint PrimaryKey NOT NULL, --ref to a_person.oid
fk_person bigint PrimaryKey NOT NULL, --ref to a_adressliste.oid
)
Having this structure and some data in the DB it's easy to select the adress lists using the following SQL statement:
select * from a_person p where p.oid in (
select
lp.fk_person
from
a_listen_person lp, a_adresssliste al
where
lp.fk_adressliste = al.oid AND al.label = {LISTE_LABEL}
)
Now, According tho the structure within the DB, I've got the corresponding JAVA POJO's to these tables (I've skipped the annotations to keep my code a little shorter)
JAVA
public class Person implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Basic(optional = false)
#Column(name = "oid")
private Integer oid;
private String vornmae;
private String nachname;
//getter; setter and the other missing stuff...
}
public class Adressliste implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Basic(optional = false)
#Column(name = "oid")
private Integer oid;
private String label;
}
public class ListenPerson implements Serializable {
#EmbeddedId
private ListenPersonPK listenPerson;//build from Person.oid and Adressliste.oid
private Person person;
private Adressliste adresssliste;
//getter; setter and the other missing stuff...
}
Now I've written a finder method in JAVA where I use a CriteriaBuilder to filter the entities by several attributes (according to the Person POJO). But I did not manage to select the Person according to a given list name.
By now my method looks like this:
public TypedQuery<Person> prepareQueryFiltered(Filter filter) {
TypedQuery<Person> retVal;
EntityManager em = this.getEntityManager();
CriteriaBuilder builder = em.getCriteriaBuilder();
CriteriaQuery<Person> query = builder.createQuery(Person.class);
Root<Person> cust = query.from(Person.class);
query.select(cust);
List<Predicate> predicateList = new ArrayList<Predicate>();
Predicate predicate;
if (null != filter.getVorname()) {
predicate = builder.like(
builder.lower(cust.<String>get("vorname")),
"%" + filter.getVorname().toLowerCase() + "%");
predicateList.add(predicate);
}
if (null != filter.getNachname()) {
predicate = builder.like(
builder.lower(cust.<String>get("nachname")),
"%" + filter.getNachname().toLowerCase() + "%");
predicateList.add(predicate);
}
//some more filtered attributes ...
query.where(predicateList.toArray(new Predicate[predicateList.size()]));
retVal = em.createQuery(query);
return retVal;
}
As you can imagine the filter attribute keeps all the data to filter my entities with. But how does the code look like if I'd like to get all the person entities belonging to a given list name?
I started to use 'Subquery's but I did not get the correct syntax. Could you please give me some hints?
For examples of sub-queries in the Criteria API see,
http://en.wikibooks.org/wiki/Java_Persistence/Criteria#Subquery
Also, I don't think you need a sub-query, a simple join should work.
Select p from Person p, ListenPerson l where l.person = p and l.adresssliste.label = :label
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.