От многих до многих доктрина querybuilder отношения и расширенный запрос symfony2

Я использую следующий запрос с помощью querybuilder, но как-то многие многие отношения работают не так, как ожидалось.

$shopData = $sm->createQueryBuilder()                    
                ->select('v')
                ->from('AdminBundle:Voucher','v')
                ->innerJoin('v.shop', 's')
                ->leftJoin('AdminBundle:VoucherProgram', 'vp', \Doctrine\ORM\Query\Expr\Join::ON, 'vp.id = v.program_id')
                ->leftJoin('AdminBundle:shopHistory', 'sh', \Doctrine\ORM\Query\Expr\Join::ON, 'sh.shop = s.id')
                ->where('s.shopStatus = :shopStatus')
                ->setParameter('shopStatus', Shop::SHOP_ACTIVATED)
                ->andWhere('s.highlightedHome = :highlightedHome')
                ->setParameter('highlightedHome', Shop::SHOP_HIGHLIGHTED_HOME)
                ->andWhere('s.offers = \'voucher\'')
                ->setFirstResult(0)
                ->setMaxResults(6)
                ->addOrderBy('v.discount_amount', 'DESC')
                ->groupBy('sh.shop')
                ->getQuery()
                ->getSql();

Сгенерированный запрос выглядит следующим образом:

SELECT v FROM AdminBundle:Voucher v INNER JOIN v.shop s LEFT JOIN AdminBundle:VoucherPrograms vp ON vp.id = v.program_id LEFT JOIN AdminBundle:shopHistory sh ON sh.shop = s.id WHERE s.shopStatus = :shopStatus AND s.highlightedHome = :highlightedHome AND s.offers = 'voucher' GROUP BY sh.shop ORDER BY v.discount_amount DESC

Если я удаляю все и сохраняю только внутреннее соединение для отношения Many2Many, он работает как ожидалось.

$sm->createQueryBuilder()                    
                ->select('v')
                ->from('AdminBundle:Voucher','v')
                ->innerJoin('v.shop', 's');

Вот сгенерированный запрос:

SELECT l0.* FROM voucher l0_ INNER JOIN shop_voucher l2_ ON l0_.id = l2_.voucher_id INNER JOIN shop l1_ ON l1_.id = l2_.shop_id;

Так что мне интересно, почему система не поднимает правильное отношение, когда я добавляю больше объединений.

Вот мои основные сущности, имеющие отношение Many2Many:

Shop.php

namespace AdminBundle\Entity;

use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;

/**
 * Shop.
 *
 * @ORM\Table(name="shop")
 * @ORM\Entity(repositoryClass="AdminBundle\Entity\ShopRepository")
 */
class Shop
{
    const SHOP_DEACTIVATED = 0;
    const SHOP_ACTIVATED = 1;
    const SHOP_HIGHLIGHTED_HOME = 1;
    ................................
    /**
     * @ORM\ManyToMany(targetEntity="Voucher", inversedBy="shop")
     * @ORM\JoinTable(name="shop_voucher")
     */
    private $voucher;
    ................................

Voucher.php

namespace AdminBundle\Entity;

use Doctrine\ORM\Mapping as ORM;
#use Doctrine\Common\Collections\ArrayCollection;

/**
 * Voucher.
 *
 * @ORM\Table(name="voucher")
 * @ORM\Entity(repositoryClass="AdminBundle\Entity\VoucherRepository")
 */
class Voucher
{
    ................................
    /**
     * @ORM\ManyToMany(targetEntity="Shop", mappedBy="voucher", cascade={"persist"})    
     */
    private $shop;
    ................................

Я уже проверял, что в стеке есть те же вопросы, но я хочу знать, как мы можем расширять запрос. У меня есть решение для моего вопроса, как показано ниже, но не совсем то, что происходит в описанном выше сценарии.

$shopDataQuery = $connection->prepare('SELECT v.* FROM voucher AS v INNER JOIN shop_voucher AS sv ON sv.voucher_id = v.id INNER JOIN shop AS s ON s.id = sv.shop_id LEFT JOIN voucher_programs AS vp ON vp.id = v.program_id LEFT JOIN shop_history AS sh ON sh.shop = s.id WHERE s.shopStatus = :shopStatus AND s.highlightedHome = :highlightedHome AND s.offers = 'voucher' GROUP BY sh.shop ORDER BY v.discount_amount DESC LIMIT 6');

Update:

Вот shopHistory.php

namespace AdminBundle\Entity;

use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;

/**
 * shopHistory.
 *
 * @ORM\Table(name="shop_history")
 * @ORM\Entity(repositoryClass="AdminBundle\Entity\shopHistoryRepository")
 */
class shopHistory
{
    /**
     * @var int
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

    /**
     * @var int
     * @ORM\ManyToOne(targetEntity="Shop", inversedBy="shopHistory")
     * @ORM\JoinColumn(name="shop", referencedColumnName="id")
     */
    private $shop;

Вот VoucherProgram.php

namespace AdminBundle\Entity;

use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;

/**
 * VoucherProgram.
 *
 * @ORM\Table(name="voucher_program")
 * @ORM\Entity(repositoryClass="AdminBundle\Entity\VoucherProgramRepository")
 */
class VoucherProgram
{
    /**
     * @var int
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

    /**
     * @ORM\OneToMany(targetEntity="Voucher", mappedBy="program")
     */
    private $voucher;

