Skip to content

Commit 57247ed

Browse files
author
d-ph
committed
Make CountWalker use COUNT(*) when $distinct is explicitly set to false (#11552)
This change makes CountWalker use COUNT(*) instead of COUNT(tbl.id), when the user declared that their query does not need to use (SELECT) DISTINCT, which is commonly the case when there are no JOINs in the query, or when the JOINs are only *ToOne. Research showed that COUNT(*) allows databases to use index(-only) scans more eagerly from any of the indexed columns, especially when the query is using a WHERE-condition that filters on an indexed column.
1 parent 1281707 commit 57247ed

File tree

2 files changed

+34
-16
lines changed

2 files changed

+34
-16
lines changed

src/Tools/Pagination/CountWalker.php

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -37,27 +37,31 @@ public function walkSelectStatement(SelectStatement $selectStatement): void
3737
throw new RuntimeException('Cannot count query which selects two FROM components, cannot make distinction');
3838
}
3939

40-
$fromRoot = reset($from);
41-
$rootAlias = $fromRoot->rangeVariableDeclaration->aliasIdentificationVariable;
42-
$rootClass = $this->getMetadataForDqlAlias($rootAlias);
43-
$identifierFieldName = $rootClass->getSingleIdentifierFieldName();
40+
$distinct = $this->_getQuery()->getHint(self::HINT_DISTINCT);
4441

45-
$pathType = PathExpression::TYPE_STATE_FIELD;
46-
if (isset($rootClass->associationMappings[$identifierFieldName])) {
47-
$pathType = PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION;
48-
}
42+
$countPathExpressionOrLiteral = '*';
43+
if ($distinct) {
44+
$fromRoot = reset($from);
45+
$rootAlias = $fromRoot->rangeVariableDeclaration->aliasIdentificationVariable;
46+
$rootClass = $this->getMetadataForDqlAlias($rootAlias);
47+
$identifierFieldName = $rootClass->getSingleIdentifierFieldName();
48+
49+
$pathType = PathExpression::TYPE_STATE_FIELD;
50+
if (isset($rootClass->associationMappings[$identifierFieldName])) {
51+
$pathType = PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION;
52+
}
4953

50-
$pathExpression = new PathExpression(
51-
PathExpression::TYPE_STATE_FIELD | PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION,
52-
$rootAlias,
53-
$identifierFieldName,
54-
);
55-
$pathExpression->type = $pathType;
54+
$countPathExpressionOrLiteral = new PathExpression(
55+
PathExpression::TYPE_STATE_FIELD | PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION,
56+
$rootAlias,
57+
$identifierFieldName,
58+
);
59+
$countPathExpressionOrLiteral->type = $pathType;
60+
}
5661

57-
$distinct = $this->_getQuery()->getHint(self::HINT_DISTINCT);
5862
$selectStatement->selectClause->selectExpressions = [
5963
new SelectExpression(
60-
new AggregateExpression('count', $pathExpression, $distinct),
64+
new AggregateExpression('count', $countPathExpressionOrLiteral, $distinct),
6165
null,
6266
),
6367
];

tests/Tests/ORM/Tools/Pagination/CountWalkerTest.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,20 @@ public function testCountQuery(): void
2727
);
2828
}
2929

30+
public function testCountQueryWithoutDistinctUsesCountStar(): void
31+
{
32+
$query = $this->entityManager->createQuery(
33+
'SELECT p, c, a FROM Doctrine\Tests\ORM\Tools\Pagination\BlogPost p JOIN p.category c JOIN p.author a',
34+
);
35+
$query->setHint(Query::HINT_CUSTOM_TREE_WALKERS, [CountWalker::class]);
36+
$query->setHint(CountWalker::HINT_DISTINCT, false);
37+
38+
self::assertEquals(
39+
'SELECT count(*) AS sclr_0 FROM BlogPost b0_ INNER JOIN Category c1_ ON b0_.category_id = c1_.id INNER JOIN Author a2_ ON b0_.author_id = a2_.id',
40+
$query->getSQL(),
41+
);
42+
}
43+
3044
public function testCountQueryMixedResultsWithName(): void
3145
{
3246
$query = $this->entityManager->createQuery(

0 commit comments

Comments
 (0)