I am making a rails 4 app for a 'question and answer' forum. I have a model Micropost. I have a model QuestionAnswer which connects different microposts as questions and answers:
Class Micropost < ActiveRecord::Base
has_many question_answers, foreign_key: "question_id"
has_many answers, through: :question_answers
has_one :reverse_question_answer, foreign_key: "answer_id", class_name: "QuestionAnswer"
has_one :question, through: :reverse_question_answer
end
Class QuestionAnswer < ActiveRecord::Base
belongs_to :question, class_name: "Micropost"
belongs_to :answer, class_name: "Micropost"
end
Now I want to do a database query. I want to use two LEFT OUTER JOINs to create a joint table with all questions and answers:
Micropost.
joins('LEFT OUTER JOIN question_answers ON question_answers.question_id = microposts.id').
joins('LEFT OUTER JOIN microposts ON microposts.id = question_answers.answer_id')
I receive the error message
SQLite3::SQLException: ambiguous column name: microposts.id SELECT "microposts".* FROM "microposts" LEFT OUTER JOIN question_answers ON question_answers.question_id = microposts.id LEFT OUTER JOIN microposts ON microposts.id = question_answers.answer_id ORDER BY created_at DESC
I replaced micropost with answers in the second joins command above, but it complains there is no such table. How can I do two joins? -Thanks.
The solution is to use the following piece of code:
Micropost.
joins('LEFT OUTER JOIN question_answers a ON a.question_id = microposts.id').
joins('LEFT OUTER JOIN microposts m ON m.id = a.answer_id')
As per your question about aliases, the a alias stands for the whole question_answers table. In this particular example, it's just for convenience. However, the m alias, used for microposts, is not.
Here we define that m is an alias for microposts table to signal that it's a different table than the one you are joining before using a.question_id = microposts.id (here the microposts is the first 'instance' of microposts table, and in the next JOIN you define another instance to which you give an m alias).
Not sure if I explained this clearly, look here:
SELECT e.name, mgr.name
FROM employees e
JOIN employees mgr ON (e.manager_id = mgr.id)
;
Here we have again two instances of employees table - we join rows from both tables to get employee's name and his manager's name. Aliases are required here because otherwise it would be unclear which column is taken from which table.
If you would like to learn more about JOIN, you can read:
http://www.w3schools.com/sql/sql_join.asp
Or (there are further links about JOINs, nicely shown and explained, hope it will be useful to you):
What is the difference between "INNER JOIN" and "OUTER JOIN"?
Related
I have three tables: users, locations, locations_users, The associations are as follows :
user has_many locations_users
user has_many locations :through => locations_users
location has_many locations_users
location has_many users :through => locations_users
How would I find all the users who are joined to location_id = 5 in one query?
Any help would be appreciated.
You can use LEFT OUTER JOIN. It fetches all data from the left table with matching data from right, if present (if not present, join column is null):
User
.joins('LEFT OUTER JOIN locations_users ON locations_users.user_id = users.id')
.where('locations_users.id IS NULL OR locations_users.location_id = ?', 5)
.all
Hovewer:
this query uses join, which's performance can be poor on big tables (maybe two queries will perform faster - test it!)
for better performance, make sure, that indexes are set on joined columns
I don't know 1 query way but the solution below is efficient.
a = User.where("id NOT IN (?)", LocationUser.pluck("DISTINCT user_id"))
b = LocationUser.where(location_id: 5).pluck("DISTINCT user_id")
result = User.where(id: a+b)
I currently have a locations and users table, with a joining table locations_users.
class Location < ActiveRecord::Base
has_and_belongs_to_many :users
end
class User < ActiveRecord::Base
has_and_belongs_to_many :locations
end
thins like location.users work correctly now. What I would like to achieve is something like: location.members
In straight SQL, this gets what I'm looking for:
select * from locations l left outer join locations_users lu on lu.location_id=l.id left outer join users u on lu.user_id=u.id where l.location_id=1 and lu.member=true;
I have a relationship where restaurants can have many users through a middle table called actions, and users has many restaurants through actions as well.
I am trying to query all restaurants and only include users that meet certain conditions; the restaurant should be returned regardless if there are no users that meet searched criteria, or if there are no users associated with it at all. My models are as follows.
Restaurant model:
has_many :actions
has_many :users, through: :actions
actions model:
belongs_to :user
belongs_to :restaurant
user model:
has_many :actions
has_many :restaurants, through: :actions
What you need to do for this situation is use a left outer join. If you just create the join to find users with as specific attribute like:
Restaurant.joins(:users).where("users.name LIKE 'user_name'")
an inner join is implicitly created which will leave out all of the restaurants without users. To include them do something like this:
Restaurant.joins(["LEFT OUTER JOIN actions on restaurants.id = actions.user_id", "LEFT OUTER JOIN users on users.id = actions.user_id"]).where("users.name LIKE 'James' OR users.name IS NULL ")
I have a simple has_many :through arrangement, as shown below
# employee.rb
class Employee < ActiveRecord::Base
has_many :group_assignments
has_many :groups, through: :group_assignments
# ...
end
# group.rb
class Group < ActiveRecord::Base
has_many :group_assignments
has_many :employees, through: :group_assignments
# ...
end
# group_assignment.rb
class GroupAssignment < ActiveRecord::Base
belongs_to :employee
belongs_to :group
end
I have a list of employees. For that list, I want to grab every group that contains at least one of the employees on that list. How would I accomplish this in a manner that isn't horridly inefficient? I'm newish to Rails and very new at SQL, and I'm pretty at a loss. I'm using SQLite in development and PostgreSQL in production.
For a list of employees named employees_list, this will work:
Group.includes(:employees).where('employees.id' => employees_list.map(&:id))
This is roughly the kind of SQL you will get:
SELECT "groups"."id" AS t0_r0,
"groups"."created_at" AS t0_r1, "groups"."updated_at" AS t0_r2,
"employees"."id" AS t1_r0, "employees"."created_at" AS t1_r1, "employees"."updated_at" AS t1_r2
FROM "groups"
LEFT OUTER JOIN "group_assignments" ON "group_assignments"."group_id" = "groups"."id"
LEFT OUTER JOIN "employees" ON "employees"."id" = "group_assignments"."employee_id"
WHERE "employees"."id" IN (1, 3)
So what is happening is that groups and group_assignments tables are first being joined with a left outer join (matching the group_id column in the group_assignments table to the id column in the groups table), and then employees again with a left outer join (matching employee_id in the group_assignments table to the id column in the employees table).
Then after that we're selecting all rows where 'employees'.'id' (the id of the employee) is in the array of employees in the employee list, which we get by mapping employees_list to their ids using map: employees_list.map(&:id). The map(&:id) here is shorthand for: map { |e| e.id }.
Note that you could use joins instead of includes here, but then you would get duplicates if one employee is a member of multiple groups. Kind of subtle but useful thing to know.
Hope that makes sense!
This is the general idea, but depending on your data, you may need to select distinct.
Group.includes(:group_assignments => :employee).where(:employee => {:id => ?}, #employees.map(&:id))
try
Group.joins(:group_assignments).where("group_assignments.employee_id in (?)", #employees.map(&:id))
Specifically, I'm trying to figure out if it's possible to generate SQL that does what I want to feed into Ruby-on-Rails' find_by_sql method.
Imagine there are Users, who are joined cyclically to other Users by a join table Friendships. Each User has the ability to create Comments.
I'd like a SQL query to return the latest 100 comments created by any friends of a given user so I can display them all in one convenient place for the user to see.
This is tricky, since essentially I'm looking to filter the comments by whether their foreign keys for their author are contained in a set of keys obtained derived from the user's friends' primary keys.
Edit: Clarifying the setup. I'm not exactly sure how to write a schema definition, so I'll describe it in terms of Rails.
class User
has_many :friends, :through => :friendships
has_many :comments
end
class Friendship
belongs_to :user
belongs_to :friend, :class_name => "User", :foreign_key => "friend_id"
end
def Comment
has_one :User
end
It's not that tricky, you just use joins. To get just the comments you only need to join the Friendships table and the Comments table, but you probably also want some information from the Users table for the person who wrote the comment.
This would get the last 100 comments from people who are friends with the user with id 42:
select top 100 c.CommentId, c.CommentText, c.PostDate, u.Name
from Friendships f
inner join Users u on u.UserId = f.FriendUserId
inner join Comments c on c.UserId = u.UserId
where f.UserId = 42
order by c.PostDate desc