In a forum, I want to list the most recent posts from each topic.
In SQL I can do a subquery with group by Topic taking Max(Post.Date) and then make an inner join with the Posts table to get the latest Post record from each Topic.
How can I reproduce this in ICriteria?
DETAIL: The Topic class have NO Posts property.
TARGET SQL:
SELECT Post.*
FROM Posts, (SELECT IdTopic, MAX(DATE) AS Date FROM Posts GROUP BY IdTopic) AS MaxDates
WHERE MaxDates.IdTopic = Posts.IdTopic AND MaxDates.Date = Posts.Date
Tks[]
Patrick Coelho
By using CreateCriteria. This is explained very well by Ayende Rahien.
Edit changed to address OP's comments
I'm on shaky ground here, but this may get you started:
DetatchedCriteria posts = DetachedCriteria.For<Post>("p")
.SetProjection(Projections.Property("p.IdPost"));
.Add(Restrictions.EqProperty("t.IdPost", "p.IdPost"));
DetachedCriteria postMax = DetachedCriteria.For<Post>, "p2")
.SetProjection(Projections.Max("Date"));
var topics Session.CreateCriteria<Topic>("t")
.Add(Subqueries.Select(postMax))
.Add(Subqueries.Exists(posts)).List<Topic>();
Related
I have an article model and comments model. How do i get a list of articles that does not have any comments using active record?
Model Columns:
Article: body:string (has many comments)
Comment: body:string, article_id:integer (belongs to article)
If you want to get the result using single query and want the result to be an activerecord relation, use:
Article.where('id NOT IN (SELECT DISTINCT(article_id) FROM comments)')
This is same but would be more rails way
Article.where.not('id IN (SELECT DISTINCT(article_id) FROM comments)')
try below code to fetch all articles with no comments:
Article.includes(:comments).where.not(comments: {article_id: nil})
OR
data = []
Article.all.each do |a|
data << a if a.comments.blank?
end
puts data
OR
ids = Comment.all.pluck(:article_id)
data = Article.where.not(id: ids)
I have a mildly-complex ActiveRecord query in Rails 3.2 / Postgres that returns documents that are related and most relevant to all documents a user has favorited in the past.
The problem is that despite specifying uniq my query does not return distinct document records:
Document.joins("INNER JOIN related_documents ON
documents.docid = related_documents.docid_id")
.select("documents.*, related_documents.relevance_score")
.where("related_documents.document_id IN (?)",
some_user.favorited_documents)
.order("related_documents.relevance_score DESC")
.uniq
.limit(10)
I use a RelatedDocument join table, ranking each relation by a related_document.relevance_score which I use to order the query result before sampling the top 10. (See this question for schema description.)
The problem is that because I select("documents.*, related_documents.relevance_score"), the same document record returned multiple times with different relevance_scores are considered unique results. (i.e. if the document is a related_document for multiple favorited-documents.)
How do I return unique Documents regardless of the related_document.relevance_score?
I have tried splitting the select into two seperate selects, and changing the position of uniq in the query with no success.
Unfortunately I must select("related_documents.relevance_score") so as to order the results by this field.
Thanks!
UPDATE - SOLUTION
Thanks to Jethroo below, GROUP BY is the needed addition, giving me the follow working query:
Document.joins("INNER JOIN related_documents ON
documents.docid = related_documents.docid_id")
.select("documents.*, max(related_documents.relevance_score)")
.where("related_documents.document_id IN (?)",
some_user.favorited_documents)
.order("related_documents.relevance_score DESC")
.group("documents.id")
.uniq
.limit(10)
Have you tried to group it by documents.docid see http://guides.rubyonrails.org/active_record_querying.html#group?
First off, I'm a Ruby/Rails newbie, so I apologize if this question is basic.
I've got a DB that (among other things) looks like this:
organizations { id, name, current_survey_id }
surveys { id, organization_id }
responses { id, survey_id, question_response_integer }
I'm trying to create a scope method that adds the average of the current survey answers to a passed-in Organization relation. In other words, the scope that's getting passed into the method would generate SQL that looks like more-or-less like this:
select * from organizations
And I'd like the scope, after it gets processed by my lambda, to generate SQL that looks like this:
select o.id, o.name, cs.average_responses
from organizations o join
(select r.id, avg(r.question_response_integer) as average_responses
from responses r
group by r.id) cs on cs.id = o.current_survey_id
The best I've got is something like this:
current_survey_average: lambda do |scope, sort_direction|
average_answers = Responses.
select("survey_id, avg(question_response_integer) as average_responses").
group("survey_id")
scope.joins(average_answers).order("average_responses #{sort_direction}")
end
That's mostly just a stab in the dark - among other things, it doesn't specify how the scope could be expected to join to average_answers - but I haven't been able to find any documentation about how to do that sort of join, and I'm running out of things to try.
Any suggestions?
EDIT: Thanks to Sean Hill for the answer. Just to have it on record, here's the code I ended up going with:
current_survey_average: lambda do |scope, sort_direction|
scope_table = scope.arel.froms.first.name
query = <<-QUERY
inner join (
select r.survey_id, avg(r.question_response_integer) as average_responses
from responses r
group by r.survey_id
) cs
on cs.survey_id = #{scope_table}.current_survey_id
QUERY
scope.
joins(query).
order("cs.average_responses #{sort_direction}")
end
That said, I can see the benefit of putting the averaged_answers scope directly onto the Responses class - so I may end up doing that.
I have not been able to test this, but I think the following would work, either as-is or with a little tweaking.
class Response < ActiveRecord::Base
scope :averaged, -> { select('r.id, avg(r.question_response_integer) as average_responses').group('r.id') }
scope :current_survey_average, ->(incoming_scope, sort_direction) do
scope_table = incoming_scope.arel.froms.first.name
query = <<-QUERY
INNER JOIN ( #{Arel.sql(averaged.to_sql)} ) cs
ON cs.id = #{scope_table}.current_survey_id
QUERY
incoming_scope.joins(query).order("average_responses #{sort_direction}")
end
end
So what I've done here is that I have split out the inner query into another scope called averaged. Since you do not know which table the incoming scope in current_survey_average is coming from, I got the scope table name via scope.arel.froms.first.name. Then I created a query string that uses the averaged scope and joined it using the scope_table variable. The rest is pretty self-explanatory.
If you do know that the incoming scope will always be from the organizations table, then you don't need the extra scope_table variable. You can just hardcode it into the join query string.
I would make one suggestion. If you do not have control over sort_direction, then I would not directly input that into the order string.
If I have a model called "Article" and another called "Comment", with each Article having zero or more Comments and each Comment having an associated user, how do I do the following:
Find all articles on the site and any comments that the given user has made
In SQL:
SELECT * FROM articles LEFT OUTER JOIN comments ON articles.id = comments.article_id AND comments.user_id = 2
I have tried doing this:
Article.joins('LEFT OUTER JOIN comments ON articles.id = comments.article_id AND comments.user_id = 2)
The result here is that result.first.comments give me all the comments for the article. I can solve this by adding conditions to the last part, but then it won't be eager
If you want all the comments made by a particular user and corresponding articles, this should work:
Comment.joins(:articles).where("comments.user_id" => 2).select("comments.*, articles.*")
Can you try with Article.joins(:comments).where("comments.user_id" => 2)?
Look here for more info.
An article has 1 or many comments. How would I get only the articles with 0 comments?
This would be easier with a counter cache. However, I need to do this without using a counter cache.
class Article < ActiveRecord::Base
has_many :comments
scope :without_comments,
joins(<<-SQL
LEFT OUTER JOIN
(SELECT article_id
FROM comments GROUP BY article_id) AS rolled_up_comments
ON comments.article_id = articles.id
SQL
).
where("rolled_up_comments.article_id" => nil)
end
Use like this:
Article.without_comments.all
This could easily be adapted to return articles with a specific number or range of comments, e.g.:
class Article < ActiveRecord::Base
has_many :comments
scope :with_comment_count,
joins(<<-SQL
LEFT OUTER JOIN
(SELECT article_id, COUNT(*) AS comment_count
FROM comments GROUP BY article_id) AS rolled_up_comments
ON comments.article_id = articles.id
SQL
)
scope :with_n_comments, lambda {
with_comment_count.
where(:"rolled_up_comments.comment_count" => n)
}
end
In the latter case, n can be a specific number, like 100, or a range like 1..10 which ActiveRecord will turn into a BETWEEN query returning articles with 1 through 10 comments.
Note that in the 0-comment case, the count is NULL, so you can't use the range query for that.
I've tested this in Postgres. I don't know if it'll work in MySQL. I'm not sure how/if MySQL handles sub-selects for joins.
Edit: The solution pointed out by a previous commenter is easier, if you only need to know articles without comments. For count ranges, the above will work.
I'm interested by the answer.
Did you try with a scope?
I'm not sure but it could be the solution.
Rails doc : http://guides.rubyonrails.org/active_record_querying.html#scopes