简体   繁体   中英

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

There is an old table with column type as JSON but only arrays are stored in this column. Even though I am storing array, I am not able to query this field using the ANY keyword (which will work on array type columns in Postgres like in this post )

Eg : let's say ['Apple', 'Orange', 'Banana'] is stored as Json in the fruits column, I want to query like Market.where(":name = ANY(fruits)", name: "Orange") and get all the markets with Oranges available.

Can anyone please help me to write a migration to change the existing column(type: Json) to array type?

One example assuming a json field:

\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 Method to convert JSON array into Postgres array:

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}

This assumes the JSON array has homogeneous elements as that is a requirement for Postgres arrays.

A simpler method of finding rows that have 'Orange' in the json field:

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

But really if you're going to do something why not create tables instead as you're not really improving anything?

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM