我可以在一个活动记录查询中找到有书A和书(B,C或D)的书架吗?

时间:2017-03-07 10:39:50

标签: ruby-on-rails ruby activerecord

想象一下这些联想:

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查询在一行中完成此操作。

1 个答案:

答案 0 :(得分:1)

这个问题可归结为您正在寻找集合Bookshelf中的A个对象的交集(包含ID为a的图书)和{{1}集Bookshelf中的对象(包含数组B中包含ID的书)。

我不记得看到使用单个b查询表达此交叉点的简单方法。正如您可能怀疑的那样,多查询方法不能很好地扩展。为什么在运行一个查询时运行三个查询?

所以这是我的解决方案:

查找Bookshelve ID

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 JOINb1的交集。我们仅检索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