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

t0berius's avatar

livewire 2 double queries executed

After taking a look into the debug bar I was able to find a query which is executed twice a time when loading the page:

users-table.blade.php

<div>
    <div class="row mb-4">
        <div class="offset-md-9 col">
            <input wire:model="search" class="form-control" type="text" autofocus="" placeholder="@lang('staff.userSearchPlaceholder')">
        </div>
    </div>

    {{ $users->links() }}

    @if($users->isNotEmpty())
        <div class="table-responsive">
            <table class="table table-sm">
                <thead>
                    <tr>
                        <th>
                            <a wire:click.prevent="sortBy('id')" role="button" href="#">
                                @lang('staff.usersTableID')
                                @include('layouts.backend.sortIcons', ['field' => 'id', 'sortField' => $this->sortField, 'sortAsc' => $this->sortAsc])
                            </a>
                        </th>
                    </tr>
                </thead>
                <tbody>
    @endif
    @forelse ($users as $user)
        <tr>
            <td>{{$user->id}}</td>
        </tr>
    @empty
        <div class="alert alert-info text-center" role="alert">
            <i class="fas fa-info-circle"></i> @lang('staff.userSearchEmpty')
        </div>
    @endforelse

    @if($users->isNotEmpty())
                </tbody>
            </table>
        </div>
    @endif

</div>

users.blade.php

@extends('layouts.backend.main')

@section('content')
    <div class="col-12 col-md-9 py-3">
        <div class="page-header mb-3">
            <h1>@lang('staff.usersHeadline')</h1>
        </div>
        @include('layouts.messages')
        @livewire('users-table')
    </div>
@endsection

UsersTable.php

class UsersTable extends Component
{
    use WithPagination, AuthorizesRequests;

    public $sortField = 'id';
    public $sortAsc = false;
    public $search = '';
    public $requestSection;
    protected $paginationTheme = 'bootstrap';

    public function mount()
    {
        $this->requestSection = request('filter', null);
    }

    public function sortBy($field)
    {
        if ($this->sortField === $field) {
            $this->sortAsc = ! $this->sortAsc;
        } else {
            $this->sortAsc = true;
        }
        $this->sortField = $field;
    }

    public function render()
    {
        $this->authorize('view', User::class);

        $users = (new User)->newQuery();

        //search for ID if field is set
        $search = $this->search;
        if($this->search !== '')
            $users->where(function($query) use ($search) {
                $query->where('id', $search)->orWhere('name', 'like', "%" . $search . "%");
            });

        //prepare basic search string first, make sure we only scope for users, not active sellers
        $users->doesntHave('sellerDetails')
        ->withCount([
            'orders as orders_count' => function ($query) {
                $query->finalized();
            },
        ]);

        //check which user section was requested (using GET parameter inside query)
        switch ($this->requestSection) {
            case 'banned':
                $users->whereHas('sanctions', function ($query) {
                    $query->active();
                })->latest();
            break;
        }

        //attach sort filter
        $users->orderBy($this->sortField, $this->sortAsc ? 'asc' : 'desc');

        return view('livewire.users-table', [
            'users' => $users->paginate(25),
        ]);
    }

}

Double extecuted query is:

select count(*) as aggregate from `users` where not exists (select * from `seller_users` where `users`.`id` = `seller_users`.`user_id` and `seller_users`.`deleted_at` is null)
select count(*) as aggregate from `users` where not exists (select * from `seller_users` where `users`.`id` = `seller_users`.`user_id` and `seller_users`.`deleted_at` is null)

It's executed two times and I'm at a loss, why livewire is firing two queries.

0 likes
10 replies
LaryAI's avatar
Level 58

The issue seems to be with the render method in the UsersTable component. The render method is responsible for rendering the component and returning the view. In this case, it is executing the query twice.

To fix this issue, you can modify the render method to execute the query only once. Here's the modified code:

public function render()
{
    $this->authorize('view', User::class);

    $users = (new User)->newQuery();

    // search for ID if field is set
    $search = $this->search;
    if ($this->search !== '') {
        $users->where(function ($query) use ($search) {
            $query->where('id', $search)->orWhere('name', 'like', "%" . $search . "%");
        });
    }

    // prepare basic search string first, make sure we only scope for users, not active sellers
    $users->doesntHave('sellerDetails')
        ->withCount([
            'orders as orders_count' => function ($query) {
                $query->finalized();
            },
        ]);

    // check which user section was requested (using GET parameter inside query)
    switch ($this->requestSection) {
        case 'banned':
            $users->whereHas('sanctions', function ($query) {
                $query->active();
            })->latest();
            break;
    }

    // attach sort filter
    $users->orderBy($this->sortField, $this->sortAsc ? 'asc' : 'desc');

    $users = $users->paginate(25);

    return view('livewire.users-table', compact('users'));
}

By assigning the paginated results to the $users variable before returning the view, you ensure that the query is executed only once.

This should resolve the issue of the query being executed twice.

t0berius's avatar

Query is still executed twice, even if I use the code from AI bot.

Snapey's avatar

do you have any $with in your user model?

t0berius's avatar

@snapey Thanks for your reply, no, only withCount() is being used, as you can see in the code above.

