【问题标题】:Laravel eloquent get the latest rows of grouped rowsLaravel eloquent 获取分组行的最新行
【发布时间】:2018-02-24 02:28:52
【问题描述】:

使用 Eloquent,试图找到一种方法来获取每一行的最新行,分组方式为:exchange、base、quote

数据

exchange    base    quote   price   value   created_at

bittrex     BTC     USD     10000   10000   2018-01-05
bittrex     BTC     USD     9000    9000    2018-01-01
poloniex    BTC     USD     10001   10001   2018-01-05
poloniex    BTC     USD     9000    9000    2018-01-01
binance     BTC     USD     10002   10002   2018-01-05
binance     BTC     USD     9000    9000    2018-01-01
binance     ETH     USD     800     800     2018-01-05
binance     ETH     USD     700     700     2018-01-01

结果:

bittrex     BTC     USD     10000   10000   2018-01-05
poloniex    BTC     USD     10001   10001   2018-01-05
binance     BTC     USD     10002   10002   2018-01-05
binance     ETH     USD     800     800     2018-01-05

更新

我选择了@Cryode 解决方案,使用原始 SQL 而不是 Eloquent(如果有人能想出一个 Eloquent 查询来复制下面的查询结果,请随时发布)。

我还更改了表的结构以添加id(增量)作为主键。我还添加了以下索引$table->index(['exchange', 'base', 'quote', 'created_at']);

解决办法如下:

$currencies  = DB::select('SELECT *
                             FROM (
                                    SELECT DISTINCT exchange, base, quote
                                      FROM tickers
                                  ) AS t1
                             JOIN tickers
                               ON tickers.id =
                                 (
                                    SELECT id
                                      FROM tickers AS t2
                                     WHERE t2.exchange  = t1.exchange
                                       AND t2.base      = t1.base
                                       AND t2.quote     = t1.quote
                                     ORDER BY created_at DESC
                                     LIMIT 1
                                 )
                         ');

谢谢

【问题讨论】:

  • 你可以试试这个吗? Table::orderBy('created_at','asc')->groupBy('exchange')->get()
  • 嗯,试过这个:Ticker::orderBy('created_at','asc')->groupBy('exchange', 'base_currency', 'quote_currency')->get() 但不确定如何只检索每个“组”的最新行
  • 类似于 Ticker::distinct()->orderBy('created_at', 'asc')->groupBy('exchange')->get(['exchange']);这将得到不同的交流......我觉得这已经接近解决方案了。
  • 你 created_at 也包括时间,对吧?
  • 是的时间也是如此

标签: laravel laravel-5 eloquent greatest-n-per-group


【解决方案1】:

让我们首先确定这个 SQL 查询的实际样子。

This DBA answer 提供了对“greatest-n-per-group”问题以及 PostgreSQL 和 MySQL 示例的一些深刻见解。受此答案的启发,这是我为您的单表提出的建议(假设 MySQL 作为您的数据库):

SELECT ticker.*
  FROM (
    SELECT DISTINCT exchange, base, quote
      FROM ticker
  ) AS exchanges
  JOIN ticker
    ON ticker.id = 
       (
         SELECT id
           FROM ticker
          WHERE ticker.exchange = exchanges.exchange
            AND ticker.base = exchanges.base
            AND ticker.quote = exchanges.quote
       ORDER BY created_at DESC
          LIMIT 1
       );

哦,亲爱的。将它融入 Laravel 语言中看起来并不容易。

就我个人而言,我什至不会尝试。复杂的 SQL 查询正是因为它们利用您的数据库来进行报告、数据收集等。试图将其推入查询构建器是乏味的,并且可能几乎没有任何好处。

也就是说,如果您想使用 Laravel 的查询构建器和 Eloquent 以简单的方式获得相同的结果,这里有一个选项:

// Get the unique sets of tickers we need to fetch.
$exchanges = DB::table('ticker')
    ->select('exchange, base, quote')
    ->distinct()
    ->get();

// Create an empty collection to hold our latest ticker rows,
// because we're going to fetch them one at a time. This could be
// an array or however you want to hold the results.
$latest = new Collection();

foreach ($exchanges as $exchange) {
    $latest->add(
        // Find each group's latest row using Eloquent + standard modifiers.
        Ticker::where([
                'exchange' => $exchange->exchange,
                'base' => $exchange->base,
                'quote' => $exchange->quote,
            ])
            ->latest()
            ->first()
    );
}

优点:您可以使用查询构建器和 Eloquent 抽象;允许您维护您的 Ticker 模型,该模型在请求期间可能需要额外的逻辑。

缺点:需要多个查询。


另一种选择是使用封装复杂查询的MySQL View,并创建一个单独的 Eloquent 模型,该模型将从该视图中获取。这样一来,您的应用代码就可以像 TickerLatest::all() 一样简单。

【讨论】:

  • 感谢您的帮助,我正在尝试测试上面的 MySQL,但是由于第一个 Select 没有检索到 id,而只有 exchangebase_currencyquote_currency,所以我无法加入idforeach 选项很好,但是每次调用都会对数据库产生数百次影响。将调查所有这些。我想使用 Eloquent 是不可能的,我应该使用原始查询
  • oups 的意思是 basequote,而不是 base_currencyquote_currency
  • 此表上没有id 列吗?
  • 你应该有一个不是为了性能,而是为了使额外的查询(比如这个)成为可能/更容易。如果您想要更好的性能,您可以对重复的数据进行规范化、添加索引(请参阅我为索引建议链接的 SO Q&A)等。主键还可以让 MySQL 在未来进行扩展。
【解决方案2】:

您可以将多个参数传递给 groupBy 方法以按多列分组

请参考文档https://laravel.com/docs/5.6/queries#ordering-grouping-limit-and-offset

$users = DB::table('users')
            ->groupBy('first_name', 'status')
            ->having('account_id', '>', 100)
            ->get();

【讨论】:

  • 我可以轻松分组,但是,如何从返回的每个组中选择最近的行
【解决方案3】:

从 Laravel 5.6.17 开始,您可以使用 joinSub() ,因此可能的 Eloqunish 解决方案可能是这样的:

分组并找到最后日期的票

    $latest = Ticker::select('exchange', 'base', 'quote', DB::raw('MAX(created_at) as created_at'))
        ->groupBy('exchange', 'base', 'quote');

然后使用 joinSub() 再次加入每个组中最新的所有记录

    $posts = DB::table('tickets')
        ->joinSub($latest, 'latest_tickets', function ($join) {
            $join->on('tickets.exchange', '=', 'latest_tickets.exchange')
                 ->on('tickets.base', '=', 'latest_tickets.base')
                 ->on('tickets.quote', '=', 'latest_tickets.quote')
                  ->on('tickets.created_at', '=', 'latest_posts. created_at');
        })->get();

【讨论】:

    【解决方案4】:

    您可以先获取最新的行,然后再对集合进行分组。

    $items = Ticker::latest()->get();
    // exchange, base, quote
    $groupedByExchange = $items->groupBy('exchange');
    $groupedByBase = $items->groupBy('base');
    $groupedByQoute = $items->groupBy('qoute');
    

    更新: 只需在groupBy()函数后面加上->first()就可以得到每组的单项。

    $latestByExchange= Ticker::latest()->groupBy('exchange')->first(); // and so on
    

    【讨论】:

    • 感谢最新将按 desc 订购,但可以分组交换,基础,报价,并拥有一个最新值
    • 谢谢,得到“允许的内存大小为 134217728 字节已用尽”,但出现“Ticker::latest()->get()”错误,表太大而无法排序.. 嗯.. 大约 2M行。必须检查一下
    • 这不是最好的主意,因为它会从数据库中返回给定模型的每一行,因此你的内存错误。
    【解决方案5】:

    这是另一种使用自左连接获取latest record per group 的方法,该查询可以轻松转换为 laravel 的查询构建器。

    • 它不需要任何特定版本的 laravel 即可工作,它也可以在旧版本的 laravel 上工作
    • 不需要其他答案中建议的 N+1 个查询(开销)

    在普通的 SQL 中,它可以写成

    select a.*
    from tickers a
    left join tickers b on a.exchange  = b.exchange
      and a.base = b.base
      and a.quote = b.quote
      and a.created_at < b.created_at 
    where b.created_at  is null
    

    在查询构建器中看起来像

    DB::table('tickers as a')
      ->select('a.*')
      ->leftJoin('tickers as b', function ($join) {
            $join->on('a.exchange', '=', 'b.exchange')
                 ->whereRaw(DB::raw('a.base = b.base'))
                 ->whereRaw(DB::raw('a.quote = b.quote'))
                 ->whereRaw(DB::raw('a.created_at < b.created_at'))
                 ;
       })
      ->whereNull('b.created_at')
      ->get();
    

    Laravel Eloquent select all rows with max created_at

    或者您使用相关子查询来选择最新行

    SQL

    select a.*
    from tickers a
    where exists (
      select 1
      from tickers b
      where a.exchange  = b.exchange
      and a.base = b.base
      and a.quote = b.quote
      group by b.exchange,b.base,b.quote
      having max(b.created_at) = a.created_at
    );
    

    查询生成器

    DB::table('tickers as a')
       ->whereExists(function ($query) {
           $query->select(DB::raw(1))
                 ->from('tickers as b')
                 ->whereRaw(DB::raw('a.exchange = b.base'))
                 ->whereRaw(DB::raw('a.base = b.base'))
                 ->whereRaw(DB::raw('a.quote = b.quote'))
                 ->groupBy(['b.exchange','b.base','b.quote'])
                 ->havingRaw('max(b.created_at) = a.created_at')
                 ;
       })
         ->get();
    

    DEMO

    【讨论】:

      猜你喜欢
      • 2017-09-21
      • 2014-07-18
      • 2019-08-06
      • 2013-09-14
      • 2021-02-04
      • 2014-01-03
      • 1970-01-01
      • 1970-01-01
      • 2019-06-13
      相关资源
      最近更新 更多