【问题标题】:How do I write a Rails finder method that will return the greatest date grouped by record?如何编写一个 Rails finder 方法来返回按记录分组的最大日期?
【发布时间】:2017-09-01 17:00:46
【问题描述】:

我正在使用带有 PostGres 9.5 的 Rails 5。我有一张跟踪价格的表格...

                                         Table "public.crypto_prices"
       Column       |            Type             |                         Modifiers
--------------------+-----------------------------+------------------------------------------------------------
 id                 | integer                     | not null default nextval('crypto_prices_id_seq'::regclass)
 crypto_currency_id | integer                     |
 market_cap_usd     | bigint                      |
 total_supply       | bigint                      |
 last_updated       | timestamp without time zone |
 created_at         | timestamp without time zone | not null
 updated_at         | timestamp without time zone | not null

我想获得选定货币的每种货币的最新价格(last_updated 最高)。我可以找到与某些货币相关的所有价格,像这样

current_prices = CryptoPrice.where(crypto_currency_id: CryptoIndexCurrency.all.pluck(:crypto_currency_id).uniq)

然后我可以按货币将它们排序到数组中,循环遍历每个数组,直到找到具有最大 last_updated 值的那个,但是 我怎样才能编写一个查找器,它会准确地返回每种货币中最大的一行last_updated 日期?

编辑:像这样尝试了 Owl Max 的建议

ids = CryptoIndexCurrency.all.pluck(:crypto_currency_id).uniq
crypto_price_ids = CryptoPrice.where(crypto_currency_id: ids).group(:crypto_currency_id).maximum(:last_updated).keys
puts "price ids: #{crypto_price_ids.length}"
@crypto_prices = CryptoPrice.where(crypto_currency_id: crypto_price_ids)
puts "ids: #{@crypto_prices.size}"

虽然第一个“puts”只显示了“12”的大小,但第二个 put 显示了超过 38,000 个结果。它应该只返回 12 个结果,每种货币一个。

【问题讨论】:

  • 只是抛出一个想法-您是否考虑过按`last_updated`排序并抢占第一个实例?
  • 那只会返回一个结果,不是吗?
  • 如果你想要多个,你可以用.first(3) for 3 做类似的事情(随便填什么数字)。
  • 我不是这个意思。我只知道某些货币的每种货币的最新价格(例如我指定的 crypto_currency_id 值)。我应该在哪里使用“first(3)”指定特定的 ID?

标签: ruby-on-rails postgresql ruby-on-rails-5


【解决方案1】:

我们可以编写一个查找器,它会以类似的方式为每种货币返回一个具有最大 last_updated 日期的行,例如

current_prices = CryptoPrice.where(crypto_currency_id: CryptoIndexCurrency.all.pluck(:crypto_currency_id).uniq).select("*, id as crypto_price_id, MAX(last_updated) as last_updated").group(:crypto_currency_id)

我希望这会让你更接近你的目标。谢谢。

【讨论】:

  • 当我运行这个我得到错误,“ActiveRecord::StatementInvalid: PG::GroupingError: ERROR: column "crypto_prices.id" 必须出现在 GROUP BY 子句中或用于聚合函数"
  • 您能否通过 ("*, id as crypto_price_id, MAX(last_updated) as last_updated") 更新选择来尝试一下。我还在某处读到,在 PG 的情况下,我们也需要明确指定排序。因此,如果上述解决方案不适合您,还可以添加排序条款,这可能会有所帮助。谢谢。
  • 请编辑您的问题以包含生成答案的代码。我不清楚你想让我做什么。
  • 不幸的是,此查询不适用于 postgreSQL,您需要将 id 包含在聚合函数中,例如 .group(:crypto_currency_id, :id),但是您不再拥有唯一的 crypto_currency_ids。
【解决方案2】:

由于or查询方法,仅适用于Rails5

specific_ids = CryptoIndexCurrency.distinct.pluck(:crypto_currency_id)
hash = CryptoPrice.where(crypto_currency_id: specific_ids)
                  .group(:crypto_currency_id)
                  .maximum(:last_updated)
hash.each_with_index do |(k, v), i|
  if i.zero?
    res = CryptoPrice.where(crypto_currency_id: k, last_updated: v)
  else
    res.or(CryptoPrice.where(crypto_currency_id: k, last_updated: v))
  end
end

说明

您可以使用group 将您的所有CryptoPrice 对象按表中的每个CryptoIndexCurrency 呈现重新组合。

然后使用maximum(感谢@artgb)取最大的值last_updated。这将输出带有键的Hashcrypto_currency_id 和值 last_updated

最后,您可以使用keys 仅获得Arraycrypto_currency_id

CryptoPrice.group(:crypto_currency_id).maximum(:last_updated)
=> => {2285=>2017-06-06 09:06:35 UTC,
       2284=>2017-05-18 15:51:05 UTC,
       2267=>2016-03-22 08:02:53 UTC}

这个解决方案的问题是你得到了每行的最大日期,而没有得到整个记录。

