Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add rudimentary UNION support to the QueryBuilder #6369

Open
wants to merge 1 commit into
base: 4.1.x
Choose a base branch
from

Conversation

sbuerk
Copy link
Contributor

@sbuerk sbuerk commented Apr 25, 2024

Q A
Type feature
Fixed issues #6368

Summary

The UNION operator is used to combine the result-set
of two or more SELECT statements, which all database
vendors supports with usual specialities for each.

Still, there is a common shared subset which works for
all of them:

    SELECT column_name(s) FROM table1
    WHERE ...

    UNION <ALL | DISTINCT>

    SELECT column_name(s) FROM table2
    WHERE ...

    ORDER BY ...
    LIMIT x OFFSET y

with shared common requirements:

  • Each SELECT must return the same fields
    in number, naming and order.

  • Each SELECT must not have ORDER BY,
    expect MySQL allowing it to be used as sub
    query expression encapsulated in parentheses.

Taking the shared requirements and working behaviour,
it is possible to provide a generic support to the
QueryBuilder with a minimalistic surface addition now,
and following methods are added:

  • union(string|QueryBuilder ...$unionParts) and
    addUnion(string|QueryBuilder ...$unionParts) to
    create a UNION query retrieving unique rows
  • unionAll(string|QueryBuilder ...$unionParts) and
    addUnionAll(string|QueryBuilder ...$unionParts) to
    create a UNION ALL query retrieving eventually
    duplicated rows.

This follows the generic logic as select(...) and
addSelect(...) along with introducing new internal
QueryType::UNION_DISTINCT and QueryType::UNION_ALL
enum cases.

Additional to the consideration to allow SQL strings
and QueryBuilder for union(), unionAll(), addUnion()
and addUnionAll() and minimize the direct handling
of miss-configuration to the number of provided parts
and let other issues like the field (order, count, naming)
or not allowed order by handling to the database itself.
With that, vendor specific implementation can be done if
required.

Technically, the SQL build process is dispatched to a
DefaultUnionSQLBuilder along with an UnionSQLBuilder
interface, which also allows application to implement
custom behaviour if required.

Example:

$platform = $connection->getDatabasePlatform();
$qb       = $>connection->createQueryBuilder();
$select10 = $platform->getDummySelectSQL('2 as field_one');
$select20 = $platform->getDummySelectSQL('1 as field_one');
$qb->union($select10,    /*$select11, $select12, ... */)
   ->addUnion($select20, /*$select21, $select22, ... */)
   ->setMaxResults(1)
   ->setFirstResult(1)
   ->orderBy('field_one', 'ASC');
$rows = $qb->executeQuery()->fetchAllAssociative();

Unit and functional tests are added to demonstrate the
implementation and cover it for future changes.

Resolves: #6368

docs/en/reference/query-builder.rst Outdated Show resolved Hide resolved
src/SQL/Builder/DefaultUnionSQLBuilder.php Outdated Show resolved Hide resolved
@sbuerk sbuerk force-pushed the union-support branch 5 times, most recently from c5f2b12 to 71c4d7a Compare April 25, 2024 15:23
@sbuerk sbuerk requested a review from derrabus April 26, 2024 09:30
The `UNION` operator is used to combine the result-set
of two or more `SELECT` statements, which all database
vendors supports with usual specialities for each.

Still, there is a common shared subset which works for
all of them:

```
    SELECT column_name(s) FROM table1
    WHERE ...

    UNION <ALL | DISTINCT>

    SELECT column_name(s) FROM table2
    WHERE ...

    ORDER BY ...
    LIMIT x OFFSET y
```

with shared common requirements:

* Each `SELECT` must return the same fields
  in number, naming and order.

* Each `SELECT` **must not** have `ORDER BY`,
  expect MySQL allowing it to be used as sub
  query expression encapsulated in parentheses.

Taking the shared requirements and working behaviour,
it is possible to provide a generic support to the
QueryBuilder with a minimalistic surface addition now,
and following methods are added:

* `union(string|QueryBuilder ...$unionParts)` and
  `addUnion(string|QueryBuilder ...$unionParts)` to
   create a `UNION` query retrieving unique rows
* `unionAll(string|QueryBuilder ...$unionParts)` and
  `addUnionAll(string|QueryBuilder ...$unionParts) to
   create a `UNION ALL` query retrieving eventually
   duplicated rows.

This follows the generic logic as `select(...)` and
`addSelect(...)` along with introducing new internal
`QueryType::UNION_DISTINCT` and `QueryType::UNION_ALL`
enum cases.

Additional to the consideration to allow SQL strings
and QueryBuilder for `union()`, `unionAll(), `addUnion()`
and `addUnionAll()` and minimize the direct handling
of miss-configuration to the number of provided parts
and let other issues like the field (order, count, naming)
or not allowed order by handling to the database itself.
With that, vendor specific implementation can be done if
required.

Technically, the SQL build process is dispatched to a
`DefaultUnionSQLBuilder` along with an `UnionSQLBuilder`
interface, which also allows application to implement
custom behaviour if required.

Example:

```php
$platform = $connection->getDatabasePlatform();
$qb       = $>connection->createQueryBuilder();
$select10 = $platform->getDummySelectSQL('2 as field_one');
$select20 = $platform->getDummySelectSQL('1 as field_one');
$qb->union($select10,    /*$select11, $select12, ... */)
   ->addUnion($select20, /*$select21, $select22, ... */)
   ->setMaxResults(1)
   ->setFirstResult(1)
   ->orderBy('field_one', 'ASC');
$rows = $qb->executeQuery()->fetchAllAssociative();
```

Unit and functional tests are added to demonstrate the
implementation and cover it for future changes.

Resolves: doctrine#6368
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants