我可以在一个ActivereCord查询中找到一个书架上有A和书籍(B,C或D)的书架



想象这些关联:

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,我们将确保我们仅获得表b1b2的交点。我们仅检索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)。

我已经创建了一个包括测试的概念证明。

最新更新