Given the following domain class:
class Game {
Integer maxUsers
static hasMany = [users: User]
}
Using the Criteria API, what should I do to get all domains with the number of users less than maxUsers property?
I don't think it's possible to do with Criteria api, as Hibernate Criteria don't support HAVING clause. There is an open JIRA issue for that, you can try patches submitted there.
An alternative would be to use HQL:
def results = Game.findAll("from Game where id in (select g.id from Game g join g.users u group by g.id, g.maxUsers having count(u) < g.maxUsers)")
Related
I have a link between ads and products and stores, and I want to sort them in the admin by store:
class Ad(models.Model):
products = models.ManyToManyField(Product, blank = True)
device = models.ForeignKey(Display, on_delete = models.CASCADE)
class Product(models.Model):
name = models.CharField("name", max_length = 128)
store = models.ForeignKey(Store, on_delete = models.CASCADE)
class Store(models.Model):
name = models.CharField("name", max_length = 128)
so each Ad can have 0, 1, 2, 3 ... products linked to it. Now I want to make the field "store" sortable in the Ads admin list, therefore I tried to overwrite the get_queryset method of AdAdmin but got stuck on the way:
class AdAdmin(admin.ModelAdmin):
list_display = ["get_store", ... ]
def get_queryset(self, request):
qs = super().get_queryset(request)
qs = qs.annotate(storename = ####)
return qs
#admin.display(ordering = "storename")
def get_store(self, obj):
try:
return obj.products.all().first().store.name
except AttributeError:
try:
return obj.device.store.name
except AttributeError:
return "NEW"
So I want to annoatate my queryset by storename and I want to be able to sort by store.name alphabetically on my Ad list admin page. I already found out how to annotate empty stores:
qs = qs.annotate(storename = Case(When(products_pk__isnull = True, then = Value("NEW"), default = Value("old")))
But this only got me so far ... how would I assign the value store.name to default dynamically using my given logic?
Every Ad has a condition: It can only be linked to (many) products of the same store.
As you mentioned, every Ad can only have products from one store. Then you can take the store name from any Product or distinct result for all Products.
To access the Product from the Ad model, you can add a related_query_name
# models.py
class Ad(models.Model):
products = models.ManyToManyField(
Product,
related_name="product_ads",
related_query_name="product_ads_qs",
blank=True,
)
...
# admin.py
from django.db.models import OuterRef
#admin.register(Ad)
class AdAdmin(admin.ModelAdmin):
list_display = ["get_store", ...]
def get_queryset(self, request):
qs = super().get_queryset(request)
qs = qs.annotate(
# Get all store names for each Ad object
storename=Product.objects.filter(product_ads_qs=OuterRef("id"))
# Distinct the result. (Will fail it multiple store names returns)
.distinct("store__name")
# Get the store name
.values("store__name")
)
return qs
#admin.display(ordering="storename")
def get_store(self, obj):
# You can change the default value here. No need for Case operation
return obj.storename or "NEW"
It should work. I am unsure about the performance, maybe a better ORM can be written.
UPDATE
Distinct can be replaced with [:1]. I forgot it yesterday, sorry.
The store is FK for Product, but we need to be sure which Product instance we have from the query. That is why OuterRef is used. It means to get the Product instance for the given Ad instance by Ad.id.
There are several ways to access the store names. It is probably faster with the related name. I am not that experienced but will try to explain:
Without the related_name, this ORM can be written like
storename=Ad.objects.filter(id=OuterRef("id")).values("products__store__name")[:1]
This ORM creates a query like the following. It started from the Ad table, and 3 joins the access to the store table.
SELECT
"app_ad"."id",
(
SELECT U3."name"
FROM "app_ad" U0
-- there is a reference table for m2m relation created by Django
LEFT OUTER JOIN "app_ad_products" U1 ON (U0."id" = U1."ad_id")
LEFT OUTER JOIN "app_product" U2 ON (U1."product_id" = U2."id")
LEFT OUTER JOIN "app_store" U3 ON (U2."store_id" = U3."id")
-- OuterRef works here and searches the Ad.id per Ad record
WHERE U0."id" = ("app_ad"."id")
-- [:1] limits the query
LIMIT 1
) AS "storename"
FROM "app_ad"
If we use a related name
storename=Product.objects.filter(product_ads_qs=OuterRef("id")).values("store__name")[:1]
The query output has 2 joins to access the store table
SELECT "app_ad"."id",
(
SELECT U3."name" FROM "app_product" U0
INNER JOIN "app_ad_products" U1 ON (U0."id" = U1."product_id")
INNER JOIN "app_store" U3 ON (U0."store_id" = U3."id")
WHERE U1."ad_id" = ("app_ad"."id") LIMIT 1
) AS "storename"
FROM "app_ad"
Bonus:
If you add the related name to Product model relation
class Product(models.Model):
name = models.CharField("name", max_length=128)
store = models.ForeignKey(
Store,
related_name="store_product",
related_query_name="store_product",
on_delete=models.CASCADE,
)
The ORM can also be written as the following, and the query will be pretty much the same as the previous one.
storename=Store.objects.filter(store_product__product_ads_qs__id=OuterRef("id")).values("name")[:1]
You can check for related naming convention.
I want to do a query like this:
I have a Tale that hasMany secondaryTags
and the query I am trying to do is the following:
def query = 'select new map(title as title,
mainTag as mainTag,
secondaryTags as secondaryTags) from Tale order by rand()';
def result = Tale.executeQuery(query, [max: 3])
But as secondaryTags is a Collection of Tag it doesn't work. Does anyone know how I do this?
Thanks a lot in advance!
A join is required to fetch the associations. Try this query, it should work:
select new map( tale.title as title,
tale.mainTag as mainTag,
sts as secondaryTags )
from Tale as tale
inner join fetch tale.secondaryTags as sts
order by rand()
since you are seeking a map. Otherwise
select tale from Tale as tale
inner join fetch tale.secondaryTags
order by rand()
should be sufficient to get whatever you need from one query. Also note that inner join fetch is used, which means the above query will eagerly fetch the association secondaryTags from tale in the same query, instead of firing another query to get the association if tale.secondaryTags is issued. To witness the fact, enable logging (logSql = true) in DataSource.groovy and check the behavior of the aforementioned principle.
I am a newbie to hql and tried out several combinations that i could find but i seem unable to construct the correct HQL query for a left join.
I have the following domain model:
class Company {
static hasMany = [employees: Employee]
}
class Employee {
static belongsTo = [
Company
]
}
So a employee does not know anything about the companies. Now i would like to create a hql query that gives met the employees without a company. In sql i have successfully created the query using a left join but i seem not to be able to create a criteria or hql query that gives me a correct result.
Any a clue or some tips on how i could achieve the result?
Here you go, this works:
Employee.executeQuery("""
Select e
from Employee e
where e not in (Select ce from Company c left join c.employees ce)
""")
How can I convert the following into an active record query:
SELECT reviews.style_id, AVG("col1"), AVG("col2")
FROM reviews, audios
WHERE reviews.consumer_id = audios.consumer_id
GROUP BY style_id;
col1 and col2 belong to the audios table, but they are uniquely named (no similar column name in reviews), so there is no ambiguity error.
I am using PostgreSQL.
If you have an association between Review and Audio then something like this:
revs = Review.joins(:audios)
.group('style_id')
.select('style_id, avg(col1) as avg_col1, avg(col2) as avg_col2')
That will give a list of Review instances in revs and those instances will have extra avg_col1 and avg_col2 methods for accessing the averages as well as the usual style/style_id methods but the other column accessor methods that Review would normally offer will raise exceptions.
If you don't have the associations set up then you can do the JOIN manually:
revs = Review.joins('join audios on reviews.consumer_id = audios.consumer_id')
.group('style_id')
.select('style_id, avg(col1) as avg_col1, avg(col2) as avg_col2')
If all you need is just the raw data without all the ActiveRecord wrapping and overhead, then you could execute the raw SQL and hashify it by hand using select_rows:
Review.connection.select_rows(%q{
select r.style_id, avg(a.col1), avg(a.col2')
from reviews r
join audios a on r.consumer_id = a.consumer_id
group by r.style_id
}).map do
{ :style_id => r.shift, :avg_col1 => r.shift.to_f, :avg_col2 => r.shift.to_f }
end
That would give you an Array of Hashes. You could even simplify that approach using Struct to create simple data wrapper classes:
c = Struct.new(:style_id, :avg_col1, :avg_col2)
revs = Review.connection.select_rows(%q{...}).map do |r|
c.new(r.shift, r.shift.to_f, r.shift.to_f)
end
PS: Don't use implicit join conditions your SQL, that's just a quick and easy way to produce cross products, use explicit join conditions:
SELECT ...
FROM reviews JOIN audios ON reviews.consumer_id = audios.consumer_id
GROUP BY style_id
Is it possible to access the relationship table when doing HQL statement?
As an example, I have 3 tables: account, commitment, account_commitment. It was generated using these domains:
class Account {
static hasMany = [ commits : Commitment ]
String name
}
class Commitment {
static hasMany = [ actors : Account ]
String description
}
My final and actual SQL query is something like this:
SELECT
b.id,
account_name,
d.nid,
d.title
FROM
account_commitment a, // is this available in HQL?
account b,
commitment c,
content_type_act d
where
d.nid = 3332
and a.account_id = b.id
and a.act_id = c.id
and c.act_id = d.nid
I believe HQL only allows valid class domains. Since the relationship table is automatically generated, is this possible in HQL?
Thanks.
No, HQL only works with mapped classes. If you want to run SQL queries just use groovy.sql.Sql. But if you only want to access the intermediate table to join the other two, that's unnecessary since HQL knows about the relationships between tables already.