Get the intersection of two sets in HQL - grails

I'm having some trouble with HQL.
I'd like to do a rather complex query, I have the following Domain-Object (simplified):
class SomeObject {
Set<SomeOtherObject> otherObjects
}
I now want to get all objects which have contain one or more of a specified list of other objects. Additionally I want also have a blacklist which otherObjects the object must not contain.
I made it this far, I can specify only one blacklist-item and a list of "allowed" otherObjects:
select o from Object as o
join o.otherObjects as otherObject
where
otherObject in :allowedotherobjects
and :excludedotherobject not in elements(o.otherObjects)
Basically I want something like this (which is not possible):
select o from Object as o
join o.otherObjects as otherObject
where
otherObject in :allowedotherobjects
and elements(:excludedotherobjects) not in elements(o.otherObjects)
btw this is a grails project I'm working on, but I'd like to solve this problem in HQL.
Any ideas?

Try this:
select o from Object as o
join o.otherObjects as otherObject
where
otherObject in :allowedotherobjects
and otherObject not in :excludedotherobjects

Related

Using symbols to map after plucking from joined tables with same column names

Currently, I have joined multiple tables using the join method, and I need to pluck out several columns, which I need to map into something else. Here's what I mean:
A.join( ... long sql statements involving model B and C...)
.pluck("A.id", "A.name", "B.id", "B.name" ...) # you get the idea
.map( |result|
# Then to use the various attributes, I was using result[0] to access A.id and so on
I was wondering is it possible to convert my attributes in my pluck to symbols like :A_id or :B_name? The reason I have to use "table_name.attribute" is due to the tables having columsn with the same name. If possible I was looking for:
A.join( ... long sql statements involving model B and C...)
.pluck(A_id, A_name, B.id, B_name ...)
.map( |A_id, A_name, B.id, B_name ...| ...)
Symbols would make it easier so that when I map, I do not need to use indexing in order to access my attributes? For example, I can straight up use :A_id instead of result[0] in the above example.
Would really help with readability since I'm plucking quite a lot of attributes and my join is pretty big (so there's plenty of columns with the same name), and it definitely looks messy with result[0] to result[10] all over in my map function.
Thanks in advance!!
.pluck returns an array and it's a bit difficult to work with for your scenario, but a combination of select and AS (alias_name) does the trick.
A.join( ... long sql statements involving model B and C...)
.select("A.id AS AID", "A.name AS ANAME", "B.id AS BID", "B.name AS BNAME" ...)
.each { |result| p result.AID ...}
The difference between pluck and select, is that select returns an array of A objects having those aliases defined in select as attributes.

Grails 3 - return list in query result from HQL query