要获取记录,您可以对散列进行成对循环。与crypto_currency_idlast_updated。这很 hacky,但我找到了唯一的解决方案。

【讨论】:

  • 谢谢。在这里,您可以找到每种货币的最新价格,但我只想获得某些货币的最新价格(例如“其中 crypto_currency_id in (ids)”)。我如何将该条件添加到您列出的内容中?
  • 只需在grouping 之前添加一个where 子句!例如CryptoPrice.where(crypto_currency_id: specific_ids). group(:crypto_currency_id).maximum(:last_updated).keys 它会做同样的事情,除了它不会考虑你不需要的CryptoIndexCurrency
  • 我编辑了我的问题以包含您的建议。这行“@crypto_prices = CryptoPrice.where(crypto_currency_id: crypto_price_ids)”正在返回数万个结果,这是不正确的。它应该只返回每种货币的一个结果。
  • 我认为这是因为您的表允许有多行具有相同的crypto_currency_id。这意味着您必须在组内选择cryptoprice.id 而不仅仅是crypto_currency_id。我将在几个小时内向您展示一个示例!您已经接近解决方案了。
  • @Dave 非常好的问题!我想我找到了一个解决方案,但它很老套,如果它有效,请告诉我,如果无效,我很抱歉,但这意味着我还不足以找到解决方案,我将停止尝试 ^^
【解决方案3】:

使用此代码,您可以在此处从特定表中获取最新更新的行。

  CryptoPrice.order(:updated_at).pluck(:updated_at).last

这应该对你有帮助。

【讨论】:

  • 这总是只返回一行,对吧?另外,您没有考虑到我只想返回某些货币的最新价格(由我的问题中的 id 标识)
【解决方案4】:

目前这在 Rails 中通过一个语句/查询来实现并不容易。如果您不介意使用多个语句/查询,那么这就是您的解决方案:

cc_ids = CryptoIndexCurrency.distinct.pluck(:crypto_currency_id)

result = cc_ids.map do |cc_id|
  max_last_updated = CryptoPrice.where(crypto_currency_id: cc_id).maximum(:last_updated)
  CryptoPrice.find_by(crypto_currency_id: cc_id, last_updated: max_last_updated)
end

map 方法的结果就是您要查找的结果。这会为每个 crypto_currency_id 生成 2 个查询,并为请求 crypto_currency_ids 生成 1 个查询。

如果您想通过一个查询来执行此操作,您需要使用OVER (PARTITION BY ...)。以下链接中的更多信息:

但在这种情况下,您必须编写一些 SQL。

编辑 1:

如果你想要一个不错的 Hash 结果运行:

cc_ids.zip(result).to_h

编辑 2:

如果您想将查询量减半,您可以将max_last_updated 查询推入find_by 作为子查询,如下所示:

cc_ids = CryptoIndexCurrency.distinct.pluck(:crypto_currency_id)

result = cc_ids.map do |cc_id|
  CryptoPrice.find_by(<<~SQL.squish)
    crypto_currency_id = #{cc_id} AND last_updated = (
      SELECT MAX(last_updated) 
      FROM crypto_prices 
      WHERE crypto_currency_id = #{cc_id})
  SQL
end

这会为每个 crypto_currency_id 生成 1 个查询,并为请求 crypto_currency_ids 生成 1 个查询。

【讨论】:

  • 我不介意运行多个语句,但这会产生多少查询?我想尽量减少查询次数。
  • 每个crypto_currency_id 2 次查询+ 1 次获取crypto_currency_ids。请记住,获取 max_last_updated 的查询仅返回 1 个值,CryptoPrice.find_by(crypto_currency_id: cc_id, last_updated: max_last_updated) 仅返回一条记录。在 Rails 中从来没有初始化一整套记录。如果您想知道查询的确切数量,请运行:CryptoIndexCurrency.distinct.pluck(:crypto_currency_id).count * 2 + 1
  • 谢谢,但我认为 Owl max 的解决方案只有 2 个查询 - 一个用于获取 ID,另一个用于获取数据。
  • 没错,请记住,他不会从CryptoIndexCurrency 获取crypto_currency_id。如果您的CryptoPrice 表包含nil 值,或者CryptoIndexCurrency 中不存在的值,那么这些值也会被返回。
  • @JohanWentholt,你是对的,我在第一个查询中跳过了第一个 where 子句,因为我虽然没有必要理解这个想法。但你是对的,它添加了另一个查询。我确信解决方案可以在一个查询中编写,但我不确定 Rails ORM 是否可行(无论如何在 POSTGRESQL 中,在 MySQL 中肯定是可行的)。我认为 Vakiliy approches 是迄今为止最好的。我还没有测试查询,但写这是行 SQL 是个好主意。您的想法也不错,但无法在大型数据库上扩展,因为它会产生 N+1 查询问题。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-06-08
  • 2020-07-04
  • 2014-01-31
相关资源
最近更新 更多