Rails 查询组并提取以返回按另一个属性分组的 ID 数组?

问题描述 投票:0回答:4

我知道

Model.group(:account_id).count
将返回 account_ids => 计数的哈希值。

{3=>3, 4=>3, 8=>8, 10=>5, 20=>4}

我想返回

account_ids => ids

的哈希值
{
  3=>[4594, 4599, 4595], 
  8=>[4600, 4603, 4604, 4606, 4609, 4613, 4611, 4605], 
  20=>[4621, 4623, 4624, 4620], 
  10=>[4626, 4627, 4630, 4631, 4628], 
  4=>[222, 1189, 715]
}

我梦想着

Model.group(:account_id).pluck(:id)
能够解决问题,但它抛出了一个错误:

ActiveRecord::StatementInvalid: PG::GroupingError: ERROR:  column 
"models.id" must appear in the GROUP BY clause or be used in an 
aggregate function
LINE 1: SELECT "models"."id" FROM "models...

我的实际解决方案相当丑陋。看来应该有更简单的方法

Model.pluck(:id, :account_id).to_h
  .group_by{|k,v| v}
  .transform_values {|v| v.map(&:first) }
ruby-on-rails activerecord
4个回答
16
投票

至少在较新版本的 Rails 和 PostgreSQL 中这是可能的:

pairs = Model.group(:account_id).pluck(:account_id, 'array_agg(id)')

返回:

[
  [1, [535, 536]],
  [2, [542, 567, 588]],
]

可以转换成哈希值:

Hash[*pairs.flatten(1)]     # Ruby < 2.3:
pairs.to_h                  # Ruby 2.3+:
{
  1 => [535, 536],
  2 => [542, 567, 588],
}

array_agg
是 PostgreSQL 函数,但我认为 MySQL 的
GROUP_CONCAT
也可以工作。


5
投票

我认为这个问题不再相关,但它会对遇到类似问题的人有所帮助。

query = "SELECT account_id, GROUP_CONCAT(id) FROM model_name GROUP BY account_id"
Model.connection.execute(query).to_h

=> {1=>"31", 22=>"12,11,10", 78=>"36,35,34,37,1,3,2,38"} # example result

函数 GROUP_CONCAT() 适用于 MySQL 5.6。对于 MySQL 5.7+,您可以使用 JSON_ARRAYAGG()
对于 PostgreSQL,您需要使用 array_agg()string_agg()

希望能有所帮助。


3
投票

Postgres 有一个 json_agg 聚合函数,可用于获取 ids 的聚合:

psql=# SELECT models.group_id, json_agg(id) AS ids FROM models GROUP BY models.group_id;
 group_id |                      ids                      
----------+-----------------------------------------------
       34 | [149]
       43 | [170, 171, 172]
       25 | [106, 107]
       32 | [134, 135, 136, 137, 138, 139]
        8 | [29, 30, 31, 32, 33, 34, 35, 36]
       12 | [53]
       10 | [39, 40, 41, 42, 43, 44, 45]
       26 | [108, 109, 110, 111, 112, 113, 114]
       11 | [46, 47, 48, 49, 50, 51, 52]
       18 | [70, 71, 72, 73, 74, 75, 76, 77, 78]
       16 | [61, 62, 63, 64]

您可以在 Rails 中的原始 SQL 查询中使用它,并使用

each_with_object
生成以 group_id 作为键的哈希值。

class Model < ApplicationRecord
  belongs_to :group

  def self.by_group
    sql = Model.select(:group_id, 'json_agg(id) AS ids')
               .group(:group_id)
               .to_sql
    rows = connection.select_all(sql)
    rows.each_with_object({}) do |row, hash|
      hash[row["group_id"]] = JSON.parse(row["ids"])
    end
  end
end

irb(main):001:0> Model.by_group
   (1.7ms)  SELECT models.group_id, json_agg(id) AS ids FROM models GROUP BY models.group_id;
=> {34=>[149], 43=>[170, 171, 172], 25=>[106, 107], 32=>[134, 135, 136, 137, 138, 139], 8=>[29, 30, 31, 32, 33, 34, 35, 36], 12=>[53], 10=>[39, 40, 41, 42, 43, 44, 45], 26=>[108, 109, 110, 111, 112, 113, 114], 11=>[46, 47, 48, 49, 50, 51, 52], 18=>[70, 71, 72, 73, 74, 75, 76, 77, 78], 16=>[61, 62, 63, 64], 39=>[167], 3=>[3, 4], 47=>[184, 185, 186, 187], 13=>[54, 55, 56, 57], 49=>[193, 194, 195, 196, 197, 198, 199, 200], 22=>[93, 94, 95, 96, 97, 98], 9=>[37, 38], 24=>[104, 105], 14=>[58, 59, 60], 45=>[179], 46=>[180, 181, 182, 183], 48=>[188, 189, 190, 191, 192], 17=>[65, 66, 67, 68, 69], 28=>[115, 116, 117], 36=>[155], 38=>[158, 159, 160, 161, 162, 163, 164, 165, 166], 4=>[5, 6, 7, 8, 9, 10, 11, 12, 13], 30=>[119, 120, 121, 122, 123, 124, 125], 50=>[201, 202], 33=>[140, 141, 142, 143, 144, 145, 146, 147, 148], 6=>[23], 40=>[168], 19=>[79, 80, 81], 29=>[118], 2=>[1, 2], 21=>[85, 86, 87, 88, 89, 90, 91, 92], 23=>[99, 100, 101, 102, 103], 41=>[169], 31=>[126, 127, 128, 129, 130, 131, 132, 133], 35=>[150, 151, 152, 153, 154], 20=>[82, 83, 84], 5=>[14, 15, 16, 17, 18, 19, 20, 21, 22], 44=>[173, 174, 175, 176, 177, 178], 7=>[24, 25, 26, 27, 28], 37=>[156, 157]}

0
投票

谢天谢地这个问题存在!我想提供另一种解决方案,因为:

  • 现有解决方案利用特定于数据库的聚合函数
  • 发帖者的原始解决方案仅在所有哈希值都是唯一的情况下才有效,即如果键
    3
    4
    都具有值
    [4594, 4599, 4595]
    ,则只有一个键可以通过
    .to_h.group_by{|k,v| v}
    解决方案获得该值。

此方法与数据库无关,适用于跨不同键具有重复值的哈希:

Model.pluck(:account_id, :id)
  .group_by { |item| item[0] }
  .transform_values { |v| v.map(&:last) }
© www.soinside.com 2019 - 2024. All rights reserved.