1

Given two models Player and Team how do I select all teams that two players specific players belong to?

Example Rails models and db table setup:

Rails models:

class Player < ActiveRecord::Base
  has_and_belongs_to_many :teams, uniq: true
end
class Team < ActiveRecord::Base
  has_and_belongs_to_many :players, uniq: true
end

SQL Tables

    teams         players            players_teams
+----------------------------------------------------+
| id | name |   | id | name |  | player_id | team_id |
+----+------+   +----+------+  +-----------+---------+
|  1 | Foo  |   |  1 | Jack |  |      1    |    1    |
|  2 | Bar  |   |  2 | Jill |  |      1    |    2    |
|    |      |   |    |      |  |      2    |    1    |

How do I select all teams that have both Jack and Jill on them? (In this case, just team Foo.)

Erwin Brandstetter
  • 605,456
  • 145
  • 1,078
  • 1,228
Andrew Hubbs
  • 9,338
  • 9
  • 48
  • 71
  • This is a typical case of relational division. We assembled a whole arsenal of query techniques [under this related question.](http://stackoverflow.com/questions/7364969/how-to-filter-sql-results-in-a-has-many-through-relation/7774879) – Erwin Brandstetter Mar 08 '14 at 00:27
  • Ah, thanks Erwin. My question is actually an exact duplicate of that one. I didn't see that one earlier. – Andrew Hubbs Mar 08 '14 at 00:29
  • Once you know the term ("relational division"), it's easier to search. – Erwin Brandstetter Mar 08 '14 at 00:30
  • Yeah, I spent a while trying to decide how to even word what I was talking about. It would appear relational division was the exact thing I was searching for. Thanks again. – Andrew Hubbs Mar 08 '14 at 00:31

4 Answers4

2

This is a very tricky question! It is not just an OR/AND in a where clause. Here you are a possible solution in a single SQL query using GROUP BY and MAX

SELECT t.*
  FROM teams t 
  JOIN players_teams pt ON t.id = pt.team_id
  JOIN players p ON p.id = pt.player_id
WHERE p.name IN ('Jack', 'Jill')
GROUP BY t.id
HAVING MAX(p.name = 'Jack') = 1 AND MAX(p.name = 'Jill') = 1

SQL Fiddle

In ActiveRecord should be something like this (untested)

Team.joins(:players).where("players.name IN (?)", ["Jack", "Jill"])
.group("teams.id")
.having("MAX(players.name = ?) = 1 AND MAX(players.name = ?) = 1", "Jack", "Jill")
Rafa Paez
  • 4,820
  • 18
  • 35
1

You can get the intersection of two separate queries like this

Team.includes(:players).where(players: { name: 'Jack' }) & Team.includes(:players).where(players: { name: 'Jill' })

For a single query, try the following

Team.includes(:players).where(players: { name: ['Jack', 'Jill'] }).group('teams.id').having('COUNT(players.id) = 2')
Carlos Ramirez III
  • 7,314
  • 26
  • 33
0
Teams.all(:include => :player, conditions => ["player.name in (?)", ["Jack","Jill"]])
slapthelownote
  • 4,249
  • 1
  • 23
  • 27
0

This is the solution I came up with that uses a subquery. Does anyone have a cleaner more AREL way of accomplishing this?

SELECT te1.* FROM (
  SELECT DISTINCT "teams".* FROM "teams"
  INNER JOIN "players_teams" ON "teams"."id" = "players_teams"."team_id"
  WHERE "players_teams"."player_id" = 2
) te1 INNER JOIN "players_teams" ON "te1"."id" = "players_teams"."team_id"
  WHERE "players_teams"."player_id" = 1;
Andrew Hubbs
  • 9,338
  • 9
  • 48
  • 71