Selecting a set of three distinct objects - drools

Drools Planner used this to select two distinct objects, ensuring that a rule did not fire twice for a given pair of objects. If o1 and o2 made a match, it fired only once, not twice for (o1,o2) and (o2,01).
when
$stp1 : SubjectTeacherPeriod( $slno : slNo,
$id : id,
$period_day_order : period.dayOrder
)
$stp2 : SubjectTeacherPeriod( slNo > $slno,
id == $id,
period.dayOrder == $period_day_order
)
How do I select a set of three distinct objects? What is a good selection criteria for that?

Same approach should work:
$f1 : SomeFact( ... )
$f2 : SomeFact( id > $f1.id, ... )
$f3 : SomeFact( id > $f2.id, ... )

Related

DBIx::Class - get all relationship that was used as a condition using prefetch?

Here are three tables: product, model, and product_model that maps products and models in N:M relationship.
product product_model model
id name product_id model_id id name
------------ ------------------- ----------
p1 Product 1 p1 m1 m1 Model 1
p2 Product 2 p2 m1 m2 Model 2
... p2 m2
What I want to do: Find all products that support Model 2(eg. product 2). Then, for each product, show the list of model_ids that the product supports(product 2 => [ m1,m2 ])
This was my first try. I needed N more queries to search model_ids for each product.
# 1 query for searching products
my #products = $schema->resultset('Product')->search(
{ 'product_models.model_id' => 'm2' },
{ 'join' => 'product_model' },
)
# N queries for searching product_models for each product
foreach my $product ( #products ) {
my #model_ids = map { $_->model_id } $product->product_models;
# #model_ids = ( 'm1', 'm2' ) for p2
}
I looked for a way to get the result using only one query. Replacing join with prefetch didn't work.
my #products = $schema->resultset('Product')->search(
{ 'product_models.model_id' => 'm2' },
{ 'prefetch' => 'product_model' }, # here
)
# no additional queries, but...
foreach my $product ( #products ) {
my #model_ids = map { $_->model_id } $product->product_models;
# now, #model_ids contains only ( `m2` )
}
Next, I tried "prefetch same table twice":
my #products = $schema->resultset('Product')->search(
{ 'product_models.model_id' => 'm2' },
{ 'prefetch' => [ 'product_models', 'product_models' ] },
);
foreach my $product ( #products ) {
my #model_ids = map { $_->model_id } $product->product_models;
}
It seemed that I succeeded. Only one query was executed and I got all model IDs from it.
However I wasn't so sure that this is the right(?) way. Is this a correct approach?
For example, if I used join instead of prefetching, Product 2 appears in the loop twice. I understand that, because the joined table is like:
id name p_m.p_id p_m.m_id p_m_2.p_id p_m_2.m_id
p2 Product 2 p2 m2 p2 m1
p2 Product 2 p2 m2 p2 m2 -- Product 2, one more time
Why does Product 2 appear only once when I use prefetch?
The resulting queries are almost same, except the difference of SELECT fields:
SELECT "me"."id", "me"."name",
"product_models"."product_id", "product_models"."model_id", -- only in prefetch
"product_models_2"."product_id", "product_models_2"."model_id" --
FROM "product" "me"
LEFT JOIN "product_model" "product_models"
ON "product_models"."product_id" = "me"."id"
LEFT JOIN "product_model" "product_models_2"
ON "product_models_2"."product_id" = "me"."id"
WHERE "product_models"."model_id" = 'm2'
If you have the correct relationships in your schema, this is possible with a single query. But it's tricky. Let's assume your database looks like this:
CREATE TABLE product
(`id` VARCHAR(2) PRIMARY KEY, `name` VARCHAR(9))
;
INSERT INTO product
(`id`, `name`) VALUES
('p1', 'Product 1'),
('p2', 'Product 2')
;
CREATE TABLE product_model (
`product_id` VARCHAR(2),
`model_id` VARCHAR(2),
PRIMARY KEY (product_id, model_id),
FOREIGN KEY(product_id) REFERENCES product(id),
FOREIGN KEY(model_id) REFERENCES model(id)
)
;
INSERT INTO product_model
(`product_id`, `model_id`) VALUES
('p1', 'm1'),
('p2', 'm1'),
('p2', 'm2')
;
CREATE TABLE model
(`id` VARCHAR(2) PRIMARY KEY, `name` VARCHAR(7))
;
INSERT INTO model
(`id`, `name`) VALUES
('m1', 'Model 1'),
('m2', 'Model 2')
;
This is essentially your DB from the question. I added primary keys and foreign keys. You probably have those anyway.
We can now create a schema from that. I've written a simple program that uses DBIx::Class::Schema::Loader to do that. It creates an SQLite database on the fly. (If no-one has put this on CPAN, I will).
The SQL from above will go in the __DATA__ section.
use strict;
use warnings;
use DBIx::Class::Schema::Loader qw/ make_schema_at /;
# create db
unlink 'foo.db';
open my $fh, '|-', 'sqlite3 foo.db' or die $!;
print $fh do { local $/; <DATA> };
close $fh;
$ENV{SCHEMA_LOADER_BACKCOMPAT} = 1;
# create schema
my $dsn = 'dbi:SQLite:foo.db';
make_schema_at(
'DB',
{
# debug => 1,
},
[ $dsn, 'sqlite', '', ],
);
$ENV{DBIC_TRACE} = 1;
# connect schema
my $schema = DB->connect($dsn);
# query goes here
__DATA__
# SQL from above
Now that we have that, we can concentrate on the query. At first this will look scary, but I'll try to explain.
my $rs = $schema->resultset('Product')->search(
{ 'product_models.model_id' => 'm2' },
{
'prefetch' => {
product_models => {
product_id => {
product_models => 'model_id'
}
}
}
},
);
while ( my $product = $rs->next ) {
foreach my $product_model ( $product->product_models->all ) {
my #models;
foreach my $supported_model ( $product_model->product_id->product_models->all ) {
push #models, $supported_model->model_id->id;
}
printf "%s: %s\n", $product->id, join ', ', #models;
}
}
The prefetch means join on this relation, and keep the data around for later. So to get all models for your product, we have to write
# 1 2
{ prefetch => { product_models => 'product_id' } }
Where product_models is the N:M table, and product_id is the name of the relation to the Models table. The arrow => 1 is for the first join from Product to ProductModel. The 2 is for ProductModel back to every product that has the model m2. See the drawing of the ER model for an illustration.
Now we want to have all the ProductModels that this Product has. That's arrow 3.
# 1 2 3
{ prefetch => { product_models => { product_id => 'product_models' } } }
And finally, to get the Models for that N:M relation, we have to use the model_id relationshop with arrow 4.
{
'prefetch' => { # 1
product_models => { # 2
product_id => { # 3
product_models => 'model_id' # 4
}
}
}
},
Looking at the ER model drawing should make that clear. Remember that each of those joins is a LEFT OUTER join by default, so it will always fetch all the rows, without loosing anything. DBIC just takes care of that for you.
Now to access all of that, we need to iterate. DBIC gives us some tools to do that.
while ( my $product = $rs->next ) {
# 1
foreach my $product_model ( $product->product_models->all ) {
my #models;
# 2 3
foreach my $supported_model ( $product_model->product_id->product_models->all ) {
# 4
push #models, $supported_model->model_id->id;
}
printf "%s: %s\n", $product->id, join ', ', #models;
}
}
First we grab all the ProductModel entries (1). For each of those, we take the Product (2). There is always only one Product in every line, because that way we have a 1:N relation, so we can directly access it. This Product in turn has a ProductModel relation. That's 3. Because this is the N side, we need to take all of them and iterate. We then push the id of every Model (4) into our list of models for this product. After that, it's just printing.
Here's another way to look at it:
We could eliminate that last model_id in the prefetch, but then we'd have to use get_column('model_id') to get the ID. It would save us a join.
Now if we turn on DBIC_TRACE=1, we get this SQL statement:
SELECT me.id, me.name, product_models.product_id, product_models.model_id, product_id.id, product_id.name, product_models_2.product_id, product_models_2.model_id, model_id.id, model_id.name
FROM product me
LEFT JOIN product_model product_models ON product_models.product_id = me.id
LEFT JOIN product product_id ON product_id.id = product_models.product_id
LEFT JOIN product_model product_models_2 ON product_models_2.product_id = product_id.id
LEFT JOIN model model_id ON model_id.id = product_models_2.model_id
WHERE (product_models.model_id = 'm2')
ORDER BY me.id
If we run this against our DB, we have these rows:
p2|Product 2|p2|m2|p2|Product 2|p2|m1|m1|Model 1
p2|Product 2|p2|m2|p2|Product 2|p2|m2|m2|Model 2
Of course that's pretty useless if we do it manually, but DBIC's magic really helps us, because all the weird joining and combining is completely abstracted away, and we only need one single query to get all the data.

