Именованные запросы Grails: поиск родителей дочерних элементов A и дочерних элементов B

У меня есть это:

class Parent {
  String name
  static hasMany = [children: Child]
}

class Child {
  String name
}

И я хочу найти всех родителей, у которых есть ребенок по имени «Том» и еще один по имени «Сэм».

id | parentName
---------------
1  | Peter 
2  | Joe
3  | Ann

id | childName
---------------
1  | Tom 
2  | Sam

idParent | idChild
---------------
1        | 1 
2        | 2
3        | 1
3        | 2

В примере это будет Энн.

Я пробовал это:

static namedQueries = {
    findParents{nameList ->
        children{
            nameList.each{childName->
                and{
                     ilike('nombre', '%'+childName+'%')                 
                    }
            }           
     }

Но таким образом я ищу только одного ребенка с Томом и Сэмом на его имя. В примере он ничего не вернет.

Я пробовал использовать 'in' 'name', ["Tom","Sam"] вместо оператора ilike, но теперь я получу всех родителей с ребенком по имени «Том» или ребенком по имени «Сэм». В примере он вернет Питера, Джо и Энн.

Есть идеи?

Заранее спасибо!


person Ido    schedule 24.10.2016    source источник


Ответы (1)


Итак, теперь я понимаю, и я воссоздал и сделал ответ для вас

idParent | idChild
---------------
1        | 1 
2        | 2
*3        | 1
*3        | 2

Так что на самом деле вы ищете 3 или Энн, верно?

Peter [test.Child : 1]
Joe [test.Child : 2]
Ann [test.Child : 1, test.Child : 2]

Что вы хотите:

list is [[test.Parent : 3, test.Child : 2, test.Parent : 3, test.Child : 1]]

E2A, декабрь 2016 г. Гораздо лучше, чем все предыдущие версии

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]) 

Производит:

println "list is $listing"
list is [[childMatch:false, parentName:Peter], 
[childMatch:false, parentName:Joe], 
[childMatch:true, parentName:Ann]]

Теперь, если мы просто изменили:

def listing = Parent.executeQuery(query,inputs,[readonly:true])?\
.findAll{it.childMatch=='true'}
    println "list is $listing"
list is [[childMatch:true, parentName:Ann]]

Как видите, гораздо меньше сложностей, чем в предыдущих методах

Альтернатива предыдущему, но все же не так хорошо, как указано выше. Вы также можете использовать in elements, который привязывается к реальному объекту, а не к вашему соединению, как показано ниже:

(:children) in elements(p.children)

Но даже с помощью этого метода вы столкнетесь с проблемами, описанными ниже. Самый первый метод в этом ответе довольно мощный, используя подсчет, вы можете использовать его как форму веса, чтобы увидеть, сколько записей имеют 2, сколько 1, сколько 0 и т. д., поэтому от него намного больше применений -

Предыдущие методы

Я использовал hql и перекрестное соединение:

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}"

Но поскольку все называется name, это немного усложняет управление, если вы хотите использовать Grails groupBy для получения более компактного списка для каждого 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}"

как и выше, который возвращает это на консоль:

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]]]

И, чтобы закончить на этом этапе, это полностью зависит от вас или данного процесса/сложности в том, считаете ли вы, что эта обработка должна быть такой, какая она есть (может быть, довольно напряженная БД по сравнению с более легким запросом и некоторым дополнительным запросом от Грааля с легким поиском)

// 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}"

Это, вероятно, не генерировало огромные перекрестные запросы к БД, но, в конце концов, выполняло множество небольших поисков по ленивому списку каждого родителя, который нас интересовал (у которого было более 1 дочернего элемента)

Это сценарии - я думаю, это зависит от вашей модели данных и того, что лучше всего подходит для вас. Первоначальный hql может быть огромным одноразовым запросом, который создает легкий список. 2-й легкий запрос с большим количеством легких поисков.

Второй пример выводит:

found [test.Parent : 3]
we now have [test.Parent : 3]

Вышеупомянутый finalList должен был дать пример того, как вы могли бы сделать это вручную, весь этот сегмент можно преобразовать в 1 лайнер:

def parents = Parent.findAll{children.name=='Tom' && children.size()>1}.collect{[name:it.name, children:it.children]}.findAll{it.children.name.contains('Sam')}

который производит:

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')}

который производит:

found [[name:Ann, children:[Tom, Sam]]]

В самом последнем примере собираются только определенные поля, поэтому окончательный список родителей содержит не целые дочерние/родительские объекты, а упрощенный список, содержащий их имена.

Так что используйте по мере необходимости.

person V H    schedule 25.10.2016
comment
Спасибо Вахид, но так он найдет родителей с ребенком Тома ИЛИ Сэма. Я ищу родителей с ребенком Тома и Сэма. - person Ido; 26.10.2016
comment
Я думаю, что это просто случай изменения || на && во втором примере я обновил ответ другим вариантом того, что предлагается - person V H; 26.10.2016
comment
Кажется, что Grails не любит два параметра, объединенных && || с помощью найти все. Во всяком случае, я думаю, что это сделает что-то вроде этого: Выберите * из родительского p внутреннего соединения parent_child pc на p.id = pc.id внутреннего соединения дочернего c на pc.id = c.id, где c.name как '%Tom%' и c.name, например '%Sam%'. Этот выбор не будет работать, потому что в Child нет строки с именами Tom и Sam одновременно. - person Ido; 26.10.2016
comment
Я получил его, чтобы вернуть правильный ответ, который он делает путем перекрестного соединения, но предыдущая более ранняя копия была, когда я был среди других вещей и имел проблемы. Я проверил выше, и все хорошо и хорошо - person V H; 26.10.2016
comment
Спасибо Вахид!! Было бы безумием совмещать это с остальными параметрами моего поиска. Думаю, будет лучше (по крайней мере, для моего психического здоровья) разрешить пользователям искать только по одному ребенку. ;) - person Ido; 02.11.2016