想象一下这些联想:
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或原始SQL查询在一行中完成此操作。
答案 0 :(得分:1)
这个问题可归结为您正在寻找集合Bookshelf
中的A
个对象的交集(包含ID为a
的图书)和{{1}集Bookshelf
中的对象(包含数组B
中包含ID的书)。
我不记得看到使用单个b
查询表达此交叉点的简单方法。正如您可能怀疑的那样,多查询方法不能很好地扩展。为什么在运行一个查询时运行三个查询?
所以这是我的解决方案:
ActiveRecord
这有点复杂,所以让我分解一下。我们自己加入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
)过滤查询表b2
。使用ID in (2,3,4)
,我们确保只获得表INNER JOIN
和b1
的交集。我们仅检索b2
,因为我们只想查找书架。最后,bookshelf_id
查询是DISTINCT
的SQL等价物,并确保返回的值是唯一的。
从这里开始,我们需要实例化.uniq
个对象。虽然我们可以这样做:
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
表上查询的条件。
我还没有对此代码进行测试,但无法确认它是否有效,但广泛的笔画应该是正确的。
SQL应该对PostgresQL有效,但可能需要一些调整,具体取决于您的特定数据库实现。
我已经更正了上面的代码,我已经在错误列(bookshelves
)上加入book_associations
表而不是正确的列id
,并且子查询返回错误的列(再次bookshelf_id
,它应该是id
)。
我已经创建了一个包含测试的proof of concept。