如何编写迁移以将 JSON 字段转换为 Postgres 数组以在 rails 中查询?

How to write a migration to convert JSON field to Postgres Array for querying in rails?

有一个旧的 table,列类型为 JSON,但此列中仅存储数组。 即使我正在存储数组,我也无法使用 ANY 关键字查询此字段(它将在 Postgres 中的数组类型列上工作,如 post

例如:假设['Apple'、'Orange'、'Banana']在[=中存储为Json 13=] 列,我想像 Market.where(":name = ANY(fruits)", name: "Orange") 一样查询并获取所有有 Oranges 的市场。

任何人都可以帮我写一个迁移来将现有列(类型:Json)更改为数组类型吗?

假设一个 json 字段的例子:

\d json_test 
               Table "public.json_test"
  Column   |  Type   | Collation | Nullable | Default 
-----------+---------+-----------+----------+---------
 id        | integer |           |          | 
 fld_json  | json    |           |          | 
 fld_jsonb | jsonb   |           |          | 
 fruits    | json    |           |          | 

insert into json_test (id, fruits) values (1, '["Apple", "Orange", "Banana"] ');
insert into json_test (id, fruits) values (2, '["Pear", "Orange", "Banana"] ');
insert into json_test (id, fruits) values (3, '["Pear", "Apple", "Banana"] ');

WITH fruits AS 
(SELECT 
    id, json_array_elements_text(fruits) fruit 
 FROM json_test) 
SELECT 
   id 
FROM 
   fruits
WHERE 
    fruit = 'Orange';
 id 
----
  1
  2

UPDATE 将 JSON 数组转换为 Postgres 数组的方法:

SELECT 
    array_agg(fruit) 
FROM 
    (SELECT 
        id, json_array_elements_text(fruits)AS fruit 
    FROM 
    json_test) AS elements 
GROUP BY 
    id;

 array_agg       
-----------------------
 {Pear,Apple,Banana}
 {Pear,Orange,Banana}
 {Apple,Orange,Banana}

这假定 JSON 数组具有同类元素,因为这是 Postgres 数组的要求。

查找 json 字段中具有 'Orange' 的行的更简单方法:

SELECT 
    id, fruits 
FROM 
    json_test 
WHERE 
    fruits::jsonb ? 'Orange';

 id |             fruits             
----+--------------------------------
  1 | ["Apple", "Orange", "Banana"] 
  2 | ["Pear", "Orange", "Banana"] 

class AddArrayFruitsToMarkets < ActiveRecord::Migration[6.0]
  def up
    rename_column :markets, :fruits, :old_fruits
    add_column :markets, :fruits, :string, array: true
    Market.update_all('fruits = json_array_elements(old_fruits)')
  end
end
class RemoveJsonFruitsFromMarkets < ActiveRecord::Migration[6.0]
  def up
    remove_column :markets, :old_fruits
  end
end

但实际上,如果您打算做某事,为什么不创建表格,因为您并没有真正改进任何东西?

class Fruit < ApplicationRecord
  validates :name, presence: true
  has_many :market_fruits
  has_many :markets, through: :market_fruits
end

class MarketFruit < ApplicationRecord
  belongs_to :market
  belongs_to :fruit
end

class Market < ApplicationRecord
  has_many :market_fruits
  has_many :fruits, through: :market_fruits

  def self.with_fruit(name)
    joins(:fruits)
      .where(fruits: { name: name })
  end

  def self.with_fruits(*names)
    left_joins(:fruits)
      .group(:id)
      .where(fruits: { name: names })
      .having('COUNT(fruits.*) >= ?', names.length) 
  end
end