I have to write some queries to implement a search in a complex data model. I want to use the Criteria DSL of Gorm to solve that.
For simple queries like the examples in the Gorm or Grails Documentation it's no problem, but I don't know, how to implement more complicated queries like
"each in", "none in" or "only one of x is in". I hope someone can give me a hint.
Grails Domain Classes:
class Content {
Integer contentNumber
static hasMany = [titles : Title]
}
class Title {
String title
Titletype titletype
}
class Titletype {
String name
}
Method with Criteria:
def filter(GrailsParameterMap args) {
//To ensure that the ids of titletypes to query are always given as Long[]
Long[] argsTitletype = [args.title.titletype.value].flatten() as Long[]
def query = new DetachedCriteria(Content).build {}
// Every Content, which has a Title with Titletype in argsTitletype
query = query.build {
titles {
'in'('titletype.id', argsTitletype)
}
}
return query.list()
}
The return of the query above has the expected result: All Content, which has a Titletype in argsTitletype
But how to query "Content, which has all the Titletypes in argsTitletype"?
I tried for example:
query = query.build {
titles {
and {
argsTitletype.each { tt ->
eq('titletype.id', tt)
}
}
}
or
query = query.build {
and {
argsTitletype.each { tt ->
titles {
eq('titletype.id', tt)
}
}
}
Where is my misstake? Have I to use Subqueries?
For your all-in Try the following out:
query = query.build {
titles {
titletype {
argsTitletype.each { tt ->
eq 'id', tt
}
}
}
}
you don't need ands here, as those are applied by default.
Related
I have such entities, stored in database using GORM (I omitted irrelevant fields):
class Payment {
static hasMany = [paymentEntries: PaymentEntry]
}
class PaymentEntry {
static hasOne = [category: PaymentCategory]
static belongsTo = [payment: Payment]
static constraints = {
category(nullable: true)
}
}
class PaymentCategory {
static hasMany = [payments: PaymentEntry, rules: PaymentRule]
String name
}
So we have Payments on the top, which can have many PaymentEntryies, and each PaymentEntry can belong to one PaymentCategory.
I need to select Payments, which meet some conditions, but only the ones that also have PaymentEntries belonging to specific categories. Currently I do it in 3 queries:
Select categories which fit to part of category name, or just omit category query (in such case we want all the payments regardless of their entries category):
private static List<PaymentCategory> getCategories(String category) {
def result = null
if (category != null && category.length() > 0) {
def c = PaymentCategory.createCriteria()
result = c {
ilike("name", "%${category}%")
}
}
result
}
Select PaymentEntries ids, based on PaymentCategory:
private static List<Long> paymentEntryIds(String category) {
def categories = getCategories(category)
def c = PaymentEntry.createCriteria()
def result = new ArrayList()
// If category is selected, but there is no matching category, return empty list
if (!categorySelectedButNoMatch(category, categories)) {
result = c {
if (category == null) {
isNull("category")
} else if (categories != null && categories.size() > 0) {
inList("category", categories)
}
projections {
property("id")
}
}
}
result
}
Finally select Payments, limited to the ones that contain specific PaymentEntries:
def paymentEntriesIds = paymentEntryIds(selectedCategory)
def c = Payment.createCriteria()
def result = new ArrayList()
// If there are no payment entries matching category criteria, we return empty list anyway, so there
// is no need querying payments.
if (paymentEntriesIds.size() > 0) {
result = c {
paymentEntries {
inList("id", paymentEntriesIds)
}
if (importEntryId != null) {
eq("importEntry.id", importEntryId)
}
if (query != null) {
ilike("description", query)
}
// Omitted ordering and paging
}
}
result
This works, but it runs 3 queries to the database. I'm pretty sure this code could be cleaner, and it could be done in less queries. All the ideas on how to improve it are welcome.
You can use at least 3 different methods: detached queries, subqueries of where queries and subqueries of HQL.
Example of DetachedQuery with criteria looks like:
def subquery = new DetachedCriteria(PaymentCategory).build {
projections {
groupProperty 'id'
}
ilike("name", "%${category}%")
}
Then you can use this subquery in another query:
return PaymentEntry.createCriteria().list {
category {
inList "id", subquery
}
}
I didn't test it on your domain exactly, this should just show the idea. Refer to DetachedCriteria part of GORM documentation: https://grails.github.io/grails-doc/latest/guide/single.html#detachedCriteria
We use it for some very complicated queries. Sometimes createAlias will be required to correctly work with associations.
class Client {
String name
static hasMany = [courses:Course]
}
class Course {
String name
static belongsTo = [client:Client]
}
I have this and I want to get all Clients that has a Course with name = "blabla"
I was trying to do : Clients.findWhere(Course.any { course -> course.name = "math" })
You can do this with criteria:
Client.withCriteria {
courses {
eq('name', 'math')
}
}
I believe that the following where query is equivalent to the above criteria:
Client.where { courses.name == 'math' }
or you may find you need another closure:
Client.where {
courses {
name == 'math'
}
}
but I rarely use where queries myself so I'm not 100% sure of that.
There are probably a lot of different syntactical expressions to achieve the same thing. I can say definitively that this works in my project though.
def ls = Client.list {
courses {
eq('name','math')
}
}
I need to do a search based on various criteria.
if a name contains the specified string.
if the name contains the specified string and a string from a list.
if the name contains one of the strings from a list.
I have the following code but it doesn't work. How bad is this code?
def taskList = Object.createCriteria().list(params) {
if (params.query) {
ilike("name", "%${params.query}%")
}
if (params.nameList) {
params.nameList.split("&").each {
or {
ilike("name", "%${it}$%")
}
}
}
The result is empty for use cases 2 & 3. What am I doing wrong? How should I do it?
Cheers
I'd use in operator for 3. case
list{
or{
if( params.query ) ilike "name", "%${params.query}%"
if( params.nameList ) 'in' 'name', params.nameList.split("&")
}
}
def taskList = Object.createCriteria().list(params) {
def names = []
if(params.nameList){
names = nameList.split("&")
}
or{
if (params.query){ilike("name", "%${params.query}%")}
and{
if (params.query){ilike("name", "%${params.query}%")}
if(names){'in'('name', names)}
}
if (names) {
names.each {name->
or {
ilike("name", "%$name$%")
}
}
}
}
}
Untested, but can you try with above. I would rather go with an HQL to achieve what you needed.
I have a Grails application and want to create filters for my domain class using named query.
I have domains Act and Status, StatusName is an Enum:
class Act {
static hasMany = [status : Status]
}
class Status {
Date setDate
StatusName name
static belongsTo = [act : Act]
}
I want to filter Acts which have their most recent Status's name equal to specific name.
For now I have this code in Act:
static namedQueries = {
filterOnStatus { StatusName s ->
status {
order('setDate', 'desc')
eq 'name', s
// I need only first Status, with most recent setDate
// among all Statuses of that Act
}
}
}
But this filter all Acts that have Status with specific name, not only with most recent. I tried to place maxResult 1 in query, but it seems not to work.
Any help would be appreciated.
EDIT: Problem was solved that way:
filteronStatus {
createAlias('status', 's1')
eq 's1.name', s
eq 's1.setDate', new DetachedCriteria(Status).build {
projections {
max('setDate')
eqProperty('act', 's1.act')
}
}
}
see 'namedQueries' from Grails Doc
// get a single recent Act
def recentAct = Act.filterOnStatus(statusName).get()
ADD:
HQL
"select s1.act from Status as s1 \
where s1.name = :statusName \
and s1.setDate = (select max(s0.setDate) from s1.act.status s0)"
NamedQuery
listByStatus { statusName ->
createAlias('status', 's1')
eq 's1.name', statusName
eq 's1.setDate', new DetachedCriteria(Status).build{ projections { max('setDate')} eqProperty('act','s1.act') }
}
Have the following Domain modal:
class TransactionHeader {
static hasMany = [details: TransactionDetail]
}
class TransactionDetail {
static belongsTo = [header: TransactionHeader]
Product product
}
I'm trying to write a criteria query that will return all the TransactionHeader rows that contain TransactionDetails with 2 different Products. This is what I have so far and it isn't doing exactly what I'm after:
def list = TransactionHeader.withCriteria {
details {
and {
eq("product", product1)
eq("product", product2)
}
}
}
What's happening is it is return rows that contain at least 1 detail with 1 of the products. I need rows that have 2 details, each with one of the products.
Feels like you want to move details out of that, so actually
def list = TransactionHeader.withCriteria {
and {
details {
eq("product", product1)
}
details {
eq("product", product2)
}
}
}
Not sure how hibernate / gorm will deal this, though.
Have you tried using the "in" statement in your criteria dsl?
def list = TransactionHeader.withCriteria {
and {
details {
'in'("product", [product1, product2])
}
}
}