DDC-3224: getResult(HYDRATE_OBJECT) with joined query is returning reduced number of rows #3992

Closed
opened 2026-01-22 14:33:10 +01:00 by admin · 16 comments
Owner

Originally created by @doctrinebot on GitHub (Jul 23, 2014).

Originally assigned to: @Ocramius on GitHub.

Jira issue originally created by user gondo:

given that i have these 2 entities (pseodocode):

/****
 * @ORM\Table(name="entity1", options={"collate"="utf8*unicode*ci", "charset"="utf8"})
 * @ORM\Entity(repositoryClass="Entity1Repository")
 */
class Entity1 {
    /****
     * @var integer
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    protected $id;

    /****
     * @var string
     * @ORM\Column(name="name", type="string")
     */
    protected $name;

    /****
     * @var Entity2[]
     * @ORM\OneToMany(targetEntity="Entity2", mappedBy="entity1")
     */
    protected $entity2;
}

/****
 * @ORM\Table(name="entity2", options={"collate"="utf8*unicode*ci", "charset"="utf8"})
 * @ORM\Entity()
 */
class Entity2 {
    /****
     * @var integer
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    protected $id;

    /****
     * @var \DateTime
     * @ORM\Column(name="date", type="datetime")
     */
    protected $date;

    /****
     * @var Entity1
     * @ORM\ManyToOne(targetEntity="Entity1", inversedBy="entity2", fetch="EAGER")
     */
    protected $entity1;
}

tables and data

entity1:
|| id || name ||
| 1 | Jhon |
| 2 | Clare |

entity2:
|| id || date || entity1_id ||
| 1 | 2011-01-01 00:00:01 | 1 |
| 2 | 2012-02-02 00:00:02 | 1 |
| 3 | 2013-03-03 00:00:03 | 2 |
| 4 | 2014-04-04 00:00:04 | 2 |

my query builder

use Doctrine\ORM\EntityRepository;

class Entity1Repository extends EntityRepository
{
    public function getData()
    {
        $qb = $this
            ->createQueryBuilder('Entity1')
            ->select('Entity1, Entity2.date')
            ->join('Entity1.entity2', 'Entity2', Join::WITH, 'Entity2.date > :date')
            ->setParameter('date', '2000-01-01 00:00:01')
        ;
        $result1 = $qb->getQuery()->getArrayResult(); // HYDRATE_ARRAY
        $result = $qb->getQuery()->getResult(); // HYDRATE_OBJECT

//        return $result1;
//        return $result2;
    }
}

proper result is this:

|| id || name || date ||
| 1 | Jhon | 2011-01-01 00:00:01 |
| 1 | Jhon | 2012-02-02 00:00:02 |
| 2 | Clare | 2013-03-03 00:00:03 |
| 2 | Clare | 2014-04-04 00:00:04 |

what is happening

$result1 = $qb->getQuery()->getArrayResult(); // HYDRATE_ARRAY

is really returning proper number of rows

BUT and here comes the BUG finally:

$result2 = $qb->getQuery()->getResult(); // HYDRATE_OBJECT

is returning just 2 rows:

|| id || name || date ||
| 1 | Jhon | 2011-01-01 00:00:01 |
| 2 | Clare | 2013-03-03 00:00:03 |

this is because somehow entities are made unique.

my workaround

as a workaround, what i have to do is, to exectute 2 queries. 1st to get just Entity1.ids joined with Entity2.dates by using getArrayResult()
and second query to get Entity1 objeces by unique ids from 1st query.
and than manualy join those results in php.

Originally created by @doctrinebot on GitHub (Jul 23, 2014). Originally assigned to: @Ocramius on GitHub. Jira issue originally created by user gondo: given that i have these 2 entities (pseodocode): ``` /**** * @ORM\Table(name="entity1", options={"collate"="utf8*unicode*ci", "charset"="utf8"}) * @ORM\Entity(repositoryClass="Entity1Repository") */ class Entity1 { /**** * @var integer * @ORM\Column(name="id", type="integer") * @ORM\Id * @ORM\GeneratedValue(strategy="AUTO") */ protected $id; /**** * @var string * @ORM\Column(name="name", type="string") */ protected $name; /**** * @var Entity2[] * @ORM\OneToMany(targetEntity="Entity2", mappedBy="entity1") */ protected $entity2; } /**** * @ORM\Table(name="entity2", options={"collate"="utf8*unicode*ci", "charset"="utf8"}) * @ORM\Entity() */ class Entity2 { /**** * @var integer * @ORM\Column(name="id", type="integer") * @ORM\Id * @ORM\GeneratedValue(strategy="AUTO") */ protected $id; /**** * @var \DateTime * @ORM\Column(name="date", type="datetime") */ protected $date; /**** * @var Entity1 * @ORM\ManyToOne(targetEntity="Entity1", inversedBy="entity2", fetch="EAGER") */ protected $entity1; } ``` ## tables and data entity1: || id || name || | 1 | Jhon | | 2 | Clare | entity2: || id || date || entity1_id || | 1 | 2011-01-01 00:00:01 | 1 | | 2 | 2012-02-02 00:00:02 | 1 | | 3 | 2013-03-03 00:00:03 | 2 | | 4 | 2014-04-04 00:00:04 | 2 | ## my query builder ``` use Doctrine\ORM\EntityRepository; class Entity1Repository extends EntityRepository { public function getData() { $qb = $this ->createQueryBuilder('Entity1') ->select('Entity1, Entity2.date') ->join('Entity1.entity2', 'Entity2', Join::WITH, 'Entity2.date > :date') ->setParameter('date', '2000-01-01 00:00:01') ; $result1 = $qb->getQuery()->getArrayResult(); // HYDRATE_ARRAY $result = $qb->getQuery()->getResult(); // HYDRATE_OBJECT // return $result1; // return $result2; } } ``` ## proper result is this: || id || name || date || | 1 | Jhon | 2011-01-01 00:00:01 | | 1 | Jhon | 2012-02-02 00:00:02 | | 2 | Clare | 2013-03-03 00:00:03 | | 2 | Clare | 2014-04-04 00:00:04 | ## what is happening ``` $result1 = $qb->getQuery()->getArrayResult(); // HYDRATE_ARRAY ``` is really returning proper number of rows ### BUT and here comes the BUG finally: ``` $result2 = $qb->getQuery()->getResult(); // HYDRATE_OBJECT ``` is returning just 2 rows: || id || name || date || | 1 | Jhon | 2011-01-01 00:00:01 | | 2 | Clare | 2013-03-03 00:00:03 | this is because somehow entities are made unique. ## my workaround as a workaround, what i have to do is, to exectute 2 queries. 1st to get just Entity1.ids joined with Entity2.dates by using `getArrayResult()` and second query to get Entity1 objeces by unique ids from 1st query. and than manualy join those results in php.
admin added the Invalid label 2026-01-22 14:33:10 +01:00
admin closed this issue 2026-01-22 14:33:11 +01:00
Author
Owner

@doctrinebot commented on GitHub (Jul 23, 2014):

Comment created by @ocramius:

I see that you are using Join::WITH, but not providing a conditional in the example. Are you filtering a fetch-joined association?

@doctrinebot commented on GitHub (Jul 23, 2014): Comment created by @ocramius: I see that you are using `Join::WITH`, but not providing a conditional in the example. Are you filtering a fetch-joined association?
Author
Owner

@doctrinebot commented on GitHub (Jul 23, 2014):

Comment created by gondo:

sorry i've tried to simplify my structure as much as it was possible, there are actually real conditions, one of them is date condition (among many others). i've updated my code

@doctrinebot commented on GitHub (Jul 23, 2014): Comment created by gondo: sorry i've tried to simplify my structure as much as it was possible, there are actually real conditions, one of them is date condition (among many others). i've updated my code
Author
Owner

