【问题标题】:Searching serialized data, using active record使用活动记录搜索序列化数据
【发布时间】:2012-04-06 13:29:40
【问题描述】:

我正在尝试对序列化列进行简单查询,您是如何做到的?

serialize :mycode, Array


1.9.3p125 :026 > MyModel.find(104).mycode
  MyModel Load (0.6ms)  SELECT `mymodels`.* FROM `mymodels` WHERE `mymodels`.`id` = 104 LIMIT 1
 => [43565, 43402] 
1.9.3p125 :027 > MyModel.find_all_by_mycode("[43402]")
  MyModel Load (0.7ms)  SELECT `mymodels`.* FROM `mymodels` WHERE `mymodels`.`mycode` = '[43402]'
 => [] 
1.9.3p125 :028 > MyModel.find_all_by_mycode(43402)
  MyModel Load (1.2ms)  SELECT `mymodels`.* FROM `mymodels` WHERE `mymodels`.`mycode` = 43402
 => [] 
1.9.3p125 :029 > MyModel.find_all_by_mycode([43565, 43402])
  MyModel Load (1.1ms)  SELECT `mymodels`.* FROM `mymodels` WHERE `mymodels`.`mycode` IN (43565, 43402)
 => [] 

【问题讨论】:

    标签: ruby-on-rails search activerecord serialization


    【解决方案1】:

    这只是一个不减慢您的应用程序速度的技巧。你必须使用.to_yaml

    确切结果:

    MyModel.where("mycode = ?", [43565, 43402].to_yaml)
    #=> [#<MyModel id:...]
    

    仅针对 MySQL 测试。

    【讨论】:

    • 也适用于 Postgres,至少,我因此能够找到具有空序列化字段 foo 的对象: where("foo = ?", {}.to_yaml).last
    • where("foo = ?", [].to_yaml) 用于序列化数组
    • 这仅适用于完全匹配。如果您使用MyModel.where("mycode LIKE ?", "%#{[43565, 43402].to_yaml}%"),它也适用于部分匹配。
    • 其实诀窍不在 to_yaml 中,而是在“mycode = ?”中。这会将来自 to_yaml 的值插入为 '---\\n- 43565\\n- 43402\\n'。使用 key: value 即使与 to yaml 也会表现不同:'--- |\\n ---\\n - 43565\\n - 43402\\n'
    【解决方案2】:

    基本上,你不能。 #serialize 的缺点是您绕过了数据库的本机抽象。您几乎只能加载和保存数据。

    也就是说,降低应用程序爬行速度的一种非常好的方法可能是:

    MyModel.all.select { |m| m.mycode.include? 43402 }
    

    故事的寓意:不要将#serialize 用于您需要查询的任何数据。

    【讨论】:

    • 不得不哈哈这个答案,这是我最喜欢的减慢速度的方法之一 :)
    • 这不再是真的。对于大多数 Rails 4 应用程序,基本上可以。有关详细信息,请参阅下面的 konung 的答案和我的答案。
    【解决方案3】:

    序列化数组以特定方式存储在数据库中,例如:

    [1, 2, 3, 4]
    in
    1\n 2\n 3\n etc
    

    因此查询将是

    MyModel.where("mycode like ?", "% 2\n%")
    

    %2 之间放置空格。

    【讨论】:

    • 你救了我的命,伙计!!感谢力量无限!
    • 如果你有两个值,比如“0597841”和“1597841”,它只能找到一个,因为"0597841".to_yaml #=&gt; "--- 0597841\n...\n""1597841".to_yaml #=&gt; "--- '1597841'\n"的格式完全不同。因此,我不喜欢to_yaml 和序列化数据。
    • 哇,这么简单。谢谢!
    【解决方案4】:

    Noodl 的回答是对的,但并不完全正确。

    这实际上取决于您使用的数据库/ORM 适配器:例如 PostgreSQL 现在可以存储和搜索哈希/json - 查看 hstore。我记得读过 PostgreSQl 的 ActiveRecord 适配器现在可以正确处理它。如果您使用 mongoid 或类似的东西 - 那么您在任何地方都在数据库级别使用非结构化数据(即 json)。

    但是,如果您使用的数据库无法真正处理散列 - 例如 MySQL / ActiveRecord 组合 - 那么您使用序列化字段的唯一原因是您可以在某些后台进程中创建/写入并显示/按需输出——根据我的经验,我发现的唯一两种用途是一些报告(比如产品模型上的统计字段——我需要存储产品的一些平均值和中位数)和用户选项(比如他们喜欢的模板颜色- 我真的不需要查询) - 但是用户信息 - 例如他们订阅邮件列表 - 需要可搜索电子邮件爆炸。

    PostgreSQL hstore ActiveRecord 示例:

    MyModel.where("mycode @> 'KEY=>\"#{VALUE}\"'")
    

    更新 截至 2017 年,MariaDB 和 MySQL 都支持 JSON 字段类型。

    【讨论】:

    • 很好的资源,展示了 Rails AR 查询和如何做到这一点blog.engineyard.com/2013/…
    • 我遇到过这样一种情况,我希望为特定订单的每个包裹提供一组简单的字符串化发货日期(例如,Order.ship_dates 将返回 ['2021-4-26', '2021-4 -27'],我想要一个 ActiveRecord 查询来返回当天需要发货的订单。范围如下所示:scope :ship_today, -> { where("ship_dates @> '{#{Date.today. to_s}}'") } 然后我可以使用:Order.ship_today 进行日常订单。希望这对其他人有用。
    【解决方案5】:

    您可以使用 sql LIKE 语句查询序列化列。

     MyModel.where("mycode LIKE '%?%'", 43402)
    

    这比使用 include? 更快,但是,您不能使用数组作为参数。

    【讨论】:

    • 这个问题是它也会匹配像 143402 或 434020 这样的记录。如果你想使用LIKE,你最好使用上面 jbmyid 的答案。
    • 好主意。如果你想搜索一个序列化的字符串数组(就像我一样),这是一个简单的方法:where('column LIKE ?', "%#{variable}%")
    【解决方案6】:

    好消息!如果您将 PostgreSQL 与 hstore 一起使用(这在 Rails 4 中非常简单),您现在可以完全搜索序列化数据。 This 是一个方便的指南,here 是来自 PG 的语法文档。

    在我的例子中,我有一个字典作为哈希存储在名为amenities 的 hstore 列中。我想检查几个查询的设施,其哈希值为1,我只是这样做

    House.where("amenities @> 'wifi => 1' AND amenities @> 'pool => 1'")
    

    万岁改进!

    【讨论】:

      【解决方案7】:

      There's a blog post from 2009 from FriendFeed 描述了如何在 MySQL 中使用序列化数据。

      您可以做的是创建表,作为您要搜索的任何数据的索引。

      创建一个包含可搜索值/字段的模型

      在您的示例中,模型看起来像这样:

      class MyModel < ApplicationRecord
        # id, name, other fields...
        serialize :mycode, Array
      end
      
      class Item < ApplicationRecord
        # id, value...
        belongs_to :my_model
      end
      

      为可搜索字段创建“索引”表

      当你保存 MyModel 时,你可以这样做来创建索引:

      Item.where(my_model: self).destroy
      self.mycode.each do |mycode_item|
        Item.create(my_model: self, value: mycode_item)
      end
      

      查询和搜索

      然后,当您要查询和搜索时,只需执行以下操作:

      Item.where(value: [43565, 43402]).all.map(&:my_model)
      Item.where(value: 43402).all.map(&:my_model)
      

      您可以向 MyModel 添加一个方法以使其更简单:

      def find_by_mycode(value_or_values)
        Item.where(value: value_or_values).all.map(&my_model)
      end
      
      MyModel.find_by_mycode([43565, 43402])
      MyModel.find_by_mycode(43402)
      

      为了加快速度,您需要为该表创建一个 SQL 索引。

      【讨论】:

      【解决方案8】:

      在这篇文章中使用以下 cmets

      https://stackoverflow.com/a/14555151/936494

      https://stackoverflow.com/a/15287674/936494

      我成功地在我的模型中查询了一个序列化的哈希

      class Model < ApplicationRecord
        serialize :column_name, Hash
      end
      

      column_name 持有像

      这样的 Hash
      { my_data: [ { data_type: 'MyType', data_id: 113 } ] }
      

      我们可以通过以下方式查询

      Model.where("column_name = ?", hash.to_yaml)
      

      生成类似的 SQL 查询

      Model Load (0.3ms)  SELECT "models".* FROM "models" WHERE (column_name = '---
      :my_data:
      - :data_type: MyType
        :data_id: 113
      ')
      

      如果有人有兴趣在 SQL 终端中执行生成的查询,它应该可以工作,但应注意值以精确的格式存储在 DB 中。但是我在PostgreSQL newline character 找到了另一种简单的方法来使用包含换行符的原始字符串

      select * from table_name where column_name = E'---\n:my_data:\n- :data_type: MyType\n  :data_id: 113\n'
      

      上述查询中最重要的部分是E

      注意:我上面执行的数据库是PostgreSQL。

      【讨论】:

      • 谢谢。在 ActiveRecord 变量替换中使用 .to_yaml 是诀窍。
      【解决方案9】:

      要搜索序列化列表,您需要为数据添加前缀和后缀。

      例子:

      而不是类似的东西:

      2345,12345,1234567 这会导致您尝试搜索 2345 的问题,而是执行类似 &lt;2345&gt;,&lt;12345&gt;,&lt;1234567&gt; 的操作并搜索 &lt;2345&gt;(搜索查询已转换)。当然,前缀/后缀字符的选择取决于将要存储的有效数据。如果您希望使用&lt; 并且可能使用|,则可以改用||| 之类的东西。当然,这会增加字段使用的数据,并可能导致性能问题。

      使用 trigrams 索引或其他东西可以避免潜在的性能问题。

      您可以像data.map { |d| "&lt;#{d}&gt;" }.join(',') 一样对其进行序列化,并通过data.gsub('&lt;').gsub('&gt;','').split(',') 对其进行反序列化。序列化程序类可以很好地加载/提取数据。

      执行此操作的方法是将数据库字段设置为文本并使用带有自定义 lib 类的 rail 的 serialize 模型方法。 lib类需要实现两个方法:

      def self.dump(obj) # (returns string to be saved to database) def self.load(text) # (returns object)

      以持续时间为例。从文章中提取,因此链接腐烂不会得到它,请访问文章以获取更多信息。该示例使用单个值,但序列化值列表并使用上述方法反序列化列表相当简单。

      class Duration
        # Used for `serialize` method in ActiveRecord
        class << self
          def load(duration)
            self.new(duration || 0)
          end
      
          def dump(obj)
            unless obj.is_a?(self)
              raise ::ActiveRecord::SerializationTypeMismatch,
                "Attribute was supposed to be a #{self}, but was a #{obj.class}. -- #{obj.inspect}"
            end
      
            obj.length
          end
        end
      
      
        attr_accessor :minutes, :seconds
      
        def initialize(duration)
          @minutes = duration / 60
          @seconds = duration % 60
        end
      
        def length
          (minutes.to_i * 60) + seconds.to_i
        end
      end
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-04-15
        • 2016-05-24
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多