【问题标题】:Rails/Ruby: turning query result into multi-level hashRails/Ruby:将查询结果转换为多级哈希
【发布时间】:2018-12-12 18:30:05
【问题描述】:

我有一个查询*,结果如下:

#<ActiveRecord::Relation [
    #<BookRank id: 2, book_id: 2, list_edition_id: 1, rank_world: 5, rank_europe: 1>,
    #<BookRank id: 3, book_id: 1, list_edition_id: 1, rank_world: 6, rank_europe: 2>,
    #<BookRank id: 8, book_id: 2, list_edition_id: 3, rank_world: 1, rank_europe: 1>,
    #<BookRank id: 9, book_id: 1, list_edition_id: 3, rank_world: 2, rank_europe: 2
]>

我想要得到的是这样的哈希:

{
    book_id => {
        list_edition_id => {
            "rank_world" => value,
            "rank_europe" => value
        }
    }
}

(最重要的是按最低 list_edition_id 的 rank_world 值对哈希进行排序,但这可能太复杂了。)

ranks_relation.group_by(&amp;:book_id) 给了我一个哈希值,其中 book_ids 是键,但是排名数据仍然在数组中:

{
    2 => [
            #<BookRank id: 2, book_id: 2, list_edition_id: 1, rank_world: 5, rank_europe: 1>,
            #<BookRank id: 8, book_id: 2, list_edition_id: 3, rank_world: 1, rank_europe: 1>
    ],
    1 => [
            #<BookRank id: 3, book_id: 1, list_edition_id: 1, rank_world: 6, rank_europe: 2>
            #<BookRank id: 9, book_id: 1, list_edition_id: 3, rank_world: 2, rank_europe: 2>
    ]
}

我应该如何进行?

*EDIT: 这是模型结构和查询。另一位用户要求:

class Book < ActiveRecord::Base
  has_many :book_ranks, dependent: :destroy
end

class List < ActiveRecord::Base
  has_many :list_editions, dependent: :destroy
end

class ListEdition < ActiveRecord::Base
  belongs_to :list
  has_many :book_ranks, dependent: :destroy
end

class BookRank < ActiveRecord::Base
  belongs_to :book
  belongs_to :list_edition

  has_one :list, through: :list_edition
end

对于查询,我已经使用了两个具有相关 ID 的数组,分别是 BookListEdition

BookRank.where(:book_id => book_ids, :list_edition_id => list_edition_ids)

【问题讨论】:

  • “那么排名数据仍然在数组中” - 如果你也对这些数组进行分组呢?
  • 我通过将多个group_bys 与map(以像另一个哈希一样将值转换为合适的格式)、to_hsort/sort_by 来解决类似的问题。我不知道有更优雅的解决方案,但也很想看到一个。但只要它有效(并且您的查询不会产生太大而无法由 ruby​​ 就地处理的数据),这可能是一种方法。
  • 我想理论上我可以在第二步中按 list_edition_id 分组,但我不知道如何正确嵌套语句。 @JaySchneider:通常每个请求最多可以处理 300 个 BookRank。不理想,宁愿在数据库中完成这项工作,但它可以工作。
  • 能否在您的问题中添加您的查询?

标签: ruby-on-rails ruby activerecord ruby-hash


【解决方案1】:

试试这个

record = your_record

hash = {}

record.each do |record|
  hash[record.book_id] ||= {}
  hash[record.book_id][record.list_edition_id] = {
    'rank_world' => record.rank_world,
    'rank_europe' => record.rank_europe
  }
end
# hash will then be {2=>{1=>{"rank_world"=>5, "rank_europe"=>1}, 3=>{"rank_world"=>1, "rank_europe"=>1}}, 1=>{1=>{"rank_world"=>6, "rank_europe"=>2}, 3=>{"rank_world"=>2, "rank_europe"=>2}}}

这只会遍历记录一次。

【讨论】:

  • 这个解决方案非常好。您可以通过编写 hash[record.book_id][record.list_edition_id] = record.slice(:rank_world, :rank_europe) 来改进它,这使它非常短。据我了解,我认为键是字符串或符号在这里并不重要。
  • @JaySchneider 是的,如果字符串或符号无关紧要,那么我们可以使用 slice
【解决方案2】:

嘿@gibihmruby(顺便说一句,好名字),

因此,既然您在 cmets 中要求对我的 ugly 方法进行更具体的描述,仅使用 group_by 和朋友,这是我的建议:

rel.group_by(&:book_id).map do |k, v| 
  [k, v.group_by(&:list_edition_id)]
end.to_h

会产生类似的结构

{2=>
  {1=>
    [#<struct BookRank
      id=2,
      book_id=2,
      list_edition_id=1,
      rank_world=5,
      rank_europe=1>],
   3=>
    [#<struct BookRank
      id=8,
      book_id=2,
      list_edition_id=3,
      rank_world=1,
      rank_europe=1>]},
 1=>
  {1=>
    [#<struct BookRank
      id=3,
      book_id=1,
      list_edition_id=1,
      rank_world=6,
      rank_europe=2>],
   3=>
    [#<struct BookRank
      id=9,
      book_id=1,
      list_edition_id=3,
      rank_world=2,
      rank_europe=2>]}}

然后您必须将最内部的对象映射到您想要的属性。如果您确定book_idlist_edition_id 的组合是唯一的,则可以摆脱数组包装,然后映射到所需的属性。您可以将slice 用于ActiveRecord 对象。然后映射将是

rel.group_by(&:book_id).map do |book_id, grouped_by_book_id| 
  [
    book_id, 
    grouped_by_book_id.group_by(&:list_edition_id).map do |list_ed_id, grouped|
      [list_ed_id, grouped.first.slice(:rank_world, :rank_europe)]
    end.to_h
  ]
end.to_h

由于我没有创建模型,而只是使用了结构(如您在上面的示例中所见),因此我并没有真正自己测试最后一点。但它应该像这样工作,如果您发现错误或有更多问题,请发表评论。我仍然希望有人提出更好的解决方案,因为我现在经常自己寻找一个。

干杯:)

编辑:小修正

【讨论】:

    【解决方案3】:
    ranks_relation.
      group_by(&:book_id).
      map do |id, books|
        [id, books.map do |book|
               [
                 book.list_edition_id,
                 {
                   "rank_world" => book.rank_world,
                   "rank_europe" => book.rank_europe
                 }
               ]
             end.sort_by { |_, hash| hash["rank_world"] }.to_h
        ]
      end.to_h
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2013-07-08
      • 1970-01-01
      • 1970-01-01
      • 2021-01-17
      • 2011-06-29
      • 2016-08-26
      • 1970-01-01
      相关资源
      最近更新 更多