使用 Postgres 查询哈希数组

ale*_*zko 2 postgresql activerecord ruby-on-rails rails-activerecord

我有一个模型,它有一列看起来像这样(示例):

column = [{:A => "1", :B => "2"}, [a, b, c, ..., n]]
Run Code Online (Sandbox Code Playgroud)

其中abcn也哈希,像这样(例子):

a = {:X => "x", :Y => "y"}
Run Code Online (Sandbox Code Playgroud)

目前,如果我调用m模型的记录,例如,m.column[0][:A]它返回=> "1"
如果我调用m.column[1][0]我得到a = {:X => "x", :Y => "y"}

到现在为止还挺好。

现在的问题是,我怎样才能获得模型的所有记录的数组,例如,column[0][:A] = "1"???

我正在尝试这样的事情(但它不起作用):

Model.where("column[0][:A] = ?", "1")
Run Code Online (Sandbox Code Playgroud)

错误说:

PG::SyntaxError: ERROR:  syntax error at or near ":"
Run Code Online (Sandbox Code Playgroud)

编辑:

column是一种text数据类型
,在它的模型中它有serialize :column, Array

mu *_*ort 5

你的问题是你serialize用来存储你的数据,serialize只是将一大堆不可查询的 YAML 倾倒到数据库中。在 PostgreSQL 中解析 YAML 当然是可能的,但它会很慢而且有点毫无意义。

如果你真的需要存储一个哈希数组,那么 PostgreSQL 的jsonb类型将是你最好的选择。你不会在数据库中得到符号键,但你会得到字符串键,所以这个 Ruby 数据:

[{:A => "1", :B => "2"}, {:C => 6, :D => 11}]
Run Code Online (Sandbox Code Playgroud)

看起来像这个 JSON:

[ { "A": "1", "B": "2" }, { "C": 6, "D": 11 } ]
Run Code Online (Sandbox Code Playgroud)

数据库里面。

进入jsonb数据库后,您可以使用PostgreSQL 提供的所有JSON 函数和运算符查询它,甚至可以索引 JSON 以支持更快的查询。在您的情况下,您的查询将如下所示:

Model.where("column->0->>'A' = ?", '1')
Run Code Online (Sandbox Code Playgroud)

->RHS 上带有整数的运算符的作用类似于 Ruby 的Array#[]方法:

-> int
获取 JSON 数组元素(从零开始索引,负整数从末尾开始计数)

RHS 上有一个字符串,它的作用类似于 Ruby 的Hash#[]. 请注意,->返回 JSON,而不是文本或整数。该->>操作是一样的->,但是这样你会使用,在年底作出比较清洁它返回文本。

或者你可以说:

Model.where("column->0->'A' = ?", '1'.to_json)
Run Code Online (Sandbox Code Playgroud)

将 string-vs-JSON 逻辑推送到 Ruby 中。


一旦您的数据库模式有意义(即它使用 PostgreSQLjsonb而不是 Rails 的serialize),一切都变得非常简单,但是您如何从这里到达那里?首先serialize :column, Array从您的模型中删除,然后您需要进行三步迁移,如下所示:

  1. 添加一jsonb列:

    def change
      add_column :models, :column_j, :jsonb
    end
    
    Run Code Online (Sandbox Code Playgroud)
  2. column从数据库中读取每个值,手动解压 YAML,并手动编写 JSON:

    def up
      connection.execute('select id, column from models').each do |row|
        a = YAML.load(row['column'])
        connection.raw_connection.exec(
          'update models set column_j = $1 where id = $2',
          [ a.to_json, row['id'].to_i ]
        )
      end
    end
    
    Run Code Online (Sandbox Code Playgroud)

    请注意,您不能为此使用模型作为模型类,并且数据库不再同意models表的结构和格式。

  3. column新的替换旧的column_j

    def change
      remove_column :models, :column, :text
      rename_column :models, :column_j, :column
    end
    
    Run Code Online (Sandbox Code Playgroud)

当然,您需要在迁移之前备份您的数据库。希望你永远不会再考虑使用serialize,这serialize是一个可怕的混合体,它看起来是一个简单的解决方案,但很快就会变成一个瞄准你脚下的角色扮演游戏。