Stefan Padberg
Stefan Padberg

Reputation: 527

TYPO3 DBAL Querybuilder: Nested SELECT statements?

Is it possible to build nested SELECT statements like the one below using the DBAL QueryBuilder?

SELECT i.id, i.stable_id, i.version, i.title
FROM initiatives AS i
INNER JOIN (
    SELECT stable_id, MAX(version) AS max_version FROM initiatives GROUP BY stable_id
) AS tbl1
ON i.stable_id = tbl1.stable_id AND i.version = tbl1.max_version
ORDER BY i.stable_id ASC

The goal is to query an external non TYPO3 table which contains different versions of each data set. Only the data set with the highest version number should be rendered. The database looks like this:

id, stable_id, version, [rest of the data row]

stable_id is the external id of the data set. id is the internal autoincrement id. And version is also incremented automatically.

Code example:

$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($this->table);
$result = $queryBuilder
    ->select(...$this->select)
    ->from($this->table)
    ->join(
        'initiatives',
        $queryBuilder
            ->select('stable_id, MAX(version) AS max_version' )
            ->from('initiatives')
            ->groupBy('stable_id'),
        'tbl1',
        $queryBuilder->and(
            $queryBuilder->expr()->eq(
                'initiatives.stable_id',
                $queryBuilder->quoteIdentifier('tbl1.stable_id')
            ),
            $queryBuilder->expr()->eq(
                'initiatives.version',
                $queryBuilder->quoteIdentifier('tbl1.max_version')
            )
        )
    )
    ->orderBy('stable_id', 'DESC')

I cannot figure out the correct syntax for the ON ... AND statement. Any idea?

Upvotes: 0

Views: 1556

Answers (2)

Stefan Padberg
Stefan Padberg

Reputation: 527

Short answer: it is not possible because the table to be joined in is generated on the fly. The related expression is back-ticked and thus causes an SQL error.

But: The SQL query can be changed to the following SQL query which does basically the same:

SELECT i1.id,stable_id, version, title, p.name, l.name, s.name
FROM initiatives i1
WHERE version = (
    SELECT MAX(i2.version)
    FROM initiatives i2
    WHERE i1.stable_id = i2.stable_id
)
ORDER BY stable_id ASC

And this can be rebuild with the DBAL queryBuilder:

$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($this->table);
$result = $queryBuilder
    ->select(...$this->select)
    ->from($this->table)
    ->where(
        $queryBuilder->expr()->eq(
            'initiatives.version',
            '(SELECT MAX(i2.version) FROM initiatives i2 WHERE initiatives.stable_id = i2.stable_id)'
        ),
    ->orderBy('stable_id', 'DESC')
    ->setMaxResults( 50 )
    ->execute();

Upvotes: 0

Jonas Eberle
Jonas Eberle

Reputation: 2921

Extbase queries have JOIN capabilities but are otherwise very limited. You could use custom SQL (see ->statement() here), though.

A better API to build complex queries is the (Doctrine DBAL) QueryBuilder, including support for JOINs, database functions like MAX() and raw expressions (->addSelectLiteral()). Make sure to read until the ExpressionBuilder where it gets interesting.

So Extbase queries are useful in order to retrieve Extbase (model) objects. It can make implicit use of its knowledge of your data structure in order to save you some code but only supports rather simple queries.

The (Doctrine DBAL) QueryBuilder fulfills all other needs. If needed, you can convert the raw data to Extbase models, too. (for example $propertyMapper->convert($data, Job::class)).

I realize that we lack clear distinguishing between the two because they were both known at some time as "QueryBuilder", but they are totally different. That's why I like to add "Doctrine" when referring to the non-Extbase one.

An example with a JOIN ON multiple criteria.

$q = TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(TYPO3\CMS\Core\Database\ConnectionPool::class)
    ->getQueryBuilderForTable('fe_users');

$res = $q->select('*')
    ->from('tt_content', 'c')
    ->join(
        'c',
        'be_users',
        'bu',
        $q->expr()->andX(
            $q->expr()->eq(
                'c.cruser_id', $q->quoteIdentifier('bu.uid')
            ),
            $q->expr()->comparison(
                '2', '=', '2'
            )
        )
    )
    ->setMaxResults(5)
    ->execute()
    ->fetchAllAssociative();

Upvotes: 1

Related Questions