问题
I have this:
class Parent {
String name
static hasMany = [children: Child]
}
class Child {
String name
}
And I want to find all the parents that have a child named "Tom" and another one named "Sam".
id | parentName
---------------
1 | Peter
2 | Joe
3 | Ann
id | childName
---------------
1 | Tom
2 | Sam
idParent | idChild
---------------
1 | 1
2 | 2
3 | 1
3 | 2
In the example, it will be Ann.
I've tried this:
static namedQueries = {
findParents{nameList ->
children{
nameList.each{childName->
and{
ilike('nombre', '%'+childName+'%')
}
}
}
But this way I am searching only one child with Tom and Sam in his name. In the example, it will return nothing.
I have tried with 'in' 'name', ["Tom","Sam"]
instead ilike operator, but now I will get all parents with a child named "Tom" or a child named "Sam". In the example, it will return Peter, Joe and Ann
Any idea?
Thanks in advance!
回答1:
So I now understand and I recreated and made an answer for you
idParent | idChild
---------------
1 | 1
2 | 2
*3 | 1
*3 | 2
So it is really 3 or Ann that you are after right ?
Peter [test.Child : 1]
Joe [test.Child : 2]
Ann [test.Child : 1, test.Child : 2]
What you want is :
list is [[test.Parent : 3, test.Child : 2, test.Parent : 3, test.Child : 1]]
E2A Dec 2016 A much better way than all of the earlier versions
String query="""select new map(p.name as parentName,
case when (select count(*) from p.children as pc where pc.name in (:children)) >= 2 then 'true'
else 'false' end as childMatch) from Parent p
"""
def inputs=[:]
inputs.children=['Tom','Sam']
def listing = Parent.executeQuery(query,inputs,[readonly:true])
Produces:
println "list is $listing"
list is [[childMatch:false, parentName:Peter],
[childMatch:false, parentName:Joe],
[childMatch:true, parentName:Ann]]
Now if we simply changed:
def listing = Parent.executeQuery(query,inputs,[readonly:true])?\
.findAll{it.childMatch=='true'}
println "list is $listing"
list is [[childMatch:true, parentName:Ann]]
As you can see much less complication than earlier methods
Alternative to above but still not as good as above
You can also use in elements
which binds to real object rather than your join like below:
(:children) in elements(p.children)
But even with this method you will hit the issues like discussed below. The very first method on this answer is rather powerful, using the count you could use it as a form of weight to see how many records have 2 how many 1 how many 0 etc so lots more usages from it -
Earlier methods
I used hql and cross joined:
String query="""from Parent p1 left join p1.children pc1 ,
Parent p left join p.children pc where
pc.name =:input1 and pc1.name= :input2 and
p1.id=p.id group by p.name"""
def inputs=[:]
inputs.input1='Tom'
inputs.input2='Sam'
def list = Parent.executeQuery(query,inputs,[readonly:true])
println "list is ${list}"
But because everything is called name it makes things a little more difficult to manage if you want to use the grails groupBy to get a more compact list per parentName:
String query="""select new map(p1.name as parentName, pc1.name as childName, pc.name as childName2) from Parent p1 left join p1.children pc1 ,
Parent p left join p.children pc where pc.name =:input1 and pc1.name= :input2 and p1.id=p.id """ //group by p1.name, p.name"""
def inputs=[:]
inputs.input1='Tom'
inputs.input2='Sam'
def list = Parent.executeQuery(query,inputs,[readonly:true])
def list1=list.groupBy{it.parentName}
println "list is ${list} vs ${list1}"
like above which returns this to the console:
Joe [test.Child : 2]
Ann [test.Child : 2, test.Child : 1]
list is [[childName:Sam, childName2:Tom, parentName:Ann]] vs [Ann:[[childName:Sam, childName2:Tom, parentName:Ann]]]
And to finalise on this point, it is entirely upto you or the given process / complexity on whether you feel this processing should be as it is (maybe quite db strenuous vs more light weight query and some further query from grails with light lookups)
// Find all the parents that have 1 of our matches
// Tom but all that also have children greater than 1
def parents = Parent.findAll{children.name=='Tom' && children.size()>1}
//This already returns our only other option 3
println "found ${parents}"
// Now to confirm that the childrens the
// parent has contains our other scenario:
// Lets build our own list from it
def finalList=[]
//Iteration through our listing of parents
parents?.each { Parent p ->
// If the child name matches other case
if (p.children.name.contains('Sam')) {
// add this to our finalList
finalList << p
}
}
// We have correct results in this list
println "we now have ${finalList}"
That probably did not generate huge db queries cross joining but in the end did lots of small lookups agains the lazy list of each parent that we had an interest in (who had more than 1 child)
This is the scenarios - I guess it is down to your data model and what works best for you. The initial hql maybe a huge one off query that produces a light weight list. 2nd light weight query with lots of light weight lookups.
The second example outputs:
found [test.Parent : 3]
we now have [test.Parent : 3]
The above finalList was to give an example of how you would do it manually, that entire segment could be converted to a 1 liner:
def parents = Parent.findAll{children.name=='Tom' && children.size()>1}.collect{[name:it.name, children:it.children]}.findAll{it.children.name.contains('Sam')}
which produces:
found [[name:Ann, children:[test.Child : 1, test.Child : 2]]]
VS:
def parents = Parent.findAll{children.name=='Tom' && children.size()>1}.collect{[name:it.name, children:it.children.name]}.findAll{it.children.contains('Sam')}
which produces:
found [[name:Ann, children:[Tom, Sam]]]
The very last example collects only specific fields so the final parents listing does not contain entire child/parent objects but a light list containing their names.
So use as required.
来源:https://stackoverflow.com/questions/40219196/grails-named-queries-find-parents-of-child-a-and-child-b