I have a domain object:
class Business {
String name
List subUnits
static hasMany = [
subUnits : SubUnit,
]
}
I want to get name and subUnits using HQL, but I get an error
Exception: org.springframework.orm.hibernate4.HibernateQueryException: not an entity
when using:
List businesses = Business.executeQuery("select business.name, business.subUnits from Business as business")
Is there a way I can get subUnits returned in the result query result as a List using HQL? When I use a left join, the query result is a flattened List that duplicates name. The actual query is more complicated - this is a simplified version, so I can't just use Business.list().
I thought I should add it as an answer, since I been doing this sort of thing for a while and a lot of knowledge that I can share with others:
As per suggestion from Yariash above:
This is forward walking through a domain object vs grabbing info as a flat list (map). There is expense involved when having an entire object then asking it to loop through and return many relations vs having it all in one contained list
#anonymous1 that sounds correct with left join - you can take a look at 'group by name' added to end of your query. Alternatively when you have all the results you can use businesses.groupBy{it.name} (this is a cool groovy feature} take a look at the output of the groupBy to understand what it has done to the
But If you are attempting to grab the entire object and map it back then actually the cost is still very hefty and is probably as costly as the suggestion by Yariash and possibly worse.
List businesses = Business.executeQuery("select new map(business.name as name, su.field1 as field1, su.field2 as field2) from Business b left join b.subUnits su ")
The above is really what you should be trying to do, left joining then grabbing each of the inner elements of the hasMany as part of your over all map you are returning within that list.
then when you have your results
def groupedBusinesses=businesses.groupBy{it.name} where name was the main object from the main class that has the hasMany relation.
If you then look at you will see each name has its own list
groupedBusinesses: [name1: [ [field1,field2,field3], [field1,field2,field3] ]
you can now do
groupedBusinesses.get(name) to get entire list for that hasMany relation.
Enable SQL logging for above hql query then compare it to
List businesses = Business.executeQuery("select new map(b.name as name, su as subUnits) from Business b left join b.subUnits su ")
What you will see is that the 2nd query will generate huge SQL queries to get the data since it attempts to map entire entry per row.
I have tested this theory and it always tends to be around an entire page full of query if not maybe multiple pages of SQL query created from within HQL compared to a few lines of query created by first example.

How do I create a Grails query for a many-to-many using primitives?

I have a POGO we'll call Foo and it has a list of Bars. In the database, these are simple integers, but they're stored in a separate table (Foo_Bars)
class Foo {
String name
...
static hasMany = [bars:Integer]
...
}
So my question is, how do I create a query to find all Foos that with bars that are in a list. I know how I would write it in SQL.
SELECT * FROM foo, foo_bars
WHERE foo.id = foo_bars.foo_id
AND foo_bars.bars_integer IN (11, 15, 52)
But I figure there must be a simpler way, using GORM or HQL. How would I write this?
but what exactly you want to achieve? list of Foo's where bars is equal to (11,15,52), or ONE OF bars is in the list or the list of bars contain each of given list?
either way, I doubt you can do it in criteria or using a dynamic finder, I'm trying to do it in an unit test and nothing worked
I would go with creating another domain class like
class FooBar {
Foo foo
Integer integer
}
which would create exactly the same database table as you already have, and then querying would be much simpler

How to add to an existing value in a map in Cypher?

I want to replace the value of the 'Amount' key in a map (literal) with the sum of the existing 'Amount' value plus the new 'Amount' value such where both the 'type' and 'Price' match. The structure I have so far is:
WITH [{type:1, Orders:[{Price:10,Amount:100},{Price:11,Amount:200},{Price:12,Amount:300}]},
{type:2, Orders:[{Price:10,Amount:100},{Price:11,Amount:200},{Price:12,Amount:300}]},
{type:3, Orders:[{Price:10,Amount:100},{Price:11,Amount:200},{Price:12,Amount:300}]}] as ExistingOrders,
{type:2, Order:{Price:11,Amount:50}} as NewOrder
(I'm trying to get it to:)
RETURN [{type:1, Orders:[{Price:10,Amount:100},{Price:11,Amount:200},{Price:12,Amount:300}]},
{type:2, Orders:[{Price:10,Amount:100},{Price:11,Amount:250},{Price:12,Amount:300}]},
{type:3, Orders:[{Price:10,Amount:100},{Price:11,Amount:200},{Price:12,Amount:300}]}] as CombinedOrders
If there is no existing NewOrder.type and NewOrder.Price then it should obviously insert the new record rather than add it together.
Sorry, this is possibly really straight forward, but I'm not very good at this yet.
thanks
Edit:
I should add, that I have been able to get this working for a simpler map structure as such:
WITH [{type:1, Amount:100},{type:2, Amount:200},{type:3, Amount:300}] as ExistingOrders,
{type:2, Amount:50} as NewValue
RETURN reduce(map=filter(p in ExistingOrders where not p.type=NewValue.type),x in [(filter(p2 in ExistingOrders where p2.type=NewValue.type)[0])]|CASE x WHEN null THEN NewValue ELSE {type:x.type,Amount:x.Amount+NewValue.Amount} END+map) as CombinedOrders
But I'm struggling I think because of the Orders[array] in my first example.
I believe you are just trying to update the value of the appropriate Amount in ExistingOrders.
The following query is legal Cypher, and should normally work:
WITH ExistingOrders, NewOrder, [x IN ExistingOrders WHERE x.type = NewOrder.type | x.Orders] AS eo
FOREACH (y IN eo |
SET y.Amount = y.Amount + CASE WHEN y.Price = NewOrder.Order.Price THEN NewOrder.Order.Amount ELSE 0 END
)
However, the above query produces a (somewhat) funny ThisShouldNotHappenError error with the message:
Developer: Stefan claims that: This should be a node or a relationship
What the message is trying to say (in obtuse fashion) is that you are not using the neo4j DB in the right way. Your properties are way too complicated, and should be separated out into nodes and relationships.
So, I will a proposed data model that does just that. Here is how you can create nodes and relationships that represent the same data as ExistingOrders:
CREATE (t1:Type {id:1}), (t2:Type {id:2}), (t3:Type {id:3}),
(t1)-[:HAS_ORDER]->(:Order {Price:10,Amount:100}),
(t1)-[:HAS_ORDER]->(:Order {Price:11,Amount:200}),
(t1)-[:HAS_ORDER]->(:Order {Price:12,Amount:300}),
(t2)-[:HAS_ORDER]->(:Order {Price:10,Amount:100}),
(t2)-[:HAS_ORDER]->(:Order {Price:11,Amount:200}),
(t2)-[:HAS_ORDER]->(:Order {Price:12,Amount:300}),
(t3)-[:HAS_ORDER]->(:Order {Price:10,Amount:100}),
(t3)-[:HAS_ORDER]->(:Order {Price:11,Amount:200}),
(t3)-[:HAS_ORDER]->(:Order {Price:12,Amount:300});
And here is a query that will update the correct Amount:
WITH {type:2, Order:{Price:11,Amount:50}} as NewOrder
MATCH (t:Type)-[:HAS_ORDER]->(o:Order)
WHERE t.id = NewOrder.type AND o.Price = NewOrder.Order.Price
SET o.Amount = o.Amount + NewOrder.Order.Amount
RETURN t.id, o.Price, o.Amount;
There's two parts to your question - one with a simple answer, and a second part that doesn't make sense. Let me take the simple one first!
As far as I can tell, it seems you're asking how to concatenate a new map on to a collection of maps. So, how to add a new item in an array. Just use + like this simple example:
return [{item:1}, {item:2}] + [{item:3}];
Note that the single item we're adding at the end isn't a map, but a collection with only one item.
So for your query:
RETURN [
{type:1, Orders:[{Price:10,Amount:100},
{Price:11,Amount:200},
{Price:12,Amount:300}]},
{type:2, Orders:[{Price:10,Amount:100},
{Price:11,Amount:**250**},
{Price:12,Amount:300}]}]
+
[{type:3, Orders:[{Price:10,Amount:100},
{Price:11,Amount:200},{Price:12,Amount:300}]}]
as **CombinedOrders**
Should do the trick.
Or you could maybe do it a bit cleaner, like this:
WITH [{type:1, Orders:[{Price:10,Amount:100},{Price:11,Amount:200},{Price:12,Amount:300}]},
{type:2, Orders:[{Price:10,Amount:100},{Price:11,Amount:200},{Price:12,Amount:300}]},
{type:3, Orders:[{Price:10,Amount:100},{Price:11,Amount:200},{Price:12,Amount:300}]}] as ExistingOrders,
{type:2, Order:{Price:11,Amount:50}} as NewOrder
RETURN ExistingOrders + [NewOrder];
OK now for the part that doesn't make sense. In your example, it looks like you want to modify the map inside of the collection. But you have two {type:2} maps in there, and you're looking to merge them into something with one resulting {type:3} map in the output that you're asking for. If you need to deconflict map entries and change what the map entry ought to be, it might be that cypher isn't your best choice for that kind of query.
I figured it out:
WITH [{type:1, Orders:[{Price:10,Amount:100},{Price:11,Amount:200},Price:12,Amount:300}]},{type:2, Orders:[{Price:10,Amount:100},{Price:11,Amount:200},{Price:12,Amount:300}]},{type:3, Orders:[{Price:10,Amount:100},{Price:11,Amount:200},{Price:12,Amount:300}]}] as ExistingOrders,{type:2, Orders:[{Price:11,Amount:50}]} as NewOrder
RETURN
reduce(map=filter(p in ExistingOrders where not p.type=NewOrder.type),
x in [(filter(p2 in ExistingOrders where p2.type=NewOrder.type)[0])]|
CASE x
WHEN null THEN NewOrder
ELSE {type:x.type, Orders:[
reduce(map2=filter(p3 in x.Orders where not (p3.Price=(NewOrder.Orders[0]).Price)),
x2 in [filter(p4 in x.Orders where p4.Price=(NewOrder.Orders[0]).Price)[0]]|
CASE x2
WHEN null THEN NewOrder.Orders[0]
ELSE {Price:x2.Price, Amount:x2.Amount+(NewOrder.Orders[0]).Amount}
END+map2 )]} END+map) as CombinedOrders
...using nested Reduce functions.
So, to start with it combines a list of orders without matching type, with a list of those orders (actually, just one) with a matching type. For those latter ExistingOrders (with type that matches the NewOrder) it does a similar thing with Price in the nested reduce function and combines non-matching Prices with matching Prices, adding the Amount in the latter case.

grails gorm hasMany query based on select options from a gsp page

I have the following scenario,
Domain class A which hasMany B's
Domain class B which hasMany C's and belongsTo A
Domain class C which belongsTo B
Class E {
A a
B b
C c
}
Class C {
String name
}
I want to query E values i.e get a list of some E property eg in this case c.name based on values selected by a user in a select box, i.e user selects A and B from multiple select boxes and based on this criteria, a list of names is obtained.
in other words i want to find all names in c which satisfy condition set by a and b
have tried to figure out the GORM query to no avail.
thanks
I solved this by using raw SQL joins. Not really certain if this was the best way but it worked for me.
I got the parameters from A and B, ie
def fromA = params.someCriteriaValueInA
def fromB = params.someCriteriaValueInB
Please note that these are fetched from a gsp. Also, fromB values would be loaded based on fromA values, using chained selects.
In the grails controller/service ...
def db = new Sql(dataSource)
def result = db.rows("your sql to go here")
Then you can do whatever you want with the results, for example manipulate it in a template
render(template: "myResults", model:[result :result])
Dont forget to inject the dataSource bean in your controller/service, and also doing the necessary import
import groovy.sql.Sql
Please note that this involved traversing many domain class instances, and for those who like raw SQL this would seem easier.Would appreciate a different approach, maybe using GORM's criteria.

Resources