@doctrinebot commented on GitHub (Jul 23, 2014):

Comment created by @ocramius:

There are still some inconsistencies in the issue - where is that query parameter used, for example?

@doctrinebot commented on GitHub (Jul 23, 2014): Comment created by @ocramius: There are still some inconsistencies in the issue - where is that query parameter used, for example?
Author
Owner

@doctrinebot commented on GitHub (Jul 23, 2014):

Comment created by gondo:

im using it in EntityRepository (sorry, didnt know thats important) i ll update my code
and the whole code is in Symfony2 project (web and command line applications)

@doctrinebot commented on GitHub (Jul 23, 2014): Comment created by gondo: im using it in EntityRepository (sorry, didnt know thats important) i ll update my code and the whole code is in Symfony2 project (web and command line applications)
Author
Owner

@doctrinebot commented on GitHub (Jul 23, 2014):

Comment created by @ocramius:

What I mean is that in ->setParameter('date', new \DateTime('last month')), parameter :date does not exist in the DQL.

@doctrinebot commented on GitHub (Jul 23, 2014): Comment created by @ocramius: What I mean is that in `->setParameter('date', new \DateTime('last month'))`, parameter `:date` does not exist in the DQL.
Author
Owner

@doctrinebot commented on GitHub (Jul 23, 2014):

Comment created by gondo:

i see, sorry :) its part of JOIN condition, i've updated the code

@doctrinebot commented on GitHub (Jul 23, 2014): Comment created by gondo: i see, sorry :) its part of JOIN condition, i've updated the code
Author
Owner

@TomasPilar commented on GitHub (Jan 6, 2016):

Hi, i just spent the few hours with debugging my app since I encountered the same bug that describes Gondo. With method getArrayResult() is the result OK but with method getResult() the result contains fewer items.

@TomasPilar commented on GitHub (Jan 6, 2016): Hi, i just spent the few hours with debugging my app since I encountered the same bug that describes Gondo. With method getArrayResult() is the result OK but with method getResult() the result contains fewer items.
Author
Owner

@Ocramius commented on GitHub (Jan 6, 2016):

Re-looked at this: seems like expected behavior from my point of view.

@Ocramius commented on GitHub (Jan 6, 2016): Re-looked at this: seems like expected behavior from my point of view.
Author
Owner

@maresja1 commented on GitHub (Jan 20, 2016):

Seems like an expected behavior to me too.

@maresja1 commented on GitHub (Jan 20, 2016): Seems like an expected behavior to me too.
Author
Owner

@Ocramius commented on GitHub (Jan 20, 2016):

Closing. Needs to be reproduced by a test, if re-opened.

@Ocramius commented on GitHub (Jan 20, 2016): Closing. Needs to be reproduced by a test, if re-opened.
Author
Owner

@gondo commented on GitHub (Oct 21, 2016):

@Ocramius , @maresja1 can you please explain how is this expected behaviour? getArrayResult() and getResult() are returning different number of results. aren't this functions suppose to return only different types of results?

@gondo commented on GitHub (Oct 21, 2016): @Ocramius , @maresja1 can you please explain how is this expected behaviour? `getArrayResult()` and `getResult()` are returning different **number** of results. aren't this functions suppose to return only different _types_ of results?
Author
Owner

@maresja1 commented on GitHub (Nov 28, 2016):

@gondo Because of how hydratation works - @Ocramius will correct me, if I'm wrong - but when you're hydratating objects (getResult()) you don't expect the same instance to be returned twice, thus when you join related table (let's call it B), having more rows linked to the same instance of parent object (call it A), Doctrine "groups" the repeating values of an instance of A, so you get each instance from A just once.

The related instances of class B are hydratated separately and that is why from one query you can get one instance of A having hydrateted collection of multiple instances of B.

This is nicely described (with the connected performance drawbacks) in this article written by @Ocramius:
https://ocramius.github.io/blog/doctrine-orm-optimization-hydration/

