【问题标题】:TYPO3 QueryBuilder - how to find the most recent record for a user?TYPO3 QueryBuilder - 如何查找用户的最新记录?
【发布时间】:2020-04-13 13:31:48
【问题描述】:

这是一个非常明显的数据问题,但我在任何地方都找不到简单的解决方案。

使用 TYPO3 QueryBuilder,如何从每个用户有多个条目的表中为每个用户选择最近的条目?

uid  user_id  value  crdate
1    1        0      123456
2    1        1      123400
3    2        1      123356
4    2        0      123300

我已经尝试了大量原始 SQL 方法,并最终找到了一种基于此解决方案的有效方法 - How can I SELECT rows with MAX(Column value), DISTINCT by another column in 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 的解决方案? 非常感谢

【问题讨论】:

    标签: typo3 doctrine-dbal


    【解决方案1】:

    引用是在 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()

    注意

    如果您使用嵌套选择/子选择 AND 使用命名参数,则必须使用最外层的 queryBuilder 实例来创建命名参数,而不是当前级别的 queryBuilder。

    【讨论】:

      【解决方案2】:

      你是对的——你不能在 TYPO3 中的 join()innerJoin()leftJoin()rightJoin() 中使用子查询作为参数,因为这些值是使用 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();
      

      【讨论】:

      • 探亲回来。再次感谢迈克尔 - 我以前也试过这个,但它产生了错误的结果。 GROUP BY 方法按用户将数据拆分为子组,但随后仅输出每个子组的第一条记录(不是最近的),crdate 字段显示整个子组中的最高 crdate。我尝试使用“ORDER BY crdate DESC”来让最新的记录排在第一位,但 mySQL 拒绝了这一点——它希望您先分组,然后再排序。但是,当您达到 ORDER BY 位时,结果已经减少到每个用户 1 条记录 - 为时已晚。嗯。
      【解决方案3】:

      您可能需要一个子查询。请尝试以下操作。

      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() 查询内部会产生双反引号(`)。我不知道如何摆脱它们,但代码显示了这个概念。

      【讨论】:

      • 嗨,迈克尔,非常感谢您的回答。 Apols 延迟响应 - 圣诞节家庭关闭。我上周尝试了您的方法,正如您指出的那样,子查询被双引号引起来 - 因为 join-> 参数需要一个表名,因此在提供的任何内容中都添加了引号。这就是导致我在这里问的原因,因为子查询不起作用。但是非常感谢您的尝试:-)
      猜你喜欢
      • 1970-01-01
      • 2021-11-22
      • 2012-11-11
      • 1970-01-01
      • 2021-07-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多