【问题标题】:method in a model to match ALL association values模型中匹配所有关联值的方法
【发布时间】:2013-08-09 17:42:41
【问题描述】:

我有一个模型,我尝试“匹配所有”用户选择的标签。但是,一旦我在我的应用程序中选择了两个标签,并将参数传递给下面的方法,它就不会返回任何结果。一个选择就可以了。

如你所见,我有一个 Style 模型,它通过 Tagizations 有_many 个标签。我想“匹配所有”用户选择的标签,我在 filter_with_params 定义下的第三行是我对正确查询的尝试。

所以,如果我将 params[:t] = ["green", "yellow", "red"] 传递给过滤器方法,我只想返回包含所有这三个标签的样式.

这是模型中的一个 sn-p:

class Style < ActiveRecord::Base
    has_many :tagizations, :dependent => :destroy
    has_many :tags, :through => :tagizations

    def self.filter_with_params(params)
        scoped = self.where("styles.name != ''")
        scoped = scoped.includes(:tags)
        scoped = params[:t].inject(scoped){|memo, val| memo.where(:tags => {:name => val})} if params[:t]  
        scoped
    end
end

这是一个包含两个请求的开发日志输出的 Gist:

https://gist.github.com/jgrannas/6195528

使用 .TO_SQL

单标签(作品):

Style.filter_with_params({:t => ["engagement"]}).to_sql
    => "SELECT \"styles\".* FROM \"styles\"  WHERE \"tags\".\"name\" = 'engagement' AND (styles.name != '')" 

多个标签(不起作用)

Style.filter_with_params({:t => ["engagement", "Halo"]}).to_sql
     => "SELECT \"styles\".* FROM \"styles\"  WHERE \"tags\".\"name\" = 'engagement' AND \"tags\".\"name\" = 'Halo' AND (styles.name != '')

我正在使用 postgres

【问题讨论】:

    标签: ruby-on-rails ruby ruby-on-rails-3 postgresql activerecord


    【解决方案1】:

    这是你的问题:

    WHERE "tags"."name" = 'engagement' AND "tags"."name" = 'Halo'

    问题是您正在寻找具有 single 标签和 all 3 个名称的东西,这是不可能的。你想要的是每个名字都有一个标签的东西。

    不完全确定这是否可行,但请尝试以下方法:

    def self.filter_with_params(params)
        scoped = self.select("distinct styles.*, count(*) AS count")
        scoped = scoped.where("styles.name != ''")
        scoped = scoped.joins(:tags)
        if params[:t]
          scoped = scoped.where(tags: { name: params[:t] })
          scoped = scoped.group('styles.id')
          scoped = scoped.having("count = #{params[:t].size}")
        end
        scoped
    end
    

    我们在这里所做的基本上是构建一个表格,其中有一行用于每个 样式标签组合,但仅适用于params[:t] 中的标签。因此,例如,我们将有一行用于style1 - tag1,另一行用于style1 - tag2。然后我们按样式 id 对行进行分组,这样我们每个唯一样式只能得到一条记录。但我们也在计算每组中有多少行。我们只想要标签集中的每个标签都有一行的记录,所以我们丢弃其余的。

    注意:以上代码适用于 MySQL,但不适用于 postgres。 The documentation for postgres SELECT 说:

    “输出列的名称可用于在 ORDER BY 和 GROUP BY 子句中引用列的值,但不能在 WHERE 或 HAVING 子句中;您必须在此处写出表达式。”

    所以我们需要scoped = scoped.having("count(*) = #{params[:t].size}")

    【讨论】:

    • 这就是我最初的方式。就像那样。但它返回具有任何标签的任何样式。我需要一个 AND,所以如果我请求标签(绿色、黄色),我只想要具有这两个标签的样式,而不是所有具有黄色的样式和所有具有绿色的样式。有意义吗?
    • 我看到了你的编辑......对,我有点认为这是问题所在。尝试将一个标签名称匹配到 2 个或更多标签名称,这些名称永远不会返回任何内容
    • 我收到这个 postgres 错误:PG::Error: ERROR: column "count" does not exist LINE 1: ...ement') AND (styles.name != '') GROUP BY id有计数 = 1
    • 嗯,我用 MySQL 来测试这个。您在第一行添加了select 子句,对吗?那应该定义count
    • 查看 Postgres 文档,我看到:“输出列的名称可用于引用 ORDER BY 和 GROUP BY 子句中的列值,但不能用于 WHERE 或 HAVING 子句;那里你必须改为写出表达式。”所以也许试试scoped.having("count(*) = #{params[:t].size}")
    【解决方案2】:

    我想我更喜欢为每个标签注入exists SQL 子句。输出 SQL 应该是这样的

    SELECT styles.* 
    FROM styles
    WHERE styles.name != ''
    AND exists(SELECT * from tags where style_id = styles.id AND tags.name = 'red')
    AND exists(SELECT * from tags where style_id = styles.id AND tags.name = 'green')
    

    据我所知,您需要手动注入 SQL 来实现这一点。因此是这样的:

    def self.filter_with_params(params)
        scoped = self.where("styles.name != ''")
        scoped = scoped.includes(:tags)
        params[:t].each do |tag|
            scoped.where('EXISTS(select 1 FROM tags where tags.name = ? and tags.style_id = styles.id )', tag) 
        end
        scoped
    end
    

    【讨论】:

      【解决方案3】:

      试试这个:

      def self.with_tags *tags
        joins(:tags).where(:tags => {:name => tags}).group("#{self.table_name}.id").having("COUNT(DISTINCT tags.name) = ?", tags.length)
      end
      

      【讨论】:

        猜你喜欢
        • 2017-05-30
        • 1970-01-01
        • 1970-01-01
        • 2021-10-23
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-02-01
        • 1970-01-01
        相关资源
        最近更新 更多