如何根据另一个表中的HABTM关系从一个表中选择项目? Postgres" ALL"不起作用,

时间:2015-02-27 04:36:11

标签: sql ruby-on-rails postgresql ruby-on-rails-4

我正在尝试检索具有某些类别的漫画(漫画)。例如,在下面的代码中,我试图搜索冒险(id = 29)和喜剧(id = 25)漫画。我正在使用“ALL”运算符,因为我希望两个类别都在漫画中。 (即通过关系表返回所有同时具有25和29类别的漫画,但也可以附加其他类别)

@search = Manga.find_by_sql("
    SELECT m.*
    FROM mangas m
    JOIN categorizations c ON c.manga_id = m.id AND c.category_id = ALL (array[29,25])
")

有问题吗?查询无法正常工作(可能我误解了所有运算符)。我从查询中得不到任何回报。 所以我试着把它改成

JOIN categorizations c ON c.manga_id = m.id AND c.category_id >= ALL (array[29,25])

我找回了ID大于29的漫画。我甚至没有获得第29类。 我在这里缺少什么吗?

此外,查询......非常慢。如果有人带来一个可以返回我想要的查询,我将不胜感激。

我正在使用Ruby on Rails 4.2和postgresql 感谢

更新(发布模型关系)

class Manga < ActiveRecord::Base
  has_many :categorizations, :dependent => :destroy
  has_many :categories, through: :categorizations
end
class Category < ActiveRecord::Base
  has_many :categorizations, :dependent => :destroy
  has_many :mangas, through: :categorizations
end
class Categorization < ActiveRecord::Base
  belongs_to :manga
  belongs_to :category
end

我的尝试基于@Beartech回答:

    wheres = categories_array.join(" = ANY (cat_ids) AND ")+" = ANY (cat_ids)"
    @m = Manga.find_by_sql("
    SELECT mangas.*
    FROM
      (SELECT manga_id, cat_ids
       FROM
       (
         SELECT c.manga_id, array_agg(c.category_id) cat_ids
         FROM categorizations c GROUP BY c.manga_id
        )
        AS sub_table1 WHERE #{wheres}
      )
      AS sub_table2
      INNER JOIN mangas ON sub_table2.manga_id = mangas.id
    ")

2 个答案:

答案 0 :(得分:1)

我将此作为一个不同的答案添加,因为我喜欢另外一个出于历史原因。它完成了工作,但效率不高,所以也许有人会看到它可以改进的地方。那说......

回答!!!

这一切都回到了Postgresql函数ALL并不是你想要的。您需要“CONTAINS”运算符,即@>。您还需要某种聚合函数,因为您希望将每个Manga与其所有类别进行匹配,只选择包含25和29的那些。

这是sql:

SELECT manga.*
FROM
  (SELECT manga_id, cat_ids
   FROM
     (SELECT manga_id, array_agg(category_id) cat_ids
      FROM categorizations GROUP BY manga_id)
       AS sub_table1 WHERE cat_ids @> ARRAY[25,29] )
    AS sub_table2
  INNER JOIN manga
    ON sub_table2.manga_id = manga.id
;

所以你要拉一个子查询来抓取连接表中的所有匹配行,将它们的类别ID放入一个数组中,然后按漫画ID进行分组。现在你可以加入漫画表来获取实际的漫画记录

红宝石看起来像:

@search = Manga.find_by_sql("SELECT manga.* FROM (SELECT manga_id, cat_ids FROM (SELECT manga_id, array_agg(category_id) cat_ids FROM categorizations GROUP BY manga_id) AS sub_table1 WHERE cat_ids @> ARRAY[25,29] ) AS sub_table2 INNER JOIN manga ON sub_table2.manga_id = manga.id

它快速而干净,在本机SQL中完成所有操作。

您可以将变量插入.find_by_sql()文本。这为您提供了即时搜索功能,因为@>询问类别数组是否包含所有搜索词。

terms = [25,29]
q = %Q(SELECT manga.* FROM (SELECT manga_id, cat_ids FROM (SELECT manga_id, array_agg(category_id) cat_ids FROM categorizations GROUP BY manga_id) AS sub_table1 WHERE cat_ids @> ARRAY#{terms} ) AS sub_table2 INNER JOIN manga ON sub_table2.manga_id = manga.id")
Manga.find_by_sql(q)

重要

我相当确定上面的代码在某种程度上是不安全的。我假设您将以某种方式验证数组的输入,即

terms.all? {|term| term.is_a? Integer} ? terms : terms = []

答案 1 :(得分:0)

魅力第三次吧?大声笑

好的,完全改变了我的答案,因为看起来这应该在Rails中非常容易,但它已经让我感到难过......

我非常依赖This answer来提出这个问题。你应该在你的漫画模型中放置一个范围:

class Manga < ActiveRecord::Base
  has_many :categorizations, :dependent => :destroy
  has_many :categories, through: :categorizations

  scope :in_categories, lambda { |*search_categories|
    joins(:categories).where(:categorizations => { :category_id => search_categories } )
    }

end

然后称之为:

@search = Manga.in_categories(25,29).group_by {|manga| ([25,29] & manga.category_ids) == [25,29]}

这将遍历包含两个类别中的至少一个或多个的所有Manga,使用[25,29]中的数组对manga.category_ids数组进行“集合”并检查以查看如果该组等于您的请求集。这消除了只有两个键之一的所有漫画。

@search现在将是一个包含两个键truefalse的哈希:

{true =>  [#<Manga id: 9, name: 'Guardians of...

     .... multiple manga objects that belong to at least 
          the two categories requested but not eliminated if
           they also belong to a third of fourth category ... ]

 false => [ ... any Manga that only belong to ONE of the two
                categories requested ...  ]
     }

现在要获得属于BOTH类别的独特Mangas,请使用.uniq:

@search[true].uniq

BOOM !!你有一组Manga对象,它们与你的类别相匹配。

<强> OR

您可以使用以下方式简化它:

@search = Manga.in_categories(25,29).keep_if {|manga| ([25,29] & manga.category_ids) == [25,29]}
@search.uniq!

我更喜欢它,它看起来更干净。

现在为您提供SQL JUNKIES

@search = Manga.find_by_sql("Select * 
  FROM categorizations
   JOIN manga ON categorizations.manga_id = manga.id 
   WHERE categorizations.cateogry_id IN (25,29)").keep_if {|manga| ([25,29] & manga.category_ids) == [25,29]}

@search.uniq!

*好的确定我会在此之后停止。 :-) *

将它全部放入Manga.rb中的范围:

scope :in_categories, lambda { |*search_categories|
joins(:categories).where(:categorizations => { :category_id => search_categories } ).uniq!.keep_if {|manga| manga.category_ids.include? search_categories[0] and manga.category_ids.include? search_categories[1]} }

有更简单的方法吗? (实际上,最后一个很容易)