我有一个带有Postgres数据库的Rails应用程序,该数据库具有一个带jsonb genres
列的Artists表。
有数十万行。
该行中的每个类型列都有一个类似["rock", "indie", "seen live", "alternative", "indie rock"]
的数组,具有不同的类型。
我想做的是在所有行中输出JSON中每种流派的计数。
类似{"rock": 532, "power metal": 328, "indie": 862}
有没有一种有效的方法?
更新...这是我目前所拥有的...
genres = Artist.all.pluck(:genres).flatten.delete_if &:empty?
output = Hash[genres.group_by {|x| x}.map {|k,v| [k,v.count]}]
final = output.sort_by{|k,v| v}.to_h
输出是哈希而不是JSON,这很好。
但是已经感觉很慢了,所以我想知道是否有更好的方法来做到这一点。
答案 0 :(得分:0)
在重新阅读您的问题时,您指出该列是JSONb类型。因此,下面的答案将不起作用,因为您需要首先从jsonb列获取数组。这样应该可以更好地工作:
output = Artist.connection.select_all('select genre, count (genre) from (select id, JSONB_ARRAY_ELEMENTS(genres) as genre from artists) as foo group by genre;')
=> #<ActiveRecord::Result:0x00007f8ef20df448 @columns=["genre", "count"], @rows=[["\"rock\"", 5], ["\"blues\"", 5], ["\"seen live\"", 3], ["\"alternative\"", 3]], @hash_rows=nil, @column_types={"genre"=>#<ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Jsonb:0x00007f8eeef5d500 @precision=nil, @scale=nil, @limit=nil>, "count"=>#<ActiveModel::Type::Integer:0x00007f8eeeb4c060 @precision=nil, @scale=nil, @limit=nil, @range=-2147483648...2147483648>}>
output.rows.to_h
=> {"\"rock\""=>5, "\"blues\""=>5, "\"seen live\""=>3, "\"alternative\""=>3}
如注释中所述,如果您可以更改数据库以对其进行规范化,请继续使用。 jsonb列中的匿名数组将是很痛苦的。如果您需要使用此答案,我至少会考虑将视图添加到数据库,以便您可以将流派计数作为具有在rails中对应模型的表的形式(您可以在模型定义中创建)。 / p>
我以为您的列是Postgres中的常规数组列类型时的原始答案。
这是在Rails中执行此操作的SQL方法:
genre_count = Artist.connection.select_all('SELECT
UNNEST(genres),
COUNT (UNNEST(genres))
FROM
artists
GROUP BY
UNNEST(genres);')
然后,您可以使用选择的方法将更小的数据集转换为JSON。
我对UNNEST
不够熟悉,知道为什么我不能像其他任何列一样给它加上别名来使其更漂亮。但这有效。
答案 1 :(得分:0)
如果仅使用体面的关系数据库设计,这是一项极其琐碎的任务:
class Artist < ApplicationRecord
has_many :artist_genres
has_many :genres, through: :artist_genres
end
class Genre < ApplicationRecord
has_many :artist_genres
has_many :artists, through: :artist_genres
end
class ArtistGenre < ApplicationRecord
belongs_to :artist
belongs_to :genre
end
您可以通过以下方式获得结果:
class Genre < ApplicationRecord
has_many :artist_genres
has_many :genres, through: :artist_genres
# This will instanciate a record for each row just like your average scope
# and return a ActiveRecord::Relation object.
def self.with_artist_counts
self.joins(:artist_genres)
.select('genres.name, COUNT(artist_genres.id) AS artists_count')
.group(:id)
end
# This pulls the columns as raw sql results and creates a hash with the genre
# name as keys
def self.pluck_artist_counts
self.connection.select_all(with_artist_counts.to_sql).inject({}) do |hash, row|
hash.merge(row["name"] => row["artists_count"])
end
end
end