Maybe this helps. using the debugbar I can see, one query is executed from controller, another one directly from the view:

780μs
/app/Http/Livewire/UsersTable.php:69
510μs
view::2760944dbd61aa17807e02208b8fcf7d4cba0468:17

Queries are exactly the same, and debugbar already detects this too:

9 statements were executed, 2 of which were duplicated, 7 unique. Show All
Snapey's avatar

so what is line 17 in the (compiled) view?

t0berius's avatar

@snapey

How I can access the compiled view? When I click on the double executed query inside the debugbar I only see:

view::2760944dbd61aa17807e02208b8fcf7d4cba0468:17

If I read it correctly my views are constructed: users.blade.php

@extends('layouts.backend.main')

@section('content')
    <div class="col-12 col-md-9 py-3">
        <div class="page-header mb-3">
            <h1>@lang('staff.usersHeadline')</h1>
        </div>
        @include('layouts.messages')
        @livewire('users-table')
    </div>
@endsection

which is loading the livewire file:

users-table.blade.php

<div>
    <div class="row mb-4">
        <div class="offset-md-9 col">
            <input wire:model="search" class="form-control" type="text" autofocus="" placeholder="@lang('staff.userSearchPlaceholder')">
        </div>
    </div>

    {{ $users->links() }}

    @if($users->isNotEmpty())
        <div class="table-responsive">
            <table class="table table-sm">
                <thead>
                    <tr>
                        <th>
                            <a wire:click.prevent="sortBy('id')" role="button" href="#">
                                @lang('staff.usersTableID')
                                @include('layouts.backend.sortIcons', ['field' => 'id', 'sortField' => $this->sortField, 'sortAsc' => $this->sortAsc])
                            </a>
                        </th>
                    </tr>
                </thead>
                <tbody>
				...

I think if I count starting on users.blade.php line 17 should be equal to:

        {{ $users->links() }}

Update

Even if I delete all lines inside the users-table.blade.php and clear all views (php artisan view:clear) I still get the doubled query add exact the same line.

Snapey's avatar
Snapey
Best Answer
Level 122

You can see the compiled views in the storage/framework/views folder. Search for the filename that debugbar shows.

1 like
t0berius's avatar

Narrowed it down to a sidebar element which is showin the exact same count as livewire is using too. Think there's no way to prevent this double query, since livewire will run it anyway (for pagination) too and the sidebar will be loaded before livewire is fired.

-Thank you @snapey .

t0berius's avatar

@snapey

Do you see any chance to get rid of the double query on the first page load? I'm using the following structure of views (condensed):

users.blade.php

@extends('layouts.backend.main')

@section('content')
  		...
        @include('layouts.messages')
        @livewire('users-table')
    	...
@endsection

main.blade.php

<body>

    @include('layouts.headbar')
    ...
    <div class="container mt-3">

        @include('layouts.messages')

        @section('navbar')
        @show

        @if(!isset($extendDiv)) <div class="row mb-5"> @endif
            @section('sidebar')
            @show

            @section('content')
            @show
        @if(!isset($extendDiv)) </div> @endif

    </div>
</body>

sidebar.blade.php

                @if(Request::is('backend/user*'))
                    <a href="{{action('Staff\UserController@getUsers')}}" class="{{ is_null(Request::query('filter')) ? 'active' : "''" }}"> @lang('staff.sidebarUsersAll') <span class="badge badge-danger">{{App\Models\User::doesntHave('sellerDetails')->count()}}</span></a>
                    <a href="{{action('Staff\UserController@getUsers', ['filter' => 'banned'])}}" class="{{ Request::query('filter') == 'banned' ? 'active' : "''" }}"> @lang('staff.sidebarUsersBanned') <span class="badge badge-danger">{{App\Models\User::doesntHave('sellerDetails')->whereHas('sanctions', function ($query) {
                            $query->active();
                        })->count()}}</span></a>
                @endif

users-table.blade.php

{{ $users->links() }}

@if($users->isNotEmpty())
    <div class="table-responsive">
        <table class="table table-sm">
            <thead>
                <tr>
                    <th>
                        <a wire:click.prevent="sortBy('id')" role="button" href="#">
                            @lang('staff.usersTableID')
                            @include('layouts.backend.sortIcons', ['field' => 'id', 'sortField' => $this->sortField, 'sortAsc' => $this->sortAsc])
                        </a>
                    </th>
                </tr>
            </thead>
            <tbody>
@endif
@forelse ($users as $user)
    <tr>
        <td>{{$user->id}}</td>
    </tr>
@empty
    <div class="alert alert-info text-center" role="alert">
        <i class="fas fa-info-circle"></i> @lang('staff.userSearchEmpty')
    </div>
@endforelse

As you can see the query, which I noticed as doubled is caused by sidebar.blade.php

App\Models\User::doesntHave('sellerDetails')->count()

Do you see any chance to get rid of this double query on pageload?

As soon as livewire is used (by entering something into the search field / interacting by using a sort button), the default livewire query will differ from the "hard coded" query inside the sidebar.blade.php file, but is there any way you see to make this a bit more elegant?

Please or to participate in this conversation.