@maresja1 commented on GitHub (Nov 28, 2016): @gondo Because of how hydratation works - @Ocramius will correct me, if I'm wrong - but when you're hydratating objects (`getResult()`) you don't expect the same instance to be returned twice, thus when you join related table (let's call it B), having more rows linked to the same instance of parent object (call it A), Doctrine "groups" the repeating values of an instance of A, so you get each instance from A just once. The related instances of class B are hydratated separately and that is why from one query you can get one instance of A having hydrateted collection of multiple instances of B. This is nicely described (with the connected performance drawbacks) in this article written by @Ocramius: https://ocramius.github.io/blog/doctrine-orm-optimization-hydration/
Author
Owner

@gondo commented on GitHub (Nov 28, 2016):

@maresja1 thanks for taking time to write the response. i will certainly read the linked article.
but this behaviour is counter-intuitive and without studying the wana be expected behaviour, one gets confused easily.

@gondo commented on GitHub (Nov 28, 2016): @maresja1 thanks for taking time to write the response. i will certainly read the linked article. but this behaviour is counter-intuitive and without studying the `wana be expected` behaviour, one gets confused easily.
Author
Owner

@Ocramius commented on GitHub (Dec 4, 2016):

This is indeed expected behavior (unless someone writes a test that shows a problem here), as per fetch join semantics (@maresja1's comment).

An ORM loads an object graph into memory, and there is always only one instance of a combination of entity identifier and entity type ever inside a single EntityManager instance. The de-duplication is applied on purpose.

@Ocramius commented on GitHub (Dec 4, 2016): This is indeed expected behavior (unless someone writes a test that shows a problem here), as per fetch join semantics (@maresja1's comment). An ORM loads an object graph into memory, and there is always only one instance of a combination of entity identifier and entity type ever inside a single `EntityManager` instance. The de-duplication is applied on purpose.
Author
Owner

@ybenhssaien commented on GitHub (Feb 5, 2019):

Having the same problem here without Join, just using a simple select with queryBuilder

Code :

public function getOldVacationsByDates($dateStart, $dateEnd)
{
	$qb = $this->registry->getManager('old')->createQueryBuilder();

	$qb->select('v')->from(Vacation::class, 'v');
	$qb->where($qb->expr()->between('v.dateLimit', $qb->expr()->literal($dateStart), $qb->expr()->literal($dateEnd)));

	return $qb->getQuery()->getArrayResult();
}

getResult() return :

array(1) { 
	[0]=> object(App\Entity\Old\Vacation)#1280 (5) { ["id":"App\Entity\Old\Vacation":private]=> int(1) ["numGesha":"App\Entity\Old\Vacation":private]=> string(15) "CRQ000000067691" ["type":"App\Entity\Old\Vacation":private]=> string(3) "DNS" ["dateGesha":"App\Entity\Old\Vacation":private]=> string(10) "2019-02-06" ["dateLimit":"App\Entity\Old\Vacation":private]=> string(19) "2019-02-05 18:00:00" } 
}

While getArrayResult() return :

array(5) { 
	[0]=> array(5) {["id"]=> int(1) ["numGesha"]=> string(15) "CRQ000000067691" ["type"]=> string(3) "DNS" ["dateGesha"]=> string(10) "2019-02-06" ["dateLimit"]=> string(19) "2019-02-05 18:00:00" } 
	[1]=> array(5) { ["id"]=> int(1) ["numGesha"]=> string(15) "CRQ000000067693" ["type"]=> string(6) "Switch" ["dateGesha"]=> string(10) "2019-02-06" ["dateLimit"]=> string(19) "2019-02-04 12:00:00" } 
	[2]=> array(5) { ["id"]=> int(1) ["numGesha"]=> string(15) "CRQ000000067695" ["type"]=> string(3) "mdl" ["dateGesha"]=> string(10) "2019-02-07" ["dateLimit"]=> string(19) "2019-02-06 18:00:00" } 
	[3]=> array(5) { ["id"]=> int(1) ["numGesha"]=> string(15) "CRQ000000067696" ["type"]=> string(3) "DNS" ["dateGesha"]=> string(10) "2019-02-08" ["dateLimit"]=> string(19) "2019-02-07 18:00:00" } 
	[4]=> array(5) { ["id"]=> int(1) ["numGesha"]=> string(15) "CRQ000000067697" ["type"]=> string(6) "Switch" ["dateGesha"]=> string(10) "2019-02-08" ["dateLimit"]=> string(19) "2019-02-05 12:00:00" } 
}

Didn't deeply investigate but seems a strange behaviour !

@ybenhssaien commented on GitHub (Feb 5, 2019): Having the same problem here without Join, just using a simple select with queryBuilder Code : ``` public function getOldVacationsByDates($dateStart, $dateEnd) { $qb = $this->registry->getManager('old')->createQueryBuilder(); $qb->select('v')->from(Vacation::class, 'v'); $qb->where($qb->expr()->between('v.dateLimit', $qb->expr()->literal($dateStart), $qb->expr()->literal($dateEnd))); return $qb->getQuery()->getArrayResult(); } ``` getResult() return : ``` array(1) { [0]=> object(App\Entity\Old\Vacation)#1280 (5) { ["id":"App\Entity\Old\Vacation":private]=> int(1) ["numGesha":"App\Entity\Old\Vacation":private]=> string(15) "CRQ000000067691" ["type":"App\Entity\Old\Vacation":private]=> string(3) "DNS" ["dateGesha":"App\Entity\Old\Vacation":private]=> string(10) "2019-02-06" ["dateLimit":"App\Entity\Old\Vacation":private]=> string(19) "2019-02-05 18:00:00" } } ``` While getArrayResult() return : ``` array(5) { [0]=> array(5) {["id"]=> int(1) ["numGesha"]=> string(15) "CRQ000000067691" ["type"]=> string(3) "DNS" ["dateGesha"]=> string(10) "2019-02-06" ["dateLimit"]=> string(19) "2019-02-05 18:00:00" } [1]=> array(5) { ["id"]=> int(1) ["numGesha"]=> string(15) "CRQ000000067693" ["type"]=> string(6) "Switch" ["dateGesha"]=> string(10) "2019-02-06" ["dateLimit"]=> string(19) "2019-02-04 12:00:00" } [2]=> array(5) { ["id"]=> int(1) ["numGesha"]=> string(15) "CRQ000000067695" ["type"]=> string(3) "mdl" ["dateGesha"]=> string(10) "2019-02-07" ["dateLimit"]=> string(19) "2019-02-06 18:00:00" } [3]=> array(5) { ["id"]=> int(1) ["numGesha"]=> string(15) "CRQ000000067696" ["type"]=> string(3) "DNS" ["dateGesha"]=> string(10) "2019-02-08" ["dateLimit"]=> string(19) "2019-02-07 18:00:00" } [4]=> array(5) { ["id"]=> int(1) ["numGesha"]=> string(15) "CRQ000000067697" ["type"]=> string(6) "Switch" ["dateGesha"]=> string(10) "2019-02-08" ["dateLimit"]=> string(19) "2019-02-05 12:00:00" } } ``` Didn't deeply investigate but seems a strange behaviour !
Author
Owner

@Ocramius commented on GitHub (Feb 5, 2019):

@youssefbenhssaien that's normal hydration behaviour. See also https://ocramius.github.io/blog/doctrine-orm-optimization-hydration/ for an explanation of what's going on. If you feel that this isn't clear from the official documentation, please also propose a patch there.

@Ocramius commented on GitHub (Feb 5, 2019): @youssefbenhssaien that's normal hydration behaviour. See also https://ocramius.github.io/blog/doctrine-orm-optimization-hydration/ for an explanation of what's going on. If you feel that this isn't clear from the official documentation, please also propose a patch there.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: doctrine/archived-orm#3992