【问题标题】:Laravel Eloquent - Sum multiple relation columnsLaravel Eloquent - 对多个关系列求和
【发布时间】:2020-07-16 10:55:29
【问题描述】:

我不是 Eloquent 大师,我做了很多研究,但无法重现我的预期。

我有以下型号:

<?php

use Illuminate\Database\Eloquent\Model;

class Invoice extends Model
{
    protected $fillable = [
        'uuid',
        'number_id'
    ];

    protected $dates = [
        'started_at',
        'ended_at'
    ];

    public $timestamps = false;


    public function contacts()
    {
        return $this->hasMany(Contact::class);
    }
}
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Contact extends Model
{
    protected $fillable = [
        'incoming_messages',
        'outgoing_messages',
        'outgoing_template_messages',
    ];

    public $timestamps = false;


    public function invoice()
    {
        return $this->belongsTo(Invoice::class);
    }
}

当我搜索特定的Invoice::class

$invoice = Invoice::number($number)
    ->latest('started_at')
    ->with(['contacts'])
    ->firstOrFail();

我需要这样退回:

{
    "id": 1,
    "number_id": "444a13fd-9789-426e-bdfb-c3e2e83c4422",
    "incoming_messages": 10, #(sum of invoice.contacts.incoming_messages)
    "outgoing_messages": 10, #(sum of invoice.contacts.outgoing_messages)
    "outgoing_template_messages": 10, #(sum of invoice.contacts.outgoing_template_messages)
    "started_at": "2020-07-01T00:00:00.000000Z",
    "ended_at": "2020-07-31T23:59:59.000000Z"
}

我目前正在InvoiceResource::class

$incoming_messages = $this->contacts->sum('incoming_messages');
$outgoing_messages = $this->contacts->sum('outgoing_messages');
$outgoing_template_messages = $this->contacts->sum('outgoing_template_messages');

但是数据量非常大,我想删除EagerLoading并在单个查询中完成所有操作以减少对数据库的影响。

关于如何仅使用Eloquent 解决此问题的任何想法?


更新

一位朋友帮我解决了我想转换为Eloquent的查询:

SELECT a.number_id
, sum(incoming_messages) as 'incoming_messages' 
, sum(outgoing_messages) as 'outgoing_messages'
, sum(outgoing_template_messages) as 'outgoing_template_messages'
, a.started_at
, a.ended_at
FROM invoices a
inner join contacts b on a.id = b.invoice_id
Where number_id = '45bh1h2g14h214hg2'
Group By a.started_at , a.ended_at

【问题讨论】:

  • 如果我正确理解了您的问题,那么您不应该删除EagerLoading,因为它目前可以为您节省大量查询。我认为你现在应该只有两个:一个给你发票,一个给你发票的联系人。执行$this-&gt;contacts-&gt;sum('incoming_messages'); 不会进行额外的查询。
  • @thefallen 问题是:在某些时候,每张发票可以有超过 100,000 个联系人。据我所知EagerLoading 将返回给我所有的联系人数据,最后我只需要指定列的总和。 Mysql 可以在不返回所有联系人的情况下求和,这会为我节省一些资源...
  • 这是一个利用withCount() 方法的“hack”:laravel-eloquent-query-with-sum-of-related-table。但我个人更愿意使用Invoice::hydrateRaw('&lt;your_raw_query&gt;', &lt;bindings&gt;)

标签: mysql laravel eloquent


【解决方案1】:

我设法仅使用 eloquent 进行查询。我的数据库性能发生了预期的巨大变化。

这是只使用 eloquent 的查询:

$invoice = Invoice::number($number)
    ->selectRaw('invoices.uuid as uuid,
    invoices.number_id as number_id,
    count(*) as contacts,
    sum(incoming_messages) as incoming_messages,
    sum(outgoing_messages) as outgoing_messages,
    sum(outgoing_template_messages) as outgoing_template_messages,
    invoices.started_at,
    invoices.ended_at')
    ->join('contacts', 'contacts.invoice_id', '=', 'invoices.id')
    ->groupBy('invoices.id')
    ->latest('started_at')
    ->firstOrFail();

更新

我遇到了一个问题,即我的查询没有返回没有任何联系人的发票,我不得不进行一些小改动。

join() 方法生成一个inner joininner join 不包括来自table A 且在table B 中没有元素的行。

要克服这种情况,必须使用left join。但是,如果left jointable B 中没有值,它会将我的总和返回为null

为了解决这个问题,我简单地使用了 MySql 函数ifnull()

这是最终的查询:

$invoices = Invoice::number($number)
    ->selectRaw('invoices.uuid as uuid,
    invoices.number_id as number_id,
    ifnull(count(contacts.id), 0) as contacts,
    ifnull(sum(incoming_messages), 0) as incoming_messages,
    ifnull(sum(outgoing_messages), 0) as outgoing_messages,
    ifnull(sum(outgoing_template_messages), 0) as outgoing_template_messages,
    invoices.started_at,
    invoices.ended_at')
    ->leftJoin('contacts', 'contacts.invoice_id', '=', 'invoices.id')
    ->groupBy('invoices.id')
    ->firstOrFail();

【讨论】:

    【解决方案2】:

    您也可以尝试为此使用 Mutator: https://laravel.com/docs/7.x/eloquent-mutators#defining-a-mutator

    public function incomingMessages()
    {
        return $this->contacts->sum('incoming_messages');
    }
    

    在您的资源中,您只需调用:

    $this->incomingMessages()
    

    【讨论】:

      【解决方案3】:

      你可以试试这样的:

      $incoming_messages = $this->contacts()
            ->selectRaw('SUM(incoming_messages) as incoming_messages, SUM(outgoing_messages) as outgoing_messages, SUM(outgoing_template_messages) as outgoing_template_messages')
            ->get();
      

      可能可以将其集成到您的原始查询中,如下所示:

      $invoice = Invoice::number($number)
          ->latest('started_at')
          ->with(['contacts' => function ($q) {
               $q->selectRaw('SUM(incoming_messages) as incoming_messages, SUM(outgoing_messages) as outgoing_messages, SUM(outgoing_template_messages) as outgoing_template_messages');
          }])
          ->firstOrFail();
      

      然后您将访问值作为 $invoice-&gt;contacts-&gt;incoming_messages 等。要在选择多个发票时使其工作,如果 Invoice-contacts 是一对多关系(即只有一个),您可能需要添加 groupBy('invoice_id')联系人的发票)如果是多对多,您可能最终需要加入数据透视表。

      【讨论】:

      • 如何把这个放在主查询中? Invoice::number($number)
      • 尝试更新。我自己没有测试过,但它可能会起作用
      • 在创建此主题之前,我尝试了类似的方法。我试过你的查询,contacts 属性是这样的:[]
      猜你喜欢
      • 2019-06-13
      • 2013-02-09
      • 2019-03-26
      • 1970-01-01
      • 2013-05-27
      • 2013-05-15
      • 2016-11-03
      • 2014-09-27
      • 1970-01-01
      相关资源
      最近更新 更多