Laravel雄辩地获取最新的分组行行

时间:2018-02-24 02:28:53

标签: laravel laravel-5 eloquent

使用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
                                 )
                         ');

由于

5 个答案:

答案 0 :(得分:3)

让我们首先确定这个SQL查询实际上是什么样的。

This DBA answer提供了对“最大n组”问题以及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()一样简单。

答案 1 :(得分:0)

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

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

更新: 您可以通过在->first()函数后简单添加groupBy()来获取每个组的单个项目。

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

答案 2 :(得分:0)

您可以将多个参数传递给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 :(得分:0)

从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 :(得分:0)

这是通过使用自左联接获得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