1
0
mirror of synced 2024-12-14 23:26:04 +03:00

Adding a little more information to the dql chapter.

This commit is contained in:
Jonathan H. Wage 2010-05-14 15:23:51 -04:00
parent 5a79963bd2
commit f99a096d01

View File

@ -2,17 +2,16 @@
DQL stands for **D**octrine **Q**uery **L**anguage and is an Object Query Language derivate that is very similar to the **H**ibernate **Q**uery **L**anguage (HQL) or the **J**ava **P**ersistence **Q**uery **L**anguage (JPQL).
In essence, DQL provides powerful querying capabilities over your object model. Imagine all your objects lying around in some storage (like an object database). When writing DQL queries, think about querying that storage to pick a certain subset of your objects.
In essence, DQL provides powerful querying capabilities over your object model. Imagine all your objects lying around in some storage (like an object database). When writing DQL queries, think about querying that storage to pick a certain subset of your objects.
> **CAUTION**
> A common mistake for beginners is to mistake DQL for being just some form of SQL
> and therefore trying to use table names and column names or join arbitrary tables
> together in a query. You need to think about DQL as a query language for your object
> model, not for your relational schema.
> model, not for your relational schema.
DQL is case in-sensitive, except for namespace, class and field names, which are case sensitive.
++ Types of DQL queries
DQL as a query language has SELECT, UPDATE and DELETE constructs that map to their corresponding
@ -21,7 +20,7 @@ have to be introduced into the persistence context through `EntityManager#persis
consistency of your object model.
DQL SELECT statements are a very powerful way of retrieving parts of your domain model that are
not accessible via assocations. Additionally they allow to retrieve entities and their associations
not accessible via associations. Additionally they allow to retrieve entities and their associations
in one single sql select statement which can make a huge difference in performance in contrast
to using several queries.
@ -37,9 +36,10 @@ The select clause of a DQL query specifies what appears in the query result. The
Here is an example that selects all users with an age > 20:
[sql]
SELECT u FROM MyProject\Model\User u WHERE u.age > 20
[php]
$query = $em->createQuery('SELECT u FROM MyProject\Model\User u WHERE u.age > 20');
$users = $query->getResult();
Lets examine the query:
* `u` is a so called identification variable or alias that refers to the `MyProject\Model\User` class. By placing this alias in the SELECT clause we specify that we want all instances of the User class that are matched by this query appear in the query result.
@ -52,7 +52,7 @@ The SELECT clause allows to specify both class identification variables that sig
of a complete entity class or just fields of the entity using the syntax `u.name`.
Combinations of both are also allowed and it is possible to wrap both fields and
identification values into aggregation and DQL functions. Numerical fields can
be part of computations using mathematical operatos. See the sub-section on [DQL Functions, Aggregates and Operations](#dqlfn)
be part of computations using mathematical operations. See the sub-section on [DQL Functions, Aggregates and Operations](#dqlfn)
on more information.
+++ Joins
@ -69,21 +69,23 @@ Example:
Regular join of the address:
[sql]
SELECT u FROM User u JOIN u.address a WHERE a.city = 'Berlin'
[php]
$query = $em->createQuery("SELECT u FROM User u JOIN u.address a WHERE a.city = 'Berlin'");
$users = $query->getResult();
Fetch join of the address:
[sql]
SELECT u, a FROM User u JOIN u.address a WHERE a.city = 'Berlin'
[php]
$query = $em->createQuery("SELECT u, a FROM User u JOIN u.address a WHERE a.city = 'Berlin'");
$users = $query->getResult();
When Doctrine hydrates a query with fetch-join it returns the class in the FROM clause on the
root level of the result array. In the previous example an array of User instances is returned
and the address of each user is fetched and hydrated into the `User#address` variable. If you access
the address Doctrine does not need to
the address Doctrine does not need to lazy load the association with another query.
> **NOTE**
> Doctrine allows you to walk all the assocations between all the objects in your domain model.
> Doctrine allows you to walk all the associations between all the objects in your domain model.
> Objects that were not already loaded from the database are replaced with lazy load proxy instances.
> Non-loaded Collections are also replaced by lazy-load instances that fetch all the contained objects upon
> first access. However relying on the lazy-load mechanism leads to many small queries executed
@ -99,95 +101,194 @@ Named parameters are specified with ":name1", ":name2" and so on.
+++ DQL SELECT Examples
This section contains a large set of DQL queries and some explainations of what is happening.
This section contains a large set of DQL queries and some explanations of what is happening.
The actual result also depends on the hydration mode.
[sql]
-- Hydrate all Users
SELECT u FROM MyProject\Model\User u
Hydrate all User entities:
-- Retrieve the IDs of all CmsUsers
SELECT u.id FROM CmsUser u
[php]
$query = $em->createQuery('SELECT u FROM MyProject\Model\User u');
$users = $query->getResult(); // array of User objects
-- Retrieve the IDs of all users that have written an article
SELECT DISTINCT a.user.id FROM CmsArticle a
Retrieve the IDs of all CmsUsers:
-- Retrieve all articles and sort them by the name of the articles users instance
SELECT a FROM CmsArticle a ORDER BY a.user.name ASC
[php]
$query = $em->createQuery('SELECT u.id FROM CmsUser u');
$ids = $query->getResult(); // array of CmsUser ids
-- Retrieve the Username and Name of a CmsUser
SELECT u.username, u.name FROM CmsUser u
Retrieve the IDs of all users that have written an article:
-- Retrieve a ForumUser and his single associated entity
SELECT u, a FROM ForumUser u JOIN u.avatar a
[php]
$query = $em->createQuery('SELECT DISTINCT a.user.id FROM CmsArticle a');
$ids = $query->getResult(); // array of CmsUser ids
-- Retrieve a CmsUser and fetch join all the phonenumbers he has
SELECT u, p FROM CmsUser u JOIN u.phonenumbers p
Retrieve all articles and sort them by the name of the articles users instance:
-- Hydrate a result in Ascending or Descending Order
SELECT u FROM ForumUser u ORDER BY u.id ASC
SELECT u FROM ForumUser u ORDER BY u.id DESC
[php]
$query = $em->createQuery('SELECT a FROM CmsArticle a ORDER BY a.user.name ASC');
$articles = $query->getResult(); // array of CmsArticle objects
-- Using Aggregate Functions
SELECT COUNT(u.id) FROM CmsUser u GROUP BY u.id
Retrieve the Username and Name of a CmsUser:
-- With WHERE Clause and Positional Parameter
SELECT u FROM ForumUser u WHERE u.id = ?1
[php]
$query = $em->createQuery('SELECT u.username, u.name FROM CmsUser u');
$users = $query->getResults(); // array of CmsUser username and id values
echo $users[0]['username'];
-- With WHERE Clause and Named Parameter
SELECT u FROM ForumUser u WHERE u.username = :name
Retrieve a ForumUser and his single associated entity:
-- With Nested Conditions in WHERE Clause
SELECT u from ForumUser u WHERE (u.username = :name OR u.username = :name2) AND u.id = :id
[php]
$query = $em->createQuery('SELECT u, a FROM ForumUser u JOIN u.avatar a');
$users = $query->getResult(); // array of ForumUser objects with the avatar association loaded
echo get_class($users[0]->getAvatar());
-- With COUNT DISTINCT
SELECT COUNT(DISTINCT u.name) FROM CmsUser
Retrieve a CmsUser and fetch join all the phonenumbers he has:
-- With Arithmetic Expression in WHERE clause
SELECT u FROM CmsUser u WHERE ((u.id + 5000) * u.id + 3) < 10000000
[php]
$query = $em->createQuery('SELECT u, p FROM CmsUser u JOIN u.phonenumbers p');
$users = $query->getResult(); // array of CmsUser objects with the phonenumbers association loaded
$phonenumbers = $users[0]->getPhonenumbers();
-- Using multiple classes fetched using a FROM clause (all returned on the root level of the result)
SELECT u, a FROM CmsUser u, CmsArticle a WHERE u.id = a.user.id
Hydrate a result in Ascending:
-- Using a LEFT JOIN to hydrate all user-ids and optionally associated article-ids
SELECT u.id, a.id FROM CmsUser u LEFT JOIN u.articles a
[php]
$query = $em->createQuery('SELECT u FROM ForumUser u ORDER BY u.id ASC');
$users = $query->getResult(); // array of ForumUser objects
-- Restricting a JOIN clause by additional conditions
SELECT u FROM CmsUser u LEFT JOIN u.articles a WITH a.topic LIKE '%foo%'
Or in Descending Order:
-- Using several Fetch JOINs
SELECT u, a, p, c FROM CmsUser u
JOIN u.articles a
JOIN u.phonenumbers p
JOIN a.comments c
[php]
$query = $em->createQuery('SELECT u FROM ForumUser u ORDER BY u.id DESC');
$users = $query->getResult(); // array of ForumUser objects
-- BETWEEN in WHERE clause
SELECT u.name FROM CmsUser u WHERE u.id BETWEEN ?1 AND ?2
Using Aggregate Functions:
-- DQL Functions in WHERE clause
SELECT u.name FROM CmsUser u WHERE TRIM(u.name) = 'someone'
[php]
$query = $em->createQuery('SELECT COUNT(u.id) FROM Entities\User u');
$count = $query->getSingleScalarResult();
-- IN() Expression
SELECT u.name FROM CmsUser u WHERE u.id IN(46)
SELECT u FROM CmsUser u WHERE u.id IN (1, 2)
SELECT u FROM CmsUser u WHERE u.id NOT IN (1)
With WHERE Clause and Positional Parameter:
-- CONCAT() DQL Function
SELECT u.id FROM CmsUser u WHERE CONCAT(u.name, 's') = ?1
SELECT CONCAT(u.id, u.name) FROM CmsUser u WHERE u.id = ?1
[php]
$query = $em->createQuery('SELECT u FROM ForumUser u WHERE u.id = ?1');
$users = $query->getResult(); // array of ForumUser objects
-- EXISTS in WHERE clause with correlated Subquery
SELECT u.id FROM CmsUser u
WHERE EXISTS (SELECT p.phonenumber FROM CmsPhonenumber p WHERE p.phonenumber = u.id)
With WHERE Clause and Named Parameter:
-- Get all users who are members of $group.
SELECT u.id FROM CmsUser u WHERE :param MEMBER OF u.groups
[php]
$query = $em->createQuery('SELECT u FROM ForumUser u WHERE u.username = :name');
$users = $query->getResult(); // array of ForumUser objects
-- Get all users that have more than 1 phonenumber
SELECT u FROM CmsUser u WHERE SIZE(u.phonenumbers) > 1
With Nested Conditions in WHERE Clause:
-- Get all users that have no phonenumber
SELECT u FROM CmsUser u WHERE u.phonenumbers IS EMPTY
[php]
$query = $em->createQuery('SELECT u from ForumUser u WHERE (u.username = :name OR u.username = :name2) AND u.id = :id');
$users = $query->getResult(); // array of ForumUser objects
With COUNT DISTINCT:
[php]
$query = $em->createQuery('SELECT COUNT(DISTINCT u.name) FROM CmsUser
$users = $query->getResult(); // array of ForumUser objects
With Arithmetic Expression in WHERE clause:
$query = $em->createQuery('SELECT u FROM CmsUser u WHERE ((u.id + 5000) * u.id + 3) < 10000000');
$users = $query->getResult(); // array of ForumUser objects
Using a LEFT JOIN to hydrate all user-ids and optionally associated article-ids:
[php]
$query = $em->createQuery('SELECT u.id, a.id as article_id FROM CmsUser u LEFT JOIN u.articles a');
$results = $query->getResult(); // array of user ids and every article_id for each user
Restricting a JOIN clause by additional conditions:
[php]
$query = $em->createQuery("SELECT u FROM CmsUser u LEFT JOIN u.articles a WITH a.topic LIKE '%foo%'");
$users = $query->getResult();
Using several Fetch JOINs:
[php]
$query = $em->createQuery('SELECT u, a, p, c FROM CmsUser u JOIN u.articles a JOIN u.phonenumbers p JOIN a.comments c');
$users = $query->getResult();
BETWEEN in WHERE clause:
[php]
$query = $em->createQuery('SELECT u.name FROM CmsUser u WHERE u.id BETWEEN ?1 AND ?2');
$usernames = $query->getResult();
DQL Functions in WHERE clause:
[php]
$query = $em->createQuery("SELECT u.name FROM CmsUser u WHERE TRIM(u.name) = 'someone'");
$usernames = $query->getResult();
IN() Expression:
[php]
$query = $em->createQuery('SELECT u.name FROM CmsUser u WHERE u.id IN(46)');
$usernames = $query->getResult();
$query = $em->createQuery('SELECT u FROM CmsUser u WHERE u.id IN (1, 2)');
$users = $query->getResult();
$query = $em->createQuery('SELECT u FROM CmsUser u WHERE u.id NOT IN (1)');
$users = $query->getResult();
CONCAT() DQL Function:
[php]
$query = $em->createQuery("SELECT u.id FROM CmsUser u WHERE CONCAT(u.name, 's') = ?1");
$ids = $query->getResult();
$query = $em->createQuery('SELECT CONCAT(u.id, u.name) FROM CmsUser u WHERE u.id = ?1');
$idUsernames = $query->getResult();
EXISTS in WHERE clause with correlated Subquery
[php]
$query = $em->createQuery('SELECT u.id FROM CmsUser u WHERE EXISTS (SELECT p.phonenumber FROM CmsPhonenumber p WHERE p.phonenumber = u.id)');
$ids = $query->getResult();
Get all users who are members of $group.
[php]
$query = $em->createQuery('SELECT u.id FROM CmsUser u WHERE :param MEMBER OF u.groups');
$ids = $query->getResult();
Get all users that have more than 1 phonenumber
[php]
$query = $em->createQuery('SELECT u FROM CmsUser u WHERE SIZE(u.phonenumbers) > 1');
$users = $query->getResult();
Get all users that have no phonenumber
[php]
$query = $em->createQuery('SELECT u FROM CmsUser u WHERE u.phonenumbers IS EMPTY');
$users = $query->getResult();
++++ Partial Object Syntax
By default when you run a DQL query in Doctrine and select only a subset of the
fields for a given entity, you do not receive objects back. Instead, you receive
only arrays as a flat rectangular result set, similar to how you would if you
were just using SQL directly and joining some data.
If you want to select partial objects you can use the `partial` DQL keyword:
[php]
$query = $em->createQuery('SELECT partial u.{id, username} FROM CmsUser u');
$users = $query->getResult(); // array of partially loaded CmsUser objects
You use the partial syntax when joining as well:
[php]
$query = $em->createQuery('SELECT partial u.{id, username}, partial a.{id, name} FROM CmsUser u JOIN u.articles a');
$users = $query->getResult(); // array of partially loaded CmsUser objects
+++ Using INDEX BY
@ -357,14 +458,13 @@ An instance of the `Doctrine\ORM\Query` class represents a DQL query. You create
[php]
// $em instanceof EntityManager
// example1: passing a DQL string
$q = $em->createQuery('select u from MyProject\Model\User u');
// example2: usin setDql
$q = $em->createQuery();
$q->setDql('select u from MyProject\Model\User u');
+++ Query Result Formats
@ -397,7 +497,7 @@ A pure result usually looks like this:
[1] => Object
[2] => Object
...
A mixed result on the other hand has the following general structure:
array
@ -431,30 +531,78 @@ Here is how the result could look like:
...
And here is how you would access it in PHP code:
[php]
foreach ($results as $row) {
echo "Name: " . $row[0]->getName();
echo "Name UPPER: " . $row['nameUpper'];
}
You may have observed that in a mixed result, the object always ends up on index 0 of a result row.
+++ Hydration Mode Asumptions
+++ Hydration Modes
Each of the Hydration Modes makes assumptions about how the result is returned to userland. You should
Each of the Hydration Modes makes assumptions about how the result is returned to user land. You should
know about all the details to make best use of the different result formats:
The constants for the different hydration modes are:
* Query::HYDRATE_OBJECT
* Query::HYDRATE_ARRAY
* Query::HYDRATE_SCALAR
* Query::HYDRATE_SINGLE_SCALAR
++++ Object Hydration
Object hydration hydrates the result set into the object graph:
[php]
$query = $em->createQuery('SELECT u FROM CmsUser u');
$users = $query->getResult(Query::HYDRATE_OBJECT);
++++ Array Hydration
++++ Scalar Hydration Details
You can run the same query with array hydration and the result set is hydrated into
an array that represents the object graph:
[php]
$query = $em->createQuery('SELECT u FROM CmsUser u');
$users = $query->getResult(Query::HYDRATE_ARRAY);
You can use the `getArrayResult()` shortcut as well:
[php]
$users = $query->getArrayResult();
++++ Scalar Hydration
If you want to return a flat rectangular result set instead of an object graph
you can use scalar hydration:
[php]
$query = $em->createQuery('SELECT u FROM CmsUser u');
$users = $query->getResult(Query::HYDRATE_SCALAR);
echo $users[0]['u_id'];
The following assumptions are made about selected fields using Scalar Hydration:
1. Fields from classes are prefixed by the DQL alias in the result. A query of the kind 'SELECT u.name ..' returns a key 'u_name' in the result rows.
++++ Single Scalar Hydration
If you a query which returns just a single scalar value you can use single scalar
hydration:
[php]
$query = $em->createQuery('SELECT COUNT(a.id) FROM CmsUser u LEFT JOIN u.articles a WHERE u.username = ?1 GROUP BY u.id');
$query->setParameter(1, 'jwage');
$numArticles = $query->getResult(Query::HYDRATE_SCALAR);
You can use the `getSingleScalarResult()` shortcut as well:
[php]
$numArticles = $query->getSingleScalarResult();
+++ Iterating Large Resultsets
There are situations when a query you want to execute returns a very large result-set that needs
@ -583,7 +731,7 @@ The following context-free grammar, written in an EBNF variant, describes the Do
QueryLanguage ::= SelectStatement | UpdateStatement | DeleteStatement
+++ Statements
+++ Statements
SelectStatement ::= SelectClause FromClause [WhereClause] [GroupByClause] [HavingClause] [OrderByClause]
UpdateStatement ::= UpdateClause [WhereClause]
@ -736,12 +884,12 @@ The following context-free grammar, written in an EBNF variant, describes the Do
ScalarExpression ::= SimpleArithmeticExpression | StringPrimary | DateTimePrimary | StateFieldPathExpression
BooleanPrimary | CaseExpression | EntityTypeExpression
CaseExpression ::= GeneralCaseExpression | SimpleCaseExpression |
CaseExpression ::= GeneralCaseExpression | SimpleCaseExpression |
CoalesceExpression | NullifExpression
GeneralCaseExpression ::= "CASE" WhenClause {WhenClause}* "ELSE" ScalarExpression
GeneralCaseExpression ::= "CASE" WhenClause {WhenClause}* "ELSE" ScalarExpression
"END"
WhenClause ::= "WHEN" ConditionalExpression "THEN" ScalarExpression
SimpleCaseExpression ::= "CASE" CaseOperand SimpleWhenClause {SimpleWhenClause}*
WhenClause ::= "WHEN" ConditionalExpression "THEN" ScalarExpression
SimpleCaseExpression ::= "CASE" CaseOperand SimpleWhenClause {SimpleWhenClause}*
"ELSE" ScalarExpression "END"
CaseOperand ::= StateFieldPathExpression | TypeDiscriminator
SimpleWhenClause ::= "WHEN" ScalarExpression "THEN" ScalarExpression
@ -761,7 +909,7 @@ The following context-free grammar, written in an EBNF variant, describes the Do
AggregateExpression ::= ("AVG" | "MAX" | "MIN" | "SUM") "(" ["DISTINCT"] StateFieldPathExpression ")" |
"COUNT" "(" ["DISTINCT"] (IdentificationVariable | SingleValuedPathExpression) ")"
+++ Other Expressions
QUANTIFIED/BETWEEN/COMPARISON/LIKE/NULL/EXISTS
@ -793,11 +941,4 @@ QUANTIFIED/BETWEEN/COMPARISON/LIKE/NULL/EXISTS
"SUBSTRING" "(" StringPrimary "," SimpleArithmeticExpression "," SimpleArithmeticExpression ")" |
"TRIM" "(" [["LEADING" | "TRAILING" | "BOTH"] [char] "FROM"] StringPrimary ")" |
"LOWER" "(" StringPrimary ")" |
"UPPER" "(" StringPrimary ")"
"UPPER" "(" StringPrimary ")"