Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

secondman's avatar

Where Doesn't Have Relation

I have a model with nested relations : Products.records.parts ...

I need to select all the products that have records without parts.

I thought this was it, but not so much:

$products = Product::whereIn('id', $this->selected)
    ->with(['records', function ($query) {
        $query->whereDoesntHave('parts');
    }])->get();

What did I miss?

1 like
6 replies
secondman's avatar

How is that going to get me deeply nested relations that are null?

That would be for a first level relation.

Product::hasMany(Record::class);
Record::hasMany(Part::class);

I need all the products that have records with no parts.

This won't get me there.

Thanks though, I did read the docs before I came here.

secondman's avatar

For this query I just ended up filtering the collection:

$products = Product::whereIn('id', $this->selected)
    ->with('records.parts')->get();

$this->products = $this->filterParts($products);

protected function filterParts(Collection $products): Collection
{
    $filtered = $products->filter(function ($product) {
        return $product->records->filter(function ($record) {
            return $record->whereDoesntHave('parts');
        });
    });

    return $filtered;
}

But for the next deeper nested relation I'll have to use something else.

I now have to filter another level down and check a column for null.

This one:

protected function filterFactories()
{
    $filtered = $this->products->filter(function ($product) {
        return $product->records->filter(function ($record) {
            return $record->materials->filter(function ($material) {
                return !is_null($material->factory_material_id);
            });
        });
    });

    $this->factories = $filtered;
}

Doesn't work.

There is a single material that does have a value in the factory_material_id column so $this->factories should have a single product, but I'm not quite there yet.

secondman's avatar

Well this works but it's disgusting :(

protected function filterFactories()
{
    $filtered = [];

    foreach ($this->products as $key => $product) {
        foreach ($product->records as $record) {
            foreach ($record->materials as $material) {
                if (!is_null($material->factory_material_id)) {
                    $filtered[] = $product;
                    $this->products->forget($key);
                }
            }
        }
    }

    $this->factories = collect($filtered);
}

Anyone with some mad collection skills that can assist in refactoring this I would be greatly appreciative.

Tray2's avatar

Wouldn't this work?

$products = Product::whereIn('id', $this->selected)
  ->with(['records', function ($query) {
      $query->doesntHave('parts');
  }])->get();
MichalOravec's avatar
$products = Product::with(['records' => function ($query) {
    $query->doesntHave('parts');
}])->whereIn('id', $this->selected)->doesntHave('records.parts')->get();
1 like

Please or to participate in this conversation.