    /**
     * @ORM\OneToMany(targetEntity="Shop", mappedBy="vprogram")
     */
    private $shop;

Ответ 1

Я думаю, это потому, что вы назвали ваше отношение таким же, как ваше поле:

SELECT v 
FROM AdminBundle:Voucher v 
INNER JOIN v.shop s 
LEFT JOIN AdminBundle:VoucherPrograms vp ON vp.id = v.program_id 
LEFT JOIN AdminBundle:shopHistory sh ON sh.shop = s.id 
WHERE s.shopStatus = :shopStatus 
AND s.highlightedHome = :highlightedHome 
AND s.offers = 'voucher' 
GROUP BY sh.shop 
ORDER BY v.discount_amount DESC

Попробуйте переименовать в свой магазинHistory Entity в поле магазин как shop_id.

Но у меня нет полной модели сущности для проведения тестов!

Если это не так, попробуйте вставить код ваших 5 сущностей, пожалуйста...

Изменить:

Я делаю тесты на своей локальной машине с выпуском symfony 2.8.

Я создал сущности с данной моделью.

Я только что добавил это в Voucher Entity:

/**
 * @ORM\ManyToOne(targetEntity="voucherProgram", inversedBy="voucher")
 */
private $program;

У меня есть следующие запросы:

Мой первый запрос

SELECT v 
FROM AppBundle:Voucher v 
INNER JOIN v.shop s 
LEFT JOIN AppBundle:voucherProgram vp WITH vp.id = v.program 
LEFT JOIN AppBundle:shopHistory sh 
WITH sh.shop = s.id 
GROUP BY sh.shop

Мой второй SQL-запрос

SELECT v0_.id AS id0, v0_.program_id AS program_id1 
FROM voucher v0_ 
INNER JOIN shop_voucher s2_ ON v0_.id = s2_.voucher_id 
INNER JOIN shop s1_ ON s1_.id = s2_.shop_id 
LEFT JOIN voucher_program v3_ ON (v3_.id = v0_.program_id) 
LEFT JOIN shop_history s4_ ON (s4_.shop = s1_.id) 
GROUP BY s4_.shop 
LIMIT 6 
OFFSET 0

Я думаю, что то, что сгенерировано, правильно!

Изменить 2:

Я использую стандартное издание symfony с:

$shopData = $this->getDoctrine()
  ->getManager()
  ->createQueryBuilder()                    
  ->select('v')
  ->from('AppBundle:Voucher','v')
  ->innerJoin('v.shop', 's')
  ->leftJoin('AppBundle:voucherProgram', 'vp', 'WITH', 'vp.id = v.program')
  ->leftJoin('AppBundle:shopHistory', 'sh', 'WITH', 'sh.shop = s.id')
  //->where('s.shopStatus = :shopStatus')
  //->setParameter('shopStatus', Shop::SHOP_ACTIVATED)
  //->andWhere('s.highlightedHome = :highlightedHome')
  //->setParameter('highlightedHome', Shop::SHOP_HIGHLIGHTED_HOME)
  //->andWhere('s.offers = \'voucher\'')
  ->setFirstResult(0)
  ->setMaxResults(6)
  //->addOrderBy('v.discount_amount', 'DESC')
  ->groupBy('sh.shop')
  ->getQuery()
  ->getDql();

Мой композитор .json:

"php": ">=5.3.9",
"symfony/symfony": "2.8.*",
"doctrine/orm": "^2.4.8",
"doctrine/doctrine-bundle": "~1.4",
"symfony/swiftmailer-bundle": "~2.3",
"symfony/monolog-bundle": "~2.4",
"sensio/distribution-bundle": "~5.0",
"sensio/framework-extra-bundle": "^3.0.2",
"incenteev/composer-parameter-handler": "~2.0"

И мои сущности:

Магазин

namespace AppBundle\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
 * Shop
 *
 * @ORM\Table(name="shop")
 * @ORM\Entity(repositoryClass="AppBundle\Repository\ShopRepository")
 */
class Shop
{
    const SHOP_DEACTIVATED = 0;
    const SHOP_ACTIVATED = 1;
    const SHOP_HIGHLIGHTED_HOME = 1;

    /**
     * @var int
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;


    /**
     * Get id
     *
     * @return integer 
     */
    public function getId()
    {
        return $this->id;
    }

    /**
     * @ORM\ManyToMany(targetEntity="Voucher", inversedBy="shop")
     * @ORM\JoinTable(name="shop_voucher")
     */
    private $voucher;
}

shopHistory

namespace AppBundle\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
 * shopHistory
 *
 * @ORM\Table(name="shop_history")
 * @ORM\Entity(repositoryClass="AppBundle\Repository\shopHistoryRepository")
 */
class shopHistory
{
    /**
     * @var int
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;


    /**
     * Get id
     *
     * @return integer 
     */
    public function getId()
    {
        return $this->id;
    }

    /**
     * @var int
     * @ORM\ManyToOne(targetEntity="Shop", inversedBy="shopHistory")
     * @ORM\JoinColumn(name="shop", referencedColumnName="id")
     */
    private $shop;
}

Ваучер

namespace AppBundle\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
 * Voucher
 *
 * @ORM\Table(name="voucher")
 * @ORM\Entity(repositoryClass="AppBundle\Repository\VoucherRepository")
 */
class Voucher
{
    /**
     * @var int
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;


    /**
     * Get id
     *
     * @return integer 
     */
    public function getId()
    {
        return $this->id;
    }

    /**
     * @ORM\ManyToMany(targetEntity="Shop", mappedBy="voucher", cascade={"persist"})    
     */
    private $shop;

    /**
     * @ORM\ManyToOne(targetEntity="voucherProgram", inversedBy="voucher")
     */
    private $program;
}

voucherProgram

namespace AppBundle\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
 * voucherProgram
 *
 * @ORM\Table(name="voucher_program")
 * @ORM\Entity(repositoryClass="AppBundle\Repository\voucherProgramRepository")
 */
class voucherProgram
{
    /**
     * @var int
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

    /**
     * @ORM\OneToMany(targetEntity="Voucher", mappedBy="program")
     */
    private $voucher;

    /**
     * @ORM\OneToMany(targetEntity="Shop", mappedBy="vprogram")
     */
    private $shop;

    /**
     * Get id
     *
     * @return integer 
     */
    public function getId()
    {
        return $this->id;
    }
}

Ответ 2

Насколько я вижу, первый заданный запрос - это DQL query, а второй - SQL. DQL-запросы могут быть намного короче и по-прежнему работают так, как ожидалось, поскольку они не управляют таблицами, а разрешают. Таким образом, вам нужно меньше соединений для соединения m2m (которые расширяются в двойное соединение при создании final SQL)

Я не могу найти ничего плохого в сгенерированном DQL, если он сгенерирован с заданным построителем запросов.