【问题标题】:ActiveRecord query array intersection?ActiveRecord 查询数组交集?
【发布时间】:2013-09-20 19:23:01
【问题描述】:

我正在尝试计算某些类型文章的数量。我有一个非常低效的查询:

Article.where(status: 'Finished').select{|x| x.tags & Article::EXPERT_TAGS}.size

在我成为一名更好的程序员的过程中,我想知道如何让这个查询变得更快。 tagsArticle 中的一个字符串数组,Article::EXPERT_TAGS 是另一个字符串数组。我想找到数组的交集,并得到结果记录数。

编辑:Article::EXPERT_TAGSarticle.tags 被定义为 Mongo 数组。这些数组包含字符串,我相信它们是序列化的字符串。例如:Article.first.tags = ["Guest Writer", "News Article", "Press Release"]。不幸的是,这没有正确设置为单独的标签表。

第二次编辑:我使用的是 MongoDB,所以实际上它使用的是 MongoWrapper,如 MongoMapper 或 mongoid,而不是 ActiveRecord。这是我的一个错误,对不起!由于这个错误,它搞砸了对这个问题的分析。感谢 PinnyM 指出错误!

【问题讨论】:

  • 这个字符串数组是如何存储在数据库中的——作为一个单独的表(请表模式)或一个数组字段?您使用的是什么 DBMS?
  • 我似乎通过序列化数组将tags 的列表存储在单个列中(@John,请保持诚实)
  • 这是作为数组存储在 MongoDB 中的单个列中,而不是作为单独的表(回想起来这会让生活变得更轻松)。
  • 如果您使用的是 MongoDB,为什么不使用 Mongoid 而不是 ActiveRecord?

标签: ruby-on-rails arrays mongodb


【解决方案1】:

由于您使用的是 MongoDB,您还可以考虑针对数组交集使用 MongoDB 特定的解决方案(聚合框架),以便在获取最终结果之前让数据库完成所有工作。

查看这个 SO 线程 How to check if an array field is a part of another array in MongoDB?

【讨论】:

    【解决方案2】:

    假设整个 tags 列表存储在单个数据库字段中,并且您希望保持这种状态,我看不出有太大的改进空间,因为您需要将所有数据放入 Ruby 中进行处理.

    但是,您的数据库查询存在一个问题

    Article.where(status: 'Finished')
    
    # This translates into the following query
    SELECT * FROM articles WHERE status = 'Finished'
    

    本质上,您正在获取所有列,而您的流程只需要 tags 列。所以,你可以像这样使用pluck

    Article.where(status: 'Finished').pluck(:tags)
    
    # This translates into the following query
    SELECT tags FROM articles WHERE status = 'Finished'
    

    【讨论】:

    • ActiveRecord 内置支持将ArrayHash 序列化为单个列。如果为tags 定义text 列并分配tags = @your_array,它会被序列化并保存到数据库中,当您查询记录时,它会被反序列化为Array 对象。请参阅此 SO 线程,例如,stackoverflow.com/questions/6694432/…
    【解决方案3】:

    我在 ActiveRecord here 中回答了一个关于一般交集的问题,例如查询。

    摘录如下:


    以下是我在 ActiveRecord 中构建类似查询的交集的一般方法:

    class Service < ActiveRecord::Base
      belongs_to :person
    
      def self.with_types(*types)
        where(service_type: types)
      end
    end
    
    class City < ActiveRecord::Base
      has_and_belongs_to_many :services
      has_many :people, inverse_of: :city
    end
    
    class Person < ActiveRecord::Base
      belongs_to :city, inverse_of: :people
    
      def self.with_cities(cities)
        where(city_id: cities)
      end
    
      # intersection like query
      def self.with_all_service_types(*types)
        types.map { |t|
          joins(:services).merge(Service.with_types t).select(:id)
        }.reduce(scoped) { |scope, subquery|
          scope.where(id: subquery)
        }
      end
    end
    
    Person.with_all_service_types(1, 2)
    Person.with_all_service_types(1, 2).with_cities(City.where(name: 'Gold Coast'))
    

    它会生成如下形式的SQL:

    SELECT "people".*
      FROM "people"
     WHERE "people"."id" in (SELECT "people"."id" FROM ...)
       AND "people"."id" in (SELECT ...)
       AND ...
    

    只要每个子查询在其结果集中返回匹配人员的 id,您就可以根据任何条件/连接等使用上述方法创建任意数量的子查询。

    每个子查询结果集将被“与”在一起,从而将匹配集限制为所有子查询的交集。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-04-21
      • 2014-08-13
      • 1970-01-01
      • 1970-01-01
      • 2019-12-19
      • 1970-01-01
      相关资源
      最近更新 更多