zlatan
zlatan

Reputation: 4019

Advanced where Laravel eloquent query

I'm currently stuck on a single where Laravel query. The problem is, I want to filter some data depending on latest record from a given table. Currently, my query looks like this:

$clients = $clients->whereDoesntHave('bill')->orWhereHas('bill', function ($query) use ($activeSchoolYear){
     $query
         ->where('created_at', '<', Carbon::parse($activeSchoolYear->start));
 })

Where $activeSchoolYear->start is today's date.

The problem occurs if there are multiple records in my bills table. So for an example, if I have two records with separate dates:

ID | Name  | created_at          |
1  | test  | 2019-09-09 15:31:25|
2  | test2 | 2018-09-10 16:42:44|

The query still return both dates, even if one of them is false. I think that query looks for all the records in database, and still returns the given user, because one of the dates is correct. My question is, is it possible to target only the latest row from database. Or this can't be done using eloquent. Any help is appreciated.

EDIT:

As @Caddy suggested, I used a query scope, but I modified it a bit:

public function scopeNoActiveBill($query){
  return $query
            ->groupBy('created_at')->take(1)
            ->where('created_at', '<=', Carbon::create(2019, 9, 1));
}

I tried to take only the last row from the bills table. Then, inside controller, this is my query:

$clients = $clients->whereDoesntHave('bill')
     ->orWhereHas('bill', function ($query) use ($activeSchoolYear){
            $query->noActiveBill();
      })
      ->whereHas('contracts', function ($query) use ($request) {
            $query
                  ->where('contract_type', '<', 2)
                  ->where('signed', 1)
                  ->where('school_year_id', $request['school_year']);
       });

So, I want both clients without any bills, or clients with bills that fit the given criteria. The query works fine if client has only one bill, but the problem is when client has more than one bill. If any of those bills fits the given scope, it returns the bill. I want to only check the latest record, and return the bill if that records fits the scope criteria.

EDIT 2:

Thanks to @Caddy DZ's asnwer, I managed to get desired result. I just changed condition to watch for clients that don't have bills created past given date. Query scope:

public function scopeNoActiveBill($query){
     return $query->where('created_at', '>=', Carbon::create(2019, 9, 1));
}

Controller:

$clients = $clients->whereDoesntHave('bill')
    ->orWhereDoesntHave('bill', function ($query) use ($activeSchoolYear){
        $query->noActiveBill();
    });

Upvotes: 2

Views: 1301

Answers (2)

Salim Djerbouh
Salim Djerbouh

Reputation: 11064

What you're looking for is relationship query scopes like this

$clients = App\Client::with(['bills' => function ($query) {
   $query->today();
}])->get();

Add today() scope to your Bill model like so
Bill.php

public function scopeToday($query)
{
    // Change today by $activeSchoolYear if necessary
    return $query->whereDate('created_at', '<', today());
}

Assuming a hasMany relationship in the Client

public function bills()
{
    return $this->hasMany(Bill::class);
}

And data seeded like this
DatabaseSeeder.php

// Will be returned because whereDoesntHave('bills')
$clientWithoutBills = Client::create([]);
$client = Client::create([]);
$client->bills()->createMany([
    [
        'name' => 'test',
        'created_at' => Carbon\Carbon::yesterday()
    ],
    [
        // Will not be returned because created today (not < today)
        'name' => 'test1'
    ],
    [
        // Will not be returned because created tomorrow
        'name' => 'test2',
        'created_at' => Carbon\Carbon::tomorrow()
    ]
]);

Results

[
    {
        "id": 1,
        "created_at": "2019-09-09 16:20:24",
        "updated_at": "2019-09-09 16:20:24",
        "bills": []
    },
    {
        "id": 2,
        "created_at": "2019-09-09 16:20:24",
        "updated_at": "2019-09-09 16:20:24",
        "bills": [
            {
                "id": 1,
                "name": "test",
                "client_id": 2,
                "created_at": "2019-09-08 00:00:00",
                "updated_at": "2019-09-09 16:20:24"
            }
        ]
    }
]

Hope this helps

Upvotes: 1

Dino Numić
Dino Numić

Reputation: 1462

You can use Eloquent whereDate().

->whereDate('created_at', '<', Carbon::parse($activeSchoolYear->start));

Upvotes: 0

Related Questions