问题描述
有一个旧表,列类型为 JSON,但此列中仅存储数组。
即使我正在存储数组,我也无法使用 ANY
关键字查询此字段(这将适用于 Postgres 中的数组类型列,例如 post)
例如:假设 ['Apple','Orange','Banana'] 在 fruits
列中存储为 Json,我想像 Market.where(":name = ANY(fruits)",name: "Orange")
一样查询并让所有市场上都有可用的橙子。
谁能帮我写一个迁移来将现有列(类型: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",fruits) values (3,"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,Banana}
这假设 JSON 数组具有同构元素,因为这是 Postgres 数组的要求。
查找 json
字段中包含“橙色”的行的更简单方法:
SELECT
id,fruits
FROM
json_test
WHERE
fruits::jsonb ? 'Orange';
id | fruits
----+--------------------------------
1 | ["Apple","Banana"]
2 | ["Pear","Banana"]
,
class AddArrayFruitsToMarkets < ActiveRecord::Migration[6.0]
def up
rename_column :markets,:fruits,:old_fruits
add_column :markets,: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