I have Posts and Comments tables. They have belongs_to and has_many relation. Everything works great.
What I need to do is writing the SQL to pull posts that have comments. How can I do that in the controller?
I need some sort of Join I guess. Right?
Thank you
Post.joins(:comments) by itself will give you all those post that do have comments related.
The INNER JOIN does that work. When you use an inner join between two tables it returns a new set of data with all of the instances of the join where the condition was met, the rows are ignored otherwise.
Related
I have a Post model that has many comments
class Post < ApplicationRecord
has_many :comments
end
How do I get distinct posts that have comments excluding the posts that do not have comments?
I believe that Post.joins(:comments).distinct("posts.*") does the job.
Is there any better alternative?
I emphasize that the result should not have duplicate entries of posts, which is what happens when we join with comments (one-to-many relationship).
Looking at this again, and considering Mark's comment to my original solution, the simplest solution may just be to drop the "posts.*" from your original.
Post.joins(:comments).distinct
This works, though obviously a matter of taste as to whether it is better:
Post.left_outer_joins(:comments).where.not(comments: {id: nil})
And left_outer_joins came in with Rails 5, so with earlier versions a more verbose solution is needed. See Finding nil has_one associations in where query
The nicest thing about it, is it pairs nicely with the opposite query:
Post.left_outer_joins(:comments).where(comments: {id: nil})
Which is all the posts without comments
You don't have to really use distinct at the end, it's not at all effective as you are selecting everything by default (posts.*) which has a unique primary key (id) for each record.
Post.joins(:comments)
# SELECT `posts`.* FROM `posts` INNER JOIN `comments` ON `comments`.`post_id` = `posts`.`id`
Note:
To see the difference yourself please try to run below queries.
Post.joins(:comments).count
Post.joins(:comments).distinct.count
# Both should be giving you the same numbers.
You can also achieve this using subquery:
Post.where(id: Comment.select(:post_id).distinct)
Load time depends on the number of comments in the database.
Suppose I have two models, one is User and one is Story. Both models has has_and_belongs_to_many relationship with each other. One user can have many stories and one story can have many users.
Since most of you already have understood, there exists a join table between the two, "users_stories" which has 'user_id' and 'story_id' as its columns.
Question is "How to get the users who do not have story?". Any efficient way would be appreciated.
Try this:
User.joins('left join user_stories ON users.id = user_stories.user_id').where('user_stories.user_id is NULL')
For more details you can see this image:
https://www.codeproject.com/KB/database/Visual_SQL_Joins/Visual_SQL_JOINS_orig.jpg
you can put a scope on model with joins query to check for count of stores greater than zero
without_stories_user = User.joins(users_stories: :story).group('users.id').having("count(stories.id) > ?",0)
For me this was users without notifications with simillar has_many and belongs to association.
without_notifications_user = User.joins(user_notifications: :notification).group('users.id').having("count(notifications.id) > ?",0)
Thanks
Environment: Rails 3.2.22
Question:
Lets say I have the models Topics, Posts, and User.
Posts belongs to Topics
User has many Posts
I want to make a query of Topic.all, but includes all posts associated to a user.
I've tried include and eager_load with a where condition for the user id, but only topics with a post which meets the condition are return.
What I want is all topics return and include only posts which match the user_id condition.
After playing around with ActiveRecord I figured out how to do the query. It requires the left join as pointed out by #pshoukry, but it is missing two items.
AND statement is required to include only posts for a specific user.
An ActiveRecord method select needs to be appended at the end to include the fields you want.
To include all fields:
Topic.joins("LEFT JOIN posts ON posts.topic_id = topics.id AND posts.user_id = ?", user.id).select('topics.*, posts.*')
Now for the caveat. For those using Postgres and on Rails version 3.2.* there is a bug where the joined table will only return strings for ALL columns, disregarding the data type set. This issue is not present with Rails 4. There was an issue posted in the Rail's Github repo, but I can't seem to locate it. Since 3.2 is no longer supported they have no intention of fixing it.
Try using left join in your relation
Topic.joins("LEFT JOIN posts ON topics.id = posts.topic_id")
I'm a bit stuck on an Active Record query problem. I'd like to do a left join instead of an inner join on a query, but the results of the joins method are returning only an inner join.
Entry.
joins(:measurements => :measurement_type).
sum(:value, :conditions => ['name = ?','Calories'])
The tricky part is that :measurements has a belongs_to relationship to :measurement_type which is where the name column resides.
Right now this is returning sums only for users who have food_posts. The goal is to return sums for all users. Users who don't have a food post would have a sum of zero.
I've tried Googling and searching other posts on here, but haven't found an answer. Any suggestions?
Also I'm using SQLite in my development environment and Postgresql for production.
If you want to do a left join instead of an inner join you need to use custom 'sql fragments'
In your example,
relation.joins("left join measurements on measurements.id = measurement_type.measurements_id")
Or something along those lines dependent on your setup.
This tutorial sums it up pretty clearly.
MetaWhere gem has support for outer joins, among much other goodness:
https://github.com/ernie/meta_where
I have a fairly complicated lookup i'm trying to do in Rails and I'm not entirely sure how hoping someone can help.
I have two models, User and Place.
A user is related to Place twice. Once for visited_places and once for planned_places. Its a many to many relationship but using has_many :through. Here's the relationship from User.
has_many :visited_places
has_many :visited, :class_name=>"Place", :through=>:visited_places, :source=>:place
has_many :planned_places
has_many :planned, :class_name=>"Place", :through=>:planned_places, :source=>:place
In place the relationship is also defined. Here's the definition there
has_many :visited_users, :class_name=>"User", :through=>:visited_places
has_many :planned_users, :class_name=>"User", :through=>:planned_places
I'm trying to write a find on Place that returns all places in the database that aren't related to a User through either visited or planned. Right now I'm accomplishing this by simply querying all Places and then subtracting visited and planned from the results but I want to add in pagination and I'm worried this could complicate that. Here's my current code.
all_places = Place.find(:all)
all_places = all_places - user.visited - user.planned
Anyone know how i can accomplish this in just a call to Place.find. Also this is a Rails 3 app so if any of the active record improvements make this easier they are an option.
How about something like:
unvisited_places = Place.find(:all, :conditions => "id NOT IN(#{visited_places.map(&:place_id)})")
That's the general idea -- it can be made more efficient and convenient depending on your final needs.
You don't show it but if I am right in assuming that the VisitedPlace and PlannedPlace models have a belongs_to :user relationships then those tables have a user_id secondary key, right?
So in that case I would think it would be most efficient to do this in the database in which case you are looking for a select across a table join of places, visited_places and planned_places where users.id is not in either of visited_places or planned_places
in sql:
select * from places where id not in
(
(select place_id from visited_places where user_id = ?)
union
(select place_id from planned_places where user_id=?)
)
If that query works, you can use as follows:
Places.find_by_sql(...the complete sql query ...)
I would not know how to write such a query, with an exclusion, in Rails 3 otherwise.
I ran into a similar desire recently... I wanted to get all Model1s that weren't associated with a Model2. Using Rails 4.1, here's what I did:
Model1.where.not(id: Model2.select(:user_id).uniq)
This creates a nested SELECT, like #nathanvda suggested, effectively letting the database do all the work. Example SQL produced is:
SELECT "model1s".* FROM "model1s" WHERE ("model1s"."id" NOT IN (SELECT DISTINCT "model2s"."model1_id" FROM "model2s"))