No soy un maestro Eloquent , investigué mucho y no pude reproducir lo que esperaba.
Tengo los siguientes modelos:
<?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); } } Cuando busco una Invoice::class específica:
$invoice = Invoice::number($number) ->latest('started_at') ->with(['contacts']) ->firstOrFail();Necesito que me lo devuelvan así:
{ "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" } Actualmente estoy haciendo esto dentro de 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'); Sin embargo, el volumen de datos es muy grande y me gustaría eliminar EagerLoading y hacer todo en una sola consulta para disminuir el impacto en la base de datos.
¿Alguna idea sobre cómo resolver este problema usando solo Eloquent ?
Un amigo me ayudó con la consulta que me gustaría convertir a 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_atLogré hacer la consulta usando solo elocuente. El rendimiento de mi base de datos ha cambiado drásticamente como se esperaba.
Esta es la consulta usando solo elocuente:
$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();Tuve un problema donde mi consulta no devolvía facturas que no tenían ningún contacto, tuve que hacer un pequeño cambio.
El método join() genera una inner join , la inner join no incluye filas de table A que no tienen elementos en table B
Para superar esta situación, left join . Sin embargo, si la left join no tiene valores en table B , estaba devolviendo mis sumas como null .
Para resolver esto, simplemente usé la función ifnull() .
Esta es la consulta final:
$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();Podrías intentar algo como:
$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();Es posible que pueda integrar esto en su consulta original de la siguiente manera:
$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(); Luego, accedería a los valores como $invoice->contacts->incoming_messages etc. Para que esto funcione al seleccionar más de una factura, es posible que deba agregar groupBy('invoice_id') si Factura-contactos es una relación de 1 a muchos (es decir, solo hay una factura para un contacto) si son muchos, es posible que necesite unirse a la tabla dinámica.
También puedes intentar usar un mutador para esto: https://laravel.com/docs/7.x/eloquent-mutators#defining-a-mutator
public function incomingMessages() { return $this->contacts->sum('incoming_messages'); }Y en su Recurso simplemente llamará:
$this->incomingMessages()