96 lines
4.0 KiB
Plaintext
96 lines
4.0 KiB
Plaintext
<code type="php">
|
|
// retrieve the first 20 users and all their associated phonenumbers
|
|
|
|
$users = $conn->query("SELECT u.*, p.* FROM User u, u.Phonenumber p LIMIT 20");
|
|
|
|
foreach($users as $user) {
|
|
print ' --- '.$user->name.' --- \n';
|
|
|
|
foreach($user->Phonenumber as $p) {
|
|
print $p->phonenumber.'\n';
|
|
}
|
|
}
|
|
</code>
|
|
|
|
|
|
+++ Introduction
|
|
|
|
Propably the most complex feature DQL parser has to offer is its LIMIT clause parser. Not only does the DQL LIMIT clause parser take care of LIMIT database portability it is capable of limiting the number of records instead of rows by using complex query analysis and subqueries.
|
|
|
|
|
|
+++ Driver portability
|
|
|
|
DQL LIMIT clause is portable on all supported databases. Special attention have been paid to following facts:
|
|
|
|
* Only Mysql, Pgsql and Sqlite implement LIMIT / OFFSET clauses natively
|
|
* In Oracle / Mssql / Firebird LIMIT / OFFSET clauses need to be emulated in driver specific way
|
|
* The limit-subquery-algorithm needs to execute to subquery separately in mysql, since mysql doesn't yet support LIMIT clause in subqueries
|
|
* Pgsql needs the order by fields to be preserved in SELECT clause, hence LS-algorithm needs to take this into consideration when pgsql driver is used
|
|
* Oracle only allows < 30 object identifiers (= table/column names/aliases), hence the limit subquery must use as short aliases as possible and it must avoid alias collisions with the main query.
|
|
|
|
|
|
+++ The limit-subquery-algorithm
|
|
|
|
The limit-subquery-algorithm is an algorithm that DQL parser uses internally when one-to-many / many-to-many relational data is being fetched simultaneously. This kind of special algorithm is needed for the LIMIT clause to limit the number of records instead of sql result set rows.
|
|
|
|
This behaviour can be overwritten using the configuration system (at global, connection or table level) using:
|
|
<code type="php">
|
|
$table->setAttribute(Doctrine::ATTR_QUERY_LIMIT, Doctrine::LIMIT_ROWS);
|
|
$table->setAttribute(Doctrine::ATTR_QUERY_LIMIT, Doctrine::LIMIT_RECORDS); // revert
|
|
</code>
|
|
|
|
In the following example we have users and phonenumbers with their relation being one-to-many. Now lets say we want fetch the first 20 users and all their related phonenumbers.
|
|
|
|
Now one might consider that adding a simple driver specific LIMIT 20 at the end of query would return the correct results. Thats wrong, since we you might get anything between 1-20 users as the first user might have 20 phonenumbers and then record set would consist of 20 rows.
|
|
|
|
DQL overcomes this problem with subqueries and with complex but efficient subquery analysis. In the next example we are going to fetch first 20 users and all their phonenumbers with single efficient query. Notice how the DQL parser is smart enough to use column aggregation inheritance even in the subquery and how it's smart enough to use different aliases for the tables in the subquery to avoid alias collisions.
|
|
|
|
DQL QUERY:
|
|
|
|
<code type="sql">
|
|
SELECT u.id, u.name, p.* FROM User u LEFT JOIN u.Phonenumber p LIMIT 20
|
|
</code>
|
|
|
|
SQL QUERY:
|
|
|
|
<code type="sql">
|
|
SELECT
|
|
e.id AS e__id,
|
|
e.name AS e__name,
|
|
p.id AS p__id,
|
|
p.phonenumber AS p__phonenumber,
|
|
p.entity_id AS p__entity_id
|
|
FROM entity e
|
|
LEFT JOIN phonenumber p ON e.id = p.entity_id
|
|
WHERE e.id IN (
|
|
SELECT DISTINCT e2.id
|
|
FROM entity e2
|
|
WHERE (e2.type = 0) LIMIT 20) AND (e.type = 0)
|
|
</code>
|
|
|
|
In the next example we are going to fetch first 20 users and all their phonenumbers and only those users that actually have phonenumbers with single efficient query, hence we use an INNER JOIN. Notice how the DQL parser is smart enough to use the INNER JOIN in the subquery.
|
|
|
|
DQL QUERY:
|
|
|
|
<code type="sql">
|
|
SELECT u.id, u.name, p.* FROM User u LEFT JOIN u.Phonenumber p LIMIT 20
|
|
</code>
|
|
|
|
SQL QUERY:
|
|
|
|
<code type="sql">
|
|
SELECT
|
|
e.id AS e__id,
|
|
e.name AS e__name,
|
|
p.id AS p__id,
|
|
p.phonenumber AS p__phonenumber,
|
|
p.entity_id AS p__entity_id
|
|
FROM entity e
|
|
LEFT JOIN phonenumber p ON e.id = p.entity_id
|
|
WHERE e.id IN (
|
|
SELECT DISTINCT e2.id
|
|
FROM entity e2
|
|
INNER JOIN phonenumber p2 ON e2.id = p2.entity_id
|
|
WHERE (e2.type = 0) LIMIT 20) AND (e.type = 0)
|
|
</code>
|