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])
}
}
}
Related
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.
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.
I've been trying to query with multiple hasMany objects, such as:
def product = Product.createCriteria().list {
createAlias("productAttributeValues", "pav")
and {
if (session.filters?.filter_instore) {
and {
eq("pav.attribute", Attribute.findByCode("instore"))
eq("pav.valueDe", session.filters?.filter_instore?.toString())
}
}
if (session.filters?.filter_promo) {
and {
eq("pav.attribute", Attribute.findByCode("promo"))
eq("pav.valueDe", "1")
}
}
}
}
This doesn't seem to work, since I never get a result set, although I have test data that would match. It seems that the problem is that I'm querying twice with the same objects.
One possible solution would be to query the productAttributeValues seperately and then intersect with a "in"("myPavs", pavs) - but this seems to be inefficient to me.
Any thoughts?
Thanks.
I would re-build your query so:
def products = Product.withCriteria{
productAttributeValues{
or{
if (session.filters?.filter_instore) {
and {
eq("attribute", Attribute.findByCode("instore"))
eq("valueDe", session.filters?.filter_instore?.toString())
}
}
if (session.filters?.filter_promo) {
and {
eq("attribute", Attribute.findByCode("promo"))
eq("valueDe", "1")
}
}
}
}
}
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 have a named query in a domain class:
class Store {
static namedQueries {
openOnWeekends {
// some conditions here...
}
}
}
I know that I can do Store.openOnWeekends.list(), but I want to do something like this:
def pickupWeekendStores = Order.get(params.id).books.store.openOnWeekends
Is there a way to use named queries like that? Any suggestions on how to get the stores open on weekends?
Edit:
Order hasMany Book, Book hasMany Store
Named queries support additional criteria at invocation, so you could try:
Store.openOnWeekends {
books {
'in'('id', Order.get(params.id).books.collect { it.id })
}
}
Does doing something like:
Set<Store> pickupWeekendStores = Order.get(params.id).books*.store.collect { it.openOnWeekends.list() }
work?