Skip to content

Commit 65d89f2

Browse files
authored
Merge pull request #3008 from Arkhas/optimize_count_queries
feat: Optimize countQuery with complex select
2 parents 1c42c78 + 4f403dd commit 65d89f2

File tree

2 files changed

+118
-5
lines changed

2 files changed

+118
-5
lines changed

src/QueryDataTable.php

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
use Illuminate\Database\Query\Expression;
99
use Illuminate\Http\JsonResponse;
1010
use Illuminate\Support\Collection;
11+
use Illuminate\Support\Facades\DB;
1112
use Illuminate\Support\Str;
1213
use Yajra\DataTables\Utilities\Helper;
1314

@@ -48,6 +49,13 @@ class QueryDataTable extends DataTableAbstract
4849
*/
4950
protected bool $keepSelectBindings = false;
5051

52+
/**
53+
* Flag to ignore the selects in count query.
54+
*
55+
* @var bool
56+
*/
57+
protected bool $ignoreSelectInCountQuery = false;
58+
5159
/**
5260
* @param QueryBuilder $builder
5361
*/
@@ -156,10 +164,20 @@ public function prepareCountQuery(): QueryBuilder
156164
$builder = clone $this->query;
157165

158166
if ($this->isComplexQuery($builder)) {
167+
$builder->select(DB::raw('1'));
168+
if ($this->ignoreSelectInCountQuery || ! $this->isComplexQuery($builder)) {
169+
return $this->getConnection()
170+
->query()
171+
->fromRaw('('.$builder->toSql().') count_row_table')
172+
->setBindings($builder->getBindings());
173+
}
174+
175+
$builder = clone $this->query;
176+
159177
return $this->getConnection()
160-
->query()
161-
->fromRaw('('.$builder->toSql().') count_row_table')
162-
->setBindings($builder->getBindings());
178+
->query()
179+
->fromRaw('('.$builder->toSql().') count_row_table')
180+
->setBindings($builder->getBindings());
163181
}
164182

165183
$row_count = $this->wrap('row_count');
@@ -818,4 +836,16 @@ public function getFilteredQuery(): QueryBuilder
818836

819837
return $this->getQuery();
820838
}
839+
840+
/**
841+
* Ignore the selects in count query.
842+
*
843+
* @return $this
844+
*/
845+
public function ignoreSelectsInCountQuery(): static
846+
{
847+
$this->ignoreSelectInCountQuery = true;
848+
849+
return $this;
850+
}
821851
}

tests/Unit/QueryDataTableTest.php

Lines changed: 85 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,69 @@ public function test_complex_query_are_wrapped_and_countable()
2828
);
2929

3030
$this->assertQueryWrapped(true, $dataTable->prepareCountQuery());
31+
$this->assertQueryHasNoSelect(false, $dataTable->prepareCountQuery());
3132
$this->assertEquals(60, $dataTable->count());
3233
}
3334

35+
public function test_complex_query_use_select_in_count()
36+
{
37+
/** @var \Yajra\DataTables\QueryDataTable $dataTable */
38+
$dataTable = app('datatables')->of(
39+
DB::table('users')
40+
->select('users.*')
41+
->addSelect([
42+
'last_post_id' => DB::table('posts')
43+
->whereColumn('posts.user_id', 'users.id')
44+
->orderBy('created_at')
45+
->select('id'),
46+
])
47+
->orderBy(DB::table('posts')->whereColumn('posts.user_id', 'users.id')->orderBy('created_at')->select('created_at')
48+
)
49+
);
50+
51+
$this->assertQueryHasNoSelect(false, $dataTable->prepareCountQuery());
52+
$this->assertEquals(20, $dataTable->count());
53+
}
54+
55+
public function test_complex_query_can_ignore_select_in_count()
56+
{
57+
/** @var \Yajra\DataTables\QueryDataTable $dataTable */
58+
$dataTable = app('datatables')->of(
59+
DB::table('users')
60+
->select('users.*')
61+
->addSelect([
62+
'last_post_id' => DB::table('posts')
63+
->whereColumn('posts.user_id', 'users.id')
64+
->orderBy('created_at')
65+
->select('id'),
66+
])
67+
->orderBy(DB::table('posts')->whereColumn('posts.user_id', 'users.id')->orderBy('created_at')->select('created_at')
68+
)
69+
)->ignoreSelectsInCountQuery();
70+
71+
$this->assertQueryHasNoSelect(true, $dataTable->prepareCountQuery());
72+
$this->assertEquals(20, $dataTable->count());
73+
}
74+
75+
public function test_simple_queries_with_complexe_select_are_wrapped_without_selects()
76+
{
77+
/** @var \Yajra\DataTables\QueryDataTable $dataTable */
78+
$dataTable = app('datatables')->of(
79+
DB::table('users')
80+
->select('users.*')
81+
->addSelect([
82+
'last_post_id' => DB::table('posts')
83+
->whereColumn('posts.user_id', 'users.id')
84+
->orderBy('created_at')
85+
->select('id'),
86+
])
87+
);
88+
89+
$this->assertQueryWrapped(true, $dataTable->prepareCountQuery());
90+
$this->assertQueryHasNoSelect(true, $dataTable->prepareCountQuery());
91+
$this->assertEquals(20, $dataTable->count());
92+
}
93+
3494
public function test_simple_queries_are_not_wrapped_and_countable()
3595
{
3696
/** @var \Yajra\DataTables\QueryDataTable $dataTable */
@@ -42,9 +102,20 @@ public function test_simple_queries_are_not_wrapped_and_countable()
42102
$this->assertEquals(20, $dataTable->count());
43103
}
44104

105+
public function test_complexe_queries_can_be_wrapped_and_countable()
106+
{
107+
/** @var \Yajra\DataTables\QueryDataTable $dataTable */
108+
$dataTable = app('datatables')->of(
109+
User::with('posts')->select('users.*')
110+
);
111+
112+
$this->assertQueryWrapped(false, $dataTable->prepareCountQuery());
113+
$this->assertEquals(20, $dataTable->count());
114+
}
115+
45116
/**
46-
* @param $expected bool
47-
* @param $query \Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Query\Builder
117+
* @param $expected bool
118+
* @param $query \Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Query\Builder
48119
* @return void
49120
*/
50121
protected function assertQueryWrapped($expected, $query)
@@ -53,4 +124,16 @@ protected function assertQueryWrapped($expected, $query)
53124

54125
$this->assertSame($expected, Str::endsWith($sql, 'count_row_table'), "'{$sql}' is not wrapped");
55126
}
127+
128+
/**
129+
* @param $expected bool
130+
* @param $query \Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Query\Builder
131+
* @return void
132+
*/
133+
public function assertQueryHasNoSelect($expected, $query)
134+
{
135+
$sql = $query->toSql();
136+
137+
$this->assertSame($expected, Str::startsWith($sql, 'select * from (select 1 from'), "'{$sql}' is not wrapped");
138+
}
56139
}

0 commit comments

Comments
 (0)