Как получить элементы, которых нет в коллекции в Laravel Eloquent?

В моем проекте Laravel 6.x у меня есть модели Product, Warehouse и WarehouseProduct.

В продукте я храню базовую информацию о своих продуктах. В WarehouseProduct я храню информацию о количестве товаров на складе. Конечно, у меня много складов со многими товарами.

Мой Product выглядит так:

class Product extends Model
{
    protected $fillable = [
        'name',
        'item_number',
        // ...
    ];
}

Warehouse выглядит так:

class Warehouse extends Model
{
    protected $fillable = [
        'name',
        'address',
        // ...
    ];

    public function products() {
        return $this->hasMany(WarehouseProduct::class);
    }

    public function missingProduct() {
        // here I need to return a Product collection which are not in this Warehouse or the
        // stored amount is 0
    }
}

Наконец, WarehouseProduct выглядит так:

class WarehouseProduct extends Model
{
    protected $fillable = [
        'product_id',
        'warehouse_id',
        'amount',
        // ...
    ];

    public function product() {
        return $this->belongsTo(Product::class, 'product_id');
    }

    public function warehouse() {
        return $this->belongsTo(Warehouse::class, 'warehouse_id');
    }

Как я могу получить коллекцию Product, которая не хранится в Warehouse или сумма равна 0?


person netdjw    schedule 19.05.2020    source источник
comment
Вы имеете в виду один конкретный экземпляр Warehouse или любой?   -  person Karol Sobański    schedule 19.05.2020


Ответы (2)


Что-то вроде этого должно работать:

use App\Product;

public function missingProduct() {
    $excludedProducts = $this->products()->where('amount', '>', 0)->pluck('id');

    return Product::whereNotIn('id', $excludedProducts)->get();
}

Основываясь на решении @KarolSobański, когда вы добавляете отношение warehouse_products к своей модели продукта:

use App\Product;
use Illuminate\Database\Eloquent\Builder;

public function missingProduct() {
    return Product::whereDoesntHave('warehouse_products', function (Builder $query) {
        $query->where('warehouse_id', $this->id);
    })->orWhereHas('warehouse_products', function (Builder $query) {
        $query->where('warehouse_id', $this->id);
        $query->where('amount', 0);
    })->get();
}
person Remul    schedule 19.05.2020
comment
На самом деле это не очень хорошее решение - вы делаете два запроса к базе данных вместо одного. Вы должны использовать отношения в своем запросе, чтобы избежать такой ситуации. - person Karol Sobański; 19.05.2020
comment
@KarolSobański Я не мог придумать решение, которое использует нормальные отношения, которые предоставляет laravel, поэтому я не думаю, что два запроса в этом случае плохие, но не могли бы вы добавить решение, использующее нормальные отношения, было бы интересно посмотреть. - person Remul; 19.05.2020
comment
добавил ответ. По крайней мере, одно из предложенных решений должно работать - person Karol Sobański; 19.05.2020
comment
Спасибо, первый код работает, второй нет. - person netdjw; 19.05.2020

Самый короткий ответ может быть примерно таким:

Product::doesntHave('warehouse_products')
       ->orWhereHas('warehouse_products', function (Builder $query) {
           $query->where('amount', '=', 0)
       })->get();

Хотя я не уверен, что вышеизложенное работает.

Но следующий более длинный запрос, безусловно, решает проблему:

Product::where(function ($query) {
    $query->doesntHave('warehouse_products');
})->orWhere(function ($query) {
    $query->whereHas('warehouse_products', function (Builder $query) {
       $query->where('amount', '=', 0);
    });
})->get();
person Karol Sobański    schedule 19.05.2020