drools not exists in collection

I need to fire a rule if a collection does not have a specific object.
AuditAssignment is available as problem fact.
AuditAssignment has a property "requiredSkill"
Audit Assignment has a property "auditor"
Auditor object has a list of "qualifications" which is a collection of "requiredSkill "
Now , I need to check , if the qualifications of the auditor in the auditassignment object has the requiredSkill
Below is sample rule which I tried but does not work .
rule "checkIfAuditSkillIsMatching"
when
$auditAssignment : AuditAssignment( $neededSkill : requiredSkill.getSkillCode())
$auditor : Auditor( $auditorSkills : qualifications)
not exists ( Skill ( skillCode == $neededSkill ) from $auditorSkills )
then
System.out.println( " **** " + $neededSkill);
scoreHolder.addHardConstraintMatch(kcontext, -1 );
end
I have tried the below one as well
rule "checkIfAuditSkillIsMatching"
when
$validAuditorCount : Number ( intValue < 1 ) from accumulate (
$auditor : Auditor( $auditorSkills: qualifications )
and exists AuditAssignment( auditor == $auditor ,
$auditorSkills.contains(requiredSkill) ) ,
count($auditor)
)
then
scoreHolder.addHardConstraintMatch(kcontext, -1 );
end
Here it is advisable to use a property method of Collection to obtain the logical value you need.
rule "checkIfAuditSkillIsMatching"
when
$auditAssignment: AuditAssignment( $neededSkill: requiredSkill.getSkillCode() )
$auditor: Auditor( $auditorSkills: qualifications,
! $auditorSkills.contains( $neededSkill ) )
then
//...no suitably qualified auditor
end
The below configuration worked
rule "checkIfAuditSkillIsMatching"
when
$auditAssignment : AuditAssignment( $neededSkill : requiredSkill ,
$assignedAuditor : auditor )
$auditor : Auditor( this == $assignedAuditor , !qualifications.contains($neededSkill) )
then
scoreHolder.addHardConstraintMatch(kcontext, -1 );
end

OrientDB Removing one result set from another using the difference() function

We are using version v.1.7-rc2 of OrientDB, embedded in our application, and I'm struggling to figure out a query for removing one set of results from another set of results.
For a simplified example, we have a class of type "A" which is organized in a directional hierarchy. The class has a "name" attribute defined as a string (referring to areas, regions, counties, cities, etc), and a "parent" edge defining a relationship from the child instances to the parent instances.
I was able to find the intersection of the result sets from the two sub-queries of my hierarchy using the instance() function:
select expand( $1 ) LET $2 = ( select from (traverse in('parent') from (select from A where name = 'Eastern')) where $depth > 0 and name like '%a%' ), $3 = ( select from (traverse in('parent') from (select from A where name = 'Eastern')) where $depth > 0 and name like '%o%' ), $1 = intersect( $2, $3 )
I thought I could accomplish the opposite effect if I used the difference() function:
select expand( $1 ) LET $2 = ( select from (traverse in('parent') from (select from A where name = 'Eastern')) where $depth > 0 and name like '%a%' ), $3 = ( select from (traverse in('parent') from (select from A where name = 'Eastern')) where $depth > 0 and name like '%o%' ), $1 = difference( $2, $3 )
but it returns zero records, when the sub queries for $2 and $3 run separately return record sets that overlap. What am I failing to understand? I've searched the forums and documentation, but haven't figured it out.
In the end, I want to take vertices found in one result set, and remove from it any vertices found in a second result set. I essentially want the analogous behavior of the SQL EXCEPT operator (https://en.wikipedia.org/wiki/Set_operations_%28SQL%29#EXCEPT_operator).
Any ideas or directions would be extremely helpful!
Regards,
Andrew

OrientDB SQL - traverse while keeping edges weight

Lets assume the following model in OrientDB graph:
I have a Profile vertex.
Profiles are connected with 2 edges: Liked and Commented. Both edges have a "value" field indicating the count of the action (or the "weight" of the edge).
So, if user A commented 3 times on posts by user B there will be a Commented edge from user A to user B with value = 3.
Now, say I want to get all the users that interacted with user B (either liked or commented), sorted by the weight of the interaction.
I can do that with the following SQL:
select * from (traverse out from
(select out, sum(value) as value from
(traverse * from (select from Profile where username="B") while $depth < 3)
where #class="Liked" or #class="Commented" group by out order by value desc)
while $depth < 2 ) where #class="Profile" )
But what if I want to know also the weight of the interaction? How do I propagate up the "value" while doing the last traverse?
Edit
According to the suggestion, a simplified version of this query will be:
select expand(out) from (
select out, sum(value) as value from (
select expand(inE("Liked", "Commented")) from Profile
where username="B"
) group by out order by value desc
)
But I still can't find a way to use LET to insert the value into the outer expanded object. $parent does not seem to point to the object that is expanded on the most outer select.
Edit 2
I'm Playing with $parent in every way I can think of. I don't see how you can use it in this case. Again - the problem I'm trying to solve is how to pass the sum(value) to the outer result set. I don't see a way of using LET for it when doing a GROUP BY, and I also don't see a way of using LET when the outer most select is doing an expand (since you can't do other projections together with expand).
Also, the results of using $current do not seem to be what is expected. For example, the following query:
select expand($v) from
(select from
(select expand(inE("Liked", "Commented")) from Profile where #rid=#11:0)
let $v = $current
)
Returns this:
{
"result" : [{
"#type" : "d",
"#rid" : "#14:4",
"#version" : 2,
"#class" : "Commented",
"value" : 1,
"out" : "#11:165",
"in" : "#11:0"
}, {
"#type" : "d",
"#rid" : "#14:4",
"#version" : 2,
"#class" : "Commented",
"value" : 1,
"out" : "#11:165",
"in" : "#11:0"
}, {
"#type" : "d",
"#rid" : "#14:4",
"#version" : 2,
"#class" : "Commented",
"value" : 1,
"out" : "#11:165",
"in" : "#11:0"
}
]
}
The same node over and over again, instead of all the edges, which is what I would expect.
I see you're using an old version of OrientDB. With more recent versions you can simplify it by. Example: original query:
select * from (
traverse out from (
select out, sum(value) as value from (
traverse * from (
select from Profile where username="B"
) while $depth < 3
) where #class="Liked" or #class="Commented" group by out order by value desc
) while $depth < 2
) where #class="Profile" )
You could skip some step by using out()/in()/both() passing the Edge's labels/class like:
select expand( out(["Liked","Commented]) ) from Profile where username="B"
However to pass the value you can use variables with LET clause. Example:
select from XXX let $parent.a = value
In this way you set the variable "a" into the upper level context, but you could do also:
select from XXX let $parent.$parent.a = value
To set it 2 levels up.
I haven't tried this with a group by yet, but you should be able to group the result using a sub query. This works for me, where prop1, prop2, prop3, etc are properties of the vertices coming out of the edge (the columns resulting from select out() Profile where username="B")
select outV().prop1, outV().prop2, outV().prop3, value from (
select expand(inE("Liked", "Commented")) from Profile
where username="B"
) order by value desc

Deleted-table used in instead of update trigger

I got the following code ( no chance to ask the person who wrote it ) and don't see the need of the inner join on deleted. Do I miss something?
I thought, the deleted-table is used in instead-of-delete triggers. In instead-of-inserted triggers it's empty everytime, isn't it in instead-of-update triggers?
CREATE TRIGGER "someTrigger" ON "dbo"."someView"
INSTEAD OF UPDATE
AS
BEGIN
INSERT INTO "someOtherTable"
(
"id"
, "startTime"
, "endTime"
, "duration"
, "resourceId"
, "resourceLocked"
, "timeLocked"
, "groupPrefix"
)
SELECT
"id" = i."id"
, "startTime" = i."startTime"
, "endTime" = i."endTime"
, "duration" = ISNULL( i."duration", 0 )
, "resourceId" = i."resourceId"
, "resourceLocked" = ISNULL( i."resourceLocked", 0 )
, "timeLocked" = ISNULL( i."timeLocked", 0 )
, "groupPrefix" = N'gp'
FROM inserted AS i
INNER JOIN deleted AS d
ON d."id" = i."id"
WHERE ( i."jobType" != 3 )
OR ( i."jobType" = 3 AND i."startTime" != d."startTime" );
END;
In an update trigger, both inserted and deleted are populated. inserted contains the new row values, deleted contains the old row values, i.e. the values from before the UPDATE statement executed.
Here, it seems to be being used in the WHERE clause:
WHERE ( i."jobType" != 3 )
OR ( i."jobType" = 3 AND i."startTime" != d."startTime" );
So, if jobType is 3, we only do the insert if the startTime has been changed.
It should be noted that correlating the rows between inserted and deleted is only possible if at least one key's column(s) are not subject to update. Here, id appears to be a key. Hopefully that id column isn't subject to update or else this trigger may misbehave when such updates occur.