想象这些关联:
class Bookshelf
has_many :book_associations, dependent: :destroy
has_many :books, through: :book_associations
end
class Book
has_many :book_associations, dependent: :destroy
has_many :bookshelves, through: :book_associations
end
class BookAssociation
belongs_to :book
belongs_to :bookshelf
end
我需要找到所有包含ID A的书架和带ID B,C或D
的书籍我可以在多步骤过程中使用Ruby这样做:
bookshelf_ids1 = Book.find(A).bookshelves.pluck(:id)
bookshelf_ids2 = Book.where(id: [B, C, D]).map(&:bookshelves).flatten.uniq.pluck(:id)
Bookshelf.where(id: bookshelf_ids1 & bookshelf_ids2)
,但是必须有一种方法可以通过ActivereCord或RAW SQL查询来完成此操作。
这个问题归结为您正在寻找set A
中的 Bookshelf
对象的相交(包含带有id a
的书)和 Bookshelf
对象,set B
(包含带有ID的书籍)在数组b
中)。
我不记得看到使用单个ActiveRecord
查询来表达此相交的任何简单方法。而且您可能怀疑,多个查询方法无法很好地扩展。当您可以运行一个时,为什么要运行三个查询?
所以这是我的解决方案:
查找书架IDS
SELECT DISTINCT b1.bookshelf_id
FROM book_associations b1
INNER JOIN book_associations b2 ON b1.bookshelf_id = b2.bookshelf_id
WHERE b1.book_id = 1 AND b2.book_id IN (2,3,4);
这有点复杂,所以让我将其分解。我们正在自行加入book_associations
表,并自行加入bookshelf_id
。这具有使两个表可用于过滤的效果。然后,我们将查询表b1
滤波为第一个条件(ID = 1
),并为其他条件(ID in (2,3,4)
)过滤查询表b2
。使用INNER JOIN
,我们将确保我们仅获得表b1
和b2
的交点。我们仅检索bookshelf_id
,因为我们只想检索书架。最后,DISTINCT
查询是.uniq
等效的SQL,并确保返回的值是唯一的。
检索书架
从这里开始,我们需要实例化Bookshelf
对象。虽然我们可以这样做:
bookshelf_ids = ActiveRecord::Base.connection.query(["SELECT DISTINCT b1.bookshelf_id
FROM book_associations b1
INNER JOIN book_associations b2 ON b1.bookshelf_id = b2.bookshelf_id
WHERE b1.book_id = ? AND b2.book_id IN (?);", 1, [2,3,4]])
bookshelves = Bookshelf.find(bookshelf_ids)
仍然是两个步骤。这是一个单步解决方案:
Bookshelf.find_by_sql(["SELECT * FROM bookshelves bs
WHERE bs.id IN (SELECT DISTINCT b1.bookshelf_id
FROM book_associations b1
INNER JOIN book_associations b2 ON b1.bookshelf_id = b2.bookshelf_id
WHERE b1.book_id = ? AND b2.book_id IN (?)
)", first_id,second_ids])
Bookshelf.select_by_sql
命令从查询结果实例化记录。bookshelf_ids
在子查询中检索,并用作bookshelves
表上查询的条件。
警告
我尚未测试此代码,无法确认它会起作用,但是广泛的笔触应该是正确的。
SQL应对PostgreSQL有效,但可能需要根据您的特定DB实现进行一些调整。
编辑
我已经纠正了上面的代码,我在错误的列(id
)上加入了book_associations
表,而不是正确的列bookshelf_id
,并且该子查询返回错误的列(在应该是bookshelf_id
时再次id
)。
我已经创建了一个包括测试的概念证明。