Подсчет элементов в массивах пересекает сотни тысяч записей - PullRequest
0 голосов
/ 03 сентября 2018

У меня есть приложение Rails с базой данных Postgres, в котором есть таблица Artists со столбцом jsonb genres.

Есть сотни тысяч строк.

Каждый столбец жанра в строке имеет массив типа ["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 голосов
/ 03 сентября 2018

Это очень тривиальная задача, если вы просто используете приличный реляционный дизайн БД:

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
0 голосов
/ 03 сентября 2018

При повторном чтении вашего вопроса вы заявляете, что столбец является типом 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 будет просто болезненным в будущем. Если вам нужно использовать этот ответ, я бы по крайней мере подумал о добавлении представления в БД, чтобы вы могли получить счетчик жанров в виде таблицы с соответствующей моделью в рельсах (которую вы можете просто создать в определениях вашей модели).

Оригинальный ответ, когда я подумал, что ваш столбец - это тип столбца обычного массива в Postgres.

Вот способ SQL сделать это в Rails:

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...