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()запрос. Я не знаю, как от них избавиться, но код показывает концепцию.

Другие вопросы по тегам