Grails query map where object1 hasMany object2 - grails

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.

Related

Properly format an ActiveRecord query with a subquery in Postgres

I have a working SQL query for Postgres v10.
SELECT *
FROM
(
SELECT DISTINCT ON (title) products.title, products.*
FROM "products"
) subquery
WHERE subquery.active = TRUE AND subquery.product_type_id = 1
ORDER BY created_at DESC
With the goal of the query to do a distinct based on the title column, then filter and order them. (I used the subquery in the first place, as it seemed there was no way to combine DISTINCT ON with ORDER BY without a subquery.
I am trying to express said query in ActiveRecord.
I have been doing
Product.select("*")
.from(Product.select("DISTINCT ON (product.title) product.title, meals.*"))
.where("subquery.active IS true")
.where("subquery.meal_type_id = ?", 1)
.order("created_at DESC")
and, that works! But, it's fairly messy with the string where clauses in there. Is there a better way to express this query with ActiveRecord/Arel, or am I just running into the limits of what ActiveRecord can express?
I think the resulting ActiveRecord call can be improved.
But I would start improving with original SQL query first.
Subquery
SELECT DISTINCT ON (title) products.title, products.* FROM products
(I think that instead of meals there should be products?) has duplicate products.title, which is not necessary there. Worse, it misses ORDER BY clause. As PostgreSQL documentation says:
Note that the “first row” of each set is unpredictable unless ORDER BY is used to ensure that the desired row appears first
I would rewrite sub-query as:
SELECT DISTINCT ON (title) * FROM products ORDER BY title ASC
which gives us a call:
Product.select('DISTINCT ON (title) *').order(title: :asc)
In main query where calls use Rails-generated alias for the subquery. I would not rely on Rails internal convention on aliasing subqueries, as it may change anytime. If you do not take this into account you could merge these conditions in one where call with hash-style argument syntax.
The final result:
Product.select('*')
.from(Product.select('DISTINCT ON (title) *').order(title: :asc))
.where(subquery: { active: true, meal_type_id: 1 })
.order('created_at DESC')

Active Record Query don't load all id's

I have an active record query like this one:
House.where.not(id: Person.ill.pluck(:house_id))
When I run explain on this query, I see that all the id's get loaded into the query:
Seq Scan on houses (cost=0.00..4274.07 rows=1102 width=77)
Filter: id <> ALL ('{1117,487,842,126,127,459,458,515,332,55,54,10,1697,449,448,1555,13,510,1986,8,9,7,6,5,1865,519,157,513,512,56,103,28,27,97,25,23,385,138,278,92,1,435,196,195,61,363,229,230,238,237,231,160,158,749,748,518,517,174,173,172,395,153,1170,207,206,276,199,198, ....
Is there a more performent method to achieve the same query? Without loading all the ids?
pluck runs its query immediately, because it returns an array of IDs.
You can use select to keep the result as a Relation, which will then be used in-place as a subquery:
House.where.not(id: Person.ill.select(:house_id))
Try to use SQL JOIN, but you will cannot reuse ill scope here. If for example, this scope looks like this:
scope :ill, -> { where(status: 'ill') }
then your query could be like this:
House.joins("JOIN persons ON houses.id = persons.house_id AND persons.status <> 'ill'")

Retrive records which are not referenced in other table, ActiveRecord query

There are 2 tables : User and Teacher. Teacher.user_id is from User. So, how do I find in a single query, all the users who are not in teachers.
I meant something along the lines :
User.not_in(Teacher.all)
You can use where.not query from ActiveRecord try something like below:
User.where.not(id: Teacher.pluck(:user_id).reject {|x| x.nil?})
Note: used reject method, in case you have nil values in some records.
The other users seem to have neglected the rails 3 tag (since removed based on the approved answer. My answer left for posterity) : Please try this
User.where("id NOT IN (?)",Teacher.pluck(:user_id).join(","))
This will become SELECT * FROM users WHERE id NOT IN (....) (two queries one to get the user_id from teachers and another to get the user(s) not in that list) and may fail based on the size of teacher table.
Other option is an arel table:
users = User.arel_table
User.where(users[:id].not_in(Teacher.select(:user_id).where("user_id IS NOT NULL")))
This should produce a single query similar to
SELECT * FROM users
WHERE id NOT IN ( SELECT user_id FROM teachers WHERE user_id IS NOT NULL)
(one query better performance) * syntax was not fully tested
Another single query option might be
User.joins("LEFT OUTER JOIN teachers ON teachers.user_id = users.id").
where("teachers.user_id IS NULL")
I think you should be able to do something like this
User.where.not(id: Teacher.ids)

HQL Query fails when using join and with clause

Good Evening,
I am using Grails and I am trying to do an HQL Query
I have an object Opportunity and inside it an object Entity and inside the Entity a collection of Titles. Each Title object can be main or not (main is a boolean field that shows which of the titles is the default one). So the query that I am doing is this:
select opportunity from Opportunity as opportunity join opportunity.entity.titles as entityTitle with entityTitle.isMain is true
But this query fails with this message:
org.hibernate.hql.ast.InvalidWithClauseException: with-clause expressions did not reference from-clause element to which the with-clause was associated.
I have tried adding the Entity and Title tables and still it fails. If I remove the with clause it works correctly but I need to filter the titles.
Thanks.
Try this query:
select distinct op from Opportunity as op
inner join op.entity as ent
inner join ent.titles as tit
with tit.isMain is true
I found the problem. First I had to join the entity to the Opportunity and then join the titles to the Opportunity. So the query is like this:
select opportunity from Opportunity as opportunity
join opportunity.entity activeEntity
with activeEntity.isActive is true
join activeEntity.titles entityTitle
with entityTitle.isActive is true

ActiveRecord subquery in select clause

So I'm getting a bunch of Volunteers records, with some filtering and sorting, which is fine. But I'd like to also get a count of the number of Children each volunteer is helping (using volunteer_id on children table), as a sub-query in the select clause to avoid having to perform a separate query for each record. As a bonus it would be good to be able to sort by this count too!
I'd like to end up with a generated query like this and be able to access the 'kids' column:
SELECT id, name, (SELECT COUNT(*) FROM children WHERE volunteer_id = volunteers.id) AS kids FROM volunteers
Is there any way of doing this with Arel? I've had a bit of a scout around and haven't found anything yet.
Alternatively, is it possible to join to the children table and get: count(children.id) ?
Thanks for any help :)
The proper way of doing this with SQL is with a GROUP BY clause:
SELECT v.id, v.name, COUNT(*) AS kids
FROM volunteers v
LEFT OUTER JOIN children c ON v.id = c.volunteer_id
GROUP BY v.id, v.name
There is a method .group() in AR for using GROUP BY queries.

Resources