доктрина ORM count arraycollection in where condition

Используя Symfony2.8 с Doctrine 2.5, я хочу отфильтровать в запросе Doctrine ORM все наборы данных, в которых коллекция массивов содержит ровно 3 элемента.

$em = $this->getDoctrine()->getManager();
$query = $em->getRepository("AppBundle:EduStructItem")
->createQueryBuilder('e')
 ->addSelect('COUNT(e.preconditions) AS HIDDEN numberpre')
 ->having('numberpre = 3')
->getQuery();
$res = $query->getResult();
dump($res);
foreach ($res as $entity){
    print "title:".$entity->getTitle()."<br>";
    dump($entity->getPreconditions()->toArray());
}

preconditions - это коллекция массивов, которая содержит набор предварительных условий.

Наконец, я хочу, чтобы все результаты имели ровно 3 предварительных условия. Кроме того, было бы здорово также упорядочить по количеству значений в коллекции массивов (что-то вроде order by Count(e.preconditions)).

Из-за использования другого пакета я понизил версию doctrine с 2.5.2 до 2.5.0. Я не думаю, что это причина моих проблем, но для полноты картины я приведу доктринную часть Шоу моего композитора:

data-dog/pager-bundle                v0.2.4             Paginator bundle for symfony2 and doctrine orm, allows customization with filters and sorters
  doctrine/annotations                 v1.2.7             Docblock Annotations Parser
  doctrine/cache                       v1.5.2             Caching library offering an object-oriented API for many cache backends
  doctrine/collections                 v1.3.0             Collections Abstraction library
  doctrine/common                      v2.5.2             Common Library for Doctrine projects
  doctrine/data-fixtures               v1.1.1             Data Fixtures for all Doctrine Object Managers
  doctrine/dbal                        v2.5.2             Database Abstraction Layer
  doctrine/doctrine-bundle             1.6.1              Symfony DoctrineBundle
  doctrine/doctrine-cache-bundle       1.2.2              Symfony Bundle for Doctrine Cache
  doctrine/doctrine-fixtures-bundle    2.3.0              Symfony DoctrineFixturesBundle
  doctrine/doctrine-migrations-bundle  1.1.1              Symfony DoctrineMigrationsBundle
  doctrine/inflector                   v1.1.0             Common String Manipulations with regard to casing and singular/plural rules.
  doctrine/instantiator                1.0.5              A small, lightweight utility to instantiate objects in PHP without invoking their constructors
  doctrine/lexer                       v1.0.1             Base library for a lexer that can be used in Top-Down, Recursive Descent Parsers.
  doctrine/migrations                  v1.1.0             Database Schema migrations using Doctrine DBAL
  doctrine/orm                         v2.5.0             Object-Relational-Mapper for PHP             

Вот тестовая сущность:

<?php
// src/AppBundle/Entity/EduStructItem.php
namespace AppBundle\Entity;


use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;

/**
 * @ORM\Entity
 * @ORM\Table(name="test_edustructitemcollection")
 */
class EduStructItem
{
    /**
     * @ORM\Column(type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    protected $id;


    /**
     * @var string
     * @Assert\NotBlank()
     * @ORM\Column(type="string", length=255, nullable=false)
     */
    private $title;


    /**
     * Preconditions are EduStructItems referencing to an EduStructItem.
     * For a single EduStructItem its empty (which have no subelements).
     * A join table holds the references of a main EduStructItem to its sub-EduStructItems (preconditions)
     *
     * @ORM\ManyToMany(targetEntity="EduStructItem",indexBy="id", cascade={"persist"})
     * @ORM\JoinTable(name="test_edustructitem_preconditioncollection",
     *     joinColumns={@ORM\JoinColumn(name="edustructitem_id", referencedColumnName="id")},
     *     inverseJoinColumns={@ORM\JoinColumn(name="edustructitem_precondition_id", referencedColumnName="id")}
     * )
     */
    public $preconditions;

    public function __construct()
    {
        $this->preconditions = new ArrayCollection();
    }

    public function getTitle()
    {
        return $this->title;
    }

    public function setTitle($title)
    {
        $this->title = $title;
    }


    public function getPreconditions()
    {
        return $this->preconditions;
    }

    public function addPrecondition(\AppBundle\Entity\EduStructItem $precondition)
    {
        $this->preconditions->add($precondition);
    }

    public function removePrecondition(\AppBundle\Entity\EduStructItem $precondition)
    {
        $this->preconditions->removeElement($precondition);
    }

}
?>

Наконец, я всегда получаю сообщение об ошибке: [Семантическая ошибка] строка 0, столбец 18 рядом с «предварительными условиями)»: Ошибка: недопустимое выражение PathExpression. Ожидается StateFieldPathExpression или SingleValuedAssociationField.

Теперь я попробовал ваше новое решение:

$em = $this->getDoctrine()->getManager();
$query = $em->getRepository("AppBundle:EduStructItem")
    ->createQueryBuilder('e')
    ->addSelect('COUNT(e.preconditions) AS HIDDEN countpre')
    ->join('e.preconditions', 'precondition', Join::WITH)
    ->having('countpre = 1')
    ->getQuery();

и снова получите сообщение об ошибке: [Семантическая ошибка] строка 0, столбец 18 рядом с «предварительными условиями)»: Ошибка: недопустимое выражение PathExpression. Ожидается StateFieldPathExpression или SingleValuedAssociationField. Когда я пишу псевдоним перед СКРЫТЫМ, я также получаю: [Семантическая ошибка] строка 0, столбец 53 рядом с 'FROM AppBundle \ Entity \ EduStructItem': Ошибка: класс 'FROM' не определен. Учтите, что это самоотражающее отношение, и существует только одна сущность, но есть две таблицы. Как вы можете видеть в аннотации моей сущности, отношения с самими собой сохраняются в таблице test_edustructitem_preconditioncollection, которая была сгенерирована доктриной из-за аннотаций.

Я попробовал ваше последнее решение:

$qb = $em->getRepository("AppBundle:EduStructItem")
    ->createQueryBuilder('item');
$qb->addSelect('COUNT(precondition.id) AS countpre HIDDEN ')
    ->join('item.preconditions', 'precondition', Join::WITH)
    ->having('countpre = 1');

Когда у меня есть countpre до HIDDEN, я всегда получаю эту ошибку: [Семантическая ошибка] строка 0, столбец 56 рядом с 'FROM AppBundle \ Entity \ EduStructItem': Ошибка: класс 'FROM' не определен.

Но когда я ставлю countpre после HIDDEN:

$qb = $em->getRepository("AppBundle:EduStructItem")
            ->createQueryBuilder('item');
        $qb->addSelect('COUNT(precondition.id) AS HIDDEN countpre')
            ->join('item.preconditions', 'precondition', Join::WITH)
            ->having('countpre = 1');

Я получаю сообщение об ошибке: Возникло исключение при выполнении 'SELECT t0_.id AS id_0, t0_.title AS title_1, COUNT (t1_.id) AS sclr_2 FROM test_edustructitemcollection t0_ ВНУТРЕННЕЕ СОЕДИНЕНИЕ test_edustructitem_preconditioncollection t2_ ON t0_.ID = t2_ON t0_.id t1_ ON t1_.id = t2_.edustructitem_precondition_id ИМЕЕТ sclr_2 = 1 ':

SQLSTATE [42S22, 207]: [Microsoft] [Драйвер ODBC 11 для SQL Server] [SQL Server] Ungültiger Spaltenname 'sclr_2'.

500 Внутренняя ошибка сервера - DBALException

1 связанное исключение: SQLSrvException »

Учтите, что есть только одна сущность со ссылкой на себя, и есть эти две таблицы:

USE [easylearndev4_rsc]
GO
/****** Object:  Table [dbo].[test_edustructitemcollection]    Script Date: 14.12.2015 09:31:55 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[test_edustructitemcollection](
    [id] [int] IDENTITY(1,1) NOT NULL,
    [title] [nvarchar](255) NOT NULL,
PRIMARY KEY CLUSTERED 
(
    [id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO

а также

USE [easylearndev4_rsc]
GO
/****** Object:  Table [dbo].[test_edustructitem_preconditioncollection]    Script Date: 14.12.2015 09:32:21 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
REATE TABLE [dbo].[test_edustructitem_preconditioncollection](
    [edustructitem_id] [int] NOT NULL,
    [edustructitem_precondition_id] [int] NOT NULL,
PRIMARY KEY CLUSTERED 
(
    [edustructitem_id] ASC,
    [edustructitem_precondition_id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
ALTER TABLE [dbo].[test_edustructitem_preconditioncollection]  WITH CHECK ADD  CONSTRAINT [FK_34E716A81B7A6CEB] FOREIGN KEY([edustructitem_precondition_id])
REFERENCES [dbo].[test_edustructitemcollection] ([id])
GO
ALTER TABLE [dbo].[test_edustructitem_preconditioncollection] CHECK CONSTRAINT [FK_34E716A81B7A6CEB]
GO
ALTER TABLE [dbo].[test_edustructitem_preconditioncollection]  WITH CHECK ADD  CONSTRAINT [FK_34E716A85D864668] FOREIGN KEY([edustructitem_id])
REFERENCES [dbo].[test_edustructitemcollection] ([id])
GO
ALTER TABLE [dbo].[test_edustructitem_preconditioncollection] CHECK CONSTRAINT [FK_34E716A85D864668]
GO

Наконец, я сам нашел обходной путь:

$em = $this->getDoctrine()->getManager();
$qb = $em->getRepository("AppBundle:EduStructItem")
    ->createQueryBuilder('e');
$qb->join('e.preconditions', 'p', Join::WITH)
    ->groupBy('e.id, e.title')
    ->having('count(p.id) = 1');

Но я не очень доволен этим, потому что коллекция массивов уже представляет собой агрегированные данные, зачем мне снова присоединяться, подсчитывать и группировать по! Это не может быть идеей Доктрины! Кто-нибудь знает лучшее решение?


person Immanuel    schedule 09.12.2015    source источник


Ответы (2)


Попробуй это:

$qb = $this->getDoctrine()->getManager()->getRepository("MyBundle:Groups")
    ->createQueryBuilder('g')
    ->having('count(g.members) = 3')
    ->orderBy('g.members', 'DESC')
; 
person scoolnico    schedule 09.12.2015
comment
Это не работает: [Семантическая ошибка] строка 0, столбец 109 рядом с mebers) ': Ошибка: недопустимое выражение PathExpression. Ожидается StateFieldPathExpression или SingleValuedAssociationField. Коллекция массивов не принимается в качестве аргумента для count ()! - person Immanuel; 09.12.2015
comment
Можем ли мы увидеть ваши объекты и, в частности, карту? - person scoolnico; 09.12.2015
comment
Я отредактировал свой пост и добавил пример кода. Проблема в том, что я не могу применить count () к коллекции массивов - мне интересно, почему вы можете! - person Immanuel; 10.12.2015

$qb = $this->getDoctrine()->getManager()->getRepository"MyBundle:Groups")
    ->createQueryBuilder('g')
    ->addSelect('COUNT(g.members) AS count HIDDEN')
    ->having('count = 3')
    ->orderBy('count', 'DESC')
    ;

РЕДАКТИРОВАТЬ

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

$qb = $em->getRepository("AppBundle:EduStructItem") //Selfreferencing ManyToMany
         ->createQueryBuilder('item');
$qb->addSelect("COUNT(precondition.id) AS count HIDDEN")
   ->join('item.preconditions', 'precondition', Join::WITH)
   ->having('count = 3')
   ->orderBy('count');
person stevenll    schedule 09.12.2015
comment
Это не работает: $ qb- ›addSelect ('COUNT (' g.members ') AS count HIDDEN') имеет синтаксическую ошибку из-за вложенных кавычек. Я меняю его на $ qb- ›addSelect (COUNT ('g.members') AS count HIDDEN), но, тем не менее, это приводит к следующему сообщению об ошибке: [Синтаксическая ошибка] строка 0, столбец 101: Ошибка: ожидаемый литерал, получено 'count' - person Immanuel; 09.12.2015
comment
с обновленным кодом я получаю сообщение об ошибке: [Семантическая ошибка] строка 0, столбец 101 рядом с «count = 3»: Ошибка: «count» не определено. Кажется, что скрытое значение недоступно в условии where. - person Immanuel; 09.12.2015
comment
есть на where состоянии, это having. У меня этот код уже работает во многих проектах. Может быть, в вашем контексте что-то другое. - person stevenll; 09.12.2015
comment
Обратите внимание на обновленный текст вопроса. Пример доступен сейчас. - person Immanuel; 10.12.2015
comment
@Immanuel, пожалуйста, посмотрите обновленный ответ. Скажите, решит ли это вашу проблему - person stevenll; 11.12.2015
comment
Ваш обновленный ответ не работает. Посмотрите на мой обновленный вопрос, чтобы увидеть результаты. - person Immanuel; 11.12.2015
comment
Ваше новое решение не работает. Проверьте обновленный вопрос, пожалуйста. - person Immanuel; 14.12.2015
comment
Наконец, я сам нашел обходной путь, но мне он не очень понравился. Проверьте обновленный вопрос, пожалуйста. - person Immanuel; 14.12.2015