mirror of
https://github.com/shlinkio/shlink.git
synced 2025-03-28 20:41:41 +03:00
Load visits and nonBotVisits via sub-queries in ShortUrlListRepository
This commit is contained in:
parent
7d415e40b2
commit
7afd3fd6a2
4 changed files with 53 additions and 39 deletions
|
@ -38,4 +38,6 @@ return static function (ClassMetadata $metadata, array $emConfig): void {
|
||||||
$builder->createManyToOne('shortUrl', ShortUrl\Entity\ShortUrl::class)
|
$builder->createManyToOne('shortUrl', ShortUrl\Entity\ShortUrl::class)
|
||||||
->addJoinColumn('short_url_id', 'id', onDelete: 'CASCADE')
|
->addJoinColumn('short_url_id', 'id', onDelete: 'CASCADE')
|
||||||
->build();
|
->build();
|
||||||
|
|
||||||
|
$builder->addUniqueConstraint(['short_url_id', 'potential_bot', 'slot_id'], 'UQ_slot_per_short_url');
|
||||||
};
|
};
|
||||||
|
|
|
@ -30,30 +30,29 @@ final class Version20240318084804 extends AbstractMigration
|
||||||
$botsCount = $visitsQb->setParameter('potential_bot', '1')->executeQuery()->fetchOne();
|
$botsCount = $visitsQb->setParameter('potential_bot', '1')->executeQuery()->fetchOne();
|
||||||
$nonBotsCount = $visitsQb->setParameter('potential_bot', '0')->executeQuery()->fetchOne();
|
$nonBotsCount = $visitsQb->setParameter('potential_bot', '0')->executeQuery()->fetchOne();
|
||||||
|
|
||||||
|
if ($botsCount > 0) {
|
||||||
|
$this->insertCount($shortUrlId, $botsCount, potentialBot: true);
|
||||||
|
}
|
||||||
|
if ($nonBotsCount > 0) {
|
||||||
|
$this->insertCount($shortUrlId, $nonBotsCount, potentialBot: false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function insertCount(string $shortUrlId, int $count, bool $potentialBot): void
|
||||||
|
{
|
||||||
$this->connection->createQueryBuilder()
|
$this->connection->createQueryBuilder()
|
||||||
->insert('short_url_visits_counts')
|
->insert('short_url_visits_counts')
|
||||||
->values([
|
->values([
|
||||||
'short_url_id' => ':short_url_id',
|
'short_url_id' => ':short_url_id',
|
||||||
'count' => ':count',
|
'count' => ':count',
|
||||||
'potential_bot' => '1',
|
'potential_bot' => ':potential_bot',
|
||||||
])
|
])
|
||||||
->setParameters([
|
->setParameters([
|
||||||
'short_url_id' => $shortUrlId,
|
'short_url_id' => $shortUrlId,
|
||||||
'count' => $botsCount,
|
'count' => $count,
|
||||||
])
|
'potential_bot' => $potentialBot ? '1' : '0',
|
||||||
->executeStatement();
|
|
||||||
$this->connection->createQueryBuilder()
|
|
||||||
->insert('short_url_visits_counts')
|
|
||||||
->values([
|
|
||||||
'short_url_id' => ':short_url_id',
|
|
||||||
'count' => ':count',
|
|
||||||
'potential_bot' => '0',
|
|
||||||
])
|
|
||||||
->setParameters([
|
|
||||||
'short_url_id' => $shortUrlId,
|
|
||||||
'count' => $nonBotsCount,
|
|
||||||
])
|
])
|
||||||
->executeStatement();
|
->executeStatement();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
|
@ -14,13 +14,13 @@ final readonly class ShortUrlWithVisitsSummary
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param array{shortUrl: ShortUrl, visitsCount: string|int, nonBotVisitsCount: string|int} $data
|
* @param array{shortUrl: ShortUrl, visits: string|int, nonBotVisits: string|int} $data
|
||||||
*/
|
*/
|
||||||
public static function fromArray(array $data): self
|
public static function fromArray(array $data): self
|
||||||
{
|
{
|
||||||
return new self($data['shortUrl'], VisitsSummary::fromTotalAndNonBots(
|
return new self($data['shortUrl'], VisitsSummary::fromTotalAndNonBots(
|
||||||
(int) $data['visitsCount'],
|
(int) $data['visits'],
|
||||||
(int) $data['nonBotVisitsCount'],
|
(int) $data['nonBotVisits'],
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,6 +15,7 @@ use Shlinkio\Shlink\Core\ShortUrl\Model\ShortUrlWithVisitsSummary;
|
||||||
use Shlinkio\Shlink\Core\ShortUrl\Model\TagsMode;
|
use Shlinkio\Shlink\Core\ShortUrl\Model\TagsMode;
|
||||||
use Shlinkio\Shlink\Core\ShortUrl\Persistence\ShortUrlsCountFiltering;
|
use Shlinkio\Shlink\Core\ShortUrl\Persistence\ShortUrlsCountFiltering;
|
||||||
use Shlinkio\Shlink\Core\ShortUrl\Persistence\ShortUrlsListFiltering;
|
use Shlinkio\Shlink\Core\ShortUrl\Persistence\ShortUrlsListFiltering;
|
||||||
|
use Shlinkio\Shlink\Core\Visit\Entity\ShortUrlVisitsCount;
|
||||||
use Shlinkio\Shlink\Core\Visit\Entity\Visit;
|
use Shlinkio\Shlink\Core\Visit\Entity\Visit;
|
||||||
|
|
||||||
use function Shlinkio\Shlink\Core\ArrayUtils\map;
|
use function Shlinkio\Shlink\Core\ArrayUtils\map;
|
||||||
|
@ -27,21 +28,33 @@ class ShortUrlListRepository extends EntitySpecificationRepository implements Sh
|
||||||
*/
|
*/
|
||||||
public function findList(ShortUrlsListFiltering $filtering): array
|
public function findList(ShortUrlsListFiltering $filtering): array
|
||||||
{
|
{
|
||||||
|
$buildVisitsSubQuery = function (string $alias, bool $excludingBots): string {
|
||||||
|
$vqb = $this->getEntityManager()->createQueryBuilder();
|
||||||
|
$vqb->select('SUM(' . $alias . '.count)')
|
||||||
|
->from(ShortUrlVisitsCount::class, $alias)
|
||||||
|
->where($vqb->expr()->eq($alias . '.shortUrl', 's'));
|
||||||
|
|
||||||
|
if ($excludingBots) {
|
||||||
|
$vqb->andWhere($vqb->expr()->eq($alias . '.potentialBot', ':potentialBot'));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $vqb->getDQL();
|
||||||
|
};
|
||||||
|
|
||||||
$qb = $this->createListQueryBuilder($filtering);
|
$qb = $this->createListQueryBuilder($filtering);
|
||||||
$qb->select('DISTINCT s AS shortUrl', 'SUM(v.count) AS visitsCount', 'SUM(v2.count) AS nonBotVisitsCount')
|
$qb->select(
|
||||||
->addSelect('SUM(v.count)')
|
'DISTINCT s AS shortUrl',
|
||||||
->leftJoin('s.visitsCounts', 'v')
|
'(' . $buildVisitsSubQuery('v', excludingBots: false) . ') AS ' . OrderableField::VISITS->value,
|
||||||
->leftJoin('s.visitsCounts', 'v2', Join::WITH, $qb->expr()->andX(
|
'(' . $buildVisitsSubQuery('v2', excludingBots: true) . ') AS ' . OrderableField::NON_BOT_VISITS->value,
|
||||||
$qb->expr()->eq('v.shortUrl', 's'),
|
)
|
||||||
$qb->expr()->eq('v.potentialBot', 'false'),
|
|
||||||
))
|
|
||||||
->groupBy('s')
|
|
||||||
->setMaxResults($filtering->limit)
|
->setMaxResults($filtering->limit)
|
||||||
->setFirstResult($filtering->offset);
|
->setFirstResult($filtering->offset)
|
||||||
|
// This param is used in one of the sub-queries, but needs to set in the parent query
|
||||||
|
->setParameter('potentialBot', 0);
|
||||||
|
|
||||||
$this->processOrderByForList($qb, $filtering);
|
$this->processOrderByForList($qb, $filtering);
|
||||||
|
|
||||||
/** @var array{shortUrl: ShortUrl, visitsCount: string, nonBotVisitsCount: string}[] $result */
|
/** @var array{shortUrl: ShortUrl, visits: string, nonBotVisits: string}[] $result */
|
||||||
$result = $qb->getQuery()->getResult();
|
$result = $qb->getQuery()->getResult();
|
||||||
return map($result, static fn (array $s) => ShortUrlWithVisitsSummary::fromArray($s));
|
return map($result, static fn (array $s) => ShortUrlWithVisitsSummary::fromArray($s));
|
||||||
}
|
}
|
||||||
|
@ -54,8 +67,8 @@ class ShortUrlListRepository extends EntitySpecificationRepository implements Sh
|
||||||
match (true) {
|
match (true) {
|
||||||
// With no explicit order by, fallback to dateCreated-DESC
|
// With no explicit order by, fallback to dateCreated-DESC
|
||||||
$fieldName === null => $qb->orderBy('s.dateCreated', 'DESC'),
|
$fieldName === null => $qb->orderBy('s.dateCreated', 'DESC'),
|
||||||
$fieldName === OrderableField::VISITS->value => $qb->orderBy('SUM(v.count)', $order),
|
$fieldName === OrderableField::VISITS->value,
|
||||||
$fieldName === OrderableField::NON_BOT_VISITS->value => $qb->orderBy('SUM(v2.count)', $order),
|
$fieldName === OrderableField::NON_BOT_VISITS->value => $qb->orderBy($fieldName, $order),
|
||||||
default => $qb->orderBy('s.' . $fieldName, $order),
|
default => $qb->orderBy('s.' . $fieldName, $order),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue