TYPO3 QueryBuilder - как найти самую последнюю запись для пользователя?
Это действительно очевидная проблема с данными, но я нигде не могу найти простого решения.
Как с помощью TYPO3 QueryBuilder выбрать самую последнюю запись для каждого пользователя из таблицы, содержащей несколько записей для каждого пользователя?
uid user_id value crdate
1 1 0 123456
2 1 1 123400
3 2 1 123356
4 2 0 123300
Я испробовал множество необработанных подходов SQL и в итоге нашел метод, который работает на основе этого решения - как я могу ВЫБРАТЬ строки с MAX(значение столбца), DISTINCT по другому столбцу в SQL?
SELECT *
FROM `tx_tablename` AS `tt`
INNER JOIN (
SELECT `uid`, `user_id`, MAX(`crdate`) AS `MaxDateTime`
FROM `tx_tablename`
GROUP BY `user_id`
) AS `groupedtt`
ON `tt`.`user_id` = `groupedtt`.`user_id`
AND `tt`.`crdate` = `groupedtt`.`MaxDateTime`
WHERE `tt`.`consent_content` = 3
Но я не вижу, как воспроизвести это в QueryBuilder, поскольку оператор ->join() будет принимать только имена таблиц в качестве параметров, а не SQL, а ->join() будет принимать только одно условие соединения, а не два.
Кто-нибудь еще нашел решение, которое работает в QueryBuilder? Большое спасибо
3 ответа
Цитирование выполняется в TYPO3 QueryBuilder. Вы можете обойти это, используя ConcreteQueryBuilder напрямую.
Но при этом вы должны сами указать идентификатор, иначе возникнут исключения.
Это должно помочь в вашем псевдокоде:
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Utility\GeneralUtility;
...
$subQueryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
->getQueryBuilderForTable('tx_tablename');
$subQuery = $subQueryBuilder
->select('uid', 'user_id')
->from('tx_tablename')
->addSelectLiteral(
$subQueryBuilder->expr()->max('crdate', 'max_crdate')
)
->groupBy('user_id');
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
->getQueryBuilderForTable('tx_tablename');
$queryResult = $queryBuilder
->select('a.*')
->from('tx_tablename', 'a')
;
$queryBuilder
->getConcreteQueryBuilder()
->innerJoin(
$queryBuilder->quoteIdentifier('a'), // !!! important, quote identifier yourself
'(' . $subQuery->getSQL() . ')',
$queryBuilder->quoteIdentifier('b'), // !!! important, quote identifier yourself
$queryBuilder->expr()->andX(
$queryBuilder->expr()->eq('a.user_id', $queryBuilder->quoteIdentifier('b.user_id')),
$queryBuilder->expr()->eq('a.crdate', $queryBuilder->quoteIdentifier('b.max_crdate'))
) // andX()
) // innerJoin()
;
$queryResult = $queryBuilder->execute();
редактировать 1
Пример исправленного кода. НеобходимостьquoteIdentifier()
вместо того createNamedParam()
.
Запись
Если вы используете вложенные выборки / подвыборы И с использованием именованных параметров, вы должны использовать самый внешний экземпляр queryBuilder для создания именованного параметра, а не queryBuilder текущего уровня.
Вы правы - вы не можете использовать подзапросы в качестве аргументов в join()
, innerJoin()
, leftJoin()
, а также rightJoin()
в TYPO3, поскольку эти значения экранируются с помощью quoteIdentifier()
(см. исходный код TYPO3 v10.2 на GitHub) и добавлены обратные кавычки.
Интересно, возвращает ли следующий SQL-запрос результат, который вам нужен:
SELECT `uid`, `user_id`, value, MAX(`crdate`)
FROM `tx_tablename`
GROUP BY `user_id`
HAVING MAX(`crdate`);
В этом случае код Doctrine будет выглядеть следующим образом:
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Utility\GeneralUtility;
...
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
->getQueryBuilderForTable('tx_tablename');
$queryResult = $queryBuilder
->select('uid', 'user_id', 'value')
->from('tx_tablename')
->addSelectLiteral(
$queryBuilder->expr()->max('crdate', 'crdate')
)
->add('having', 'MAX(`crdate`)')
->groupBy('user_id')
->execute();
Для этого вам, вероятно, понадобится подзапрос. Попробуйте следующее.
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Utility\GeneralUtility;
...
$subQueryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
->getQueryBuilderForTable('tx_tablename');
$subQuery = $subQueryBuilder
->select('uid', 'user_id')
->from('tx_tablename')
->addSelectLiteral(
$subQueryBuilder->expr()->max('crdate', 'max_crdate')
)
->groupBy('user_id');
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
->getQueryBuilderForTable('tx_tablename');
$queryResult = $queryBuilder
->select('a.*')
->from('tx_tablename', 'a')
->innerJoin(
'a',
'(' . $subQuery->getSQL() . ')',
'b',
$queryBuilder->expr()->andX(
$queryBuilder->expr()->eq('a.user_id', $queryBuilder->createNamedParameter('b.user_id', \PDO::PARAM_STR)),
$queryBuilder->expr()->eq('a.crdate', $queryBuilder->createNamedParameter('b.max_crdate', \PDO::PARAM_STR))
)
)
->execute();
Тем не менее, код - как он стоит сейчас - производит двойные обратные кавычки (`) внутриinnerJoin()
запрос. Я не знаю, как от них избавиться, но код показывает концепцию.