对数组中的项目进行计数会跨越100的数千条记录

时间:2018-09-03 15:26:47

标签: ruby-on-rails arrays json ruby postgresql

我有一个带有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,这很好。

但是已经感觉很慢了,所以我想知道是否有更好的方法来做到这一点。

2 个答案:

答案 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不够熟悉,知道为什么我不能像其他任何列一样给它加上别名来使其更漂亮。但这有效。

http://sqlfiddle.com/#!15/30597/21/0

答案 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