Symfony2: как использовать group_concat в QueryBuilder
У меня есть вложенный набор (с использованием дерева Gedmo), который называется "Местоположение". У объекта "Appartment" есть location_id и что мне нужно сделать для сопоставления скалярного значения, называемого, например, "path", чтобы запрос возвращал все квартиры.
В Doctrine1 у меня был этот код:
/**
* Add "path" to each element
*
* @param Doctrine_Query $query
* @param string $separator
*/
protected function addScalar_path(Doctrine_Query $query, $separator=", ")
{
$subquery = "k99.root_id=o.root_id AND k99.lft<=o.lft AND k99.rgt>=o.rgt AND k99.level<=o.level" ;
$query->addSelect("(SELECT GROUP_CONCAT(k99.name ORDER BY k99.level SEPARATOR '$separator') FROM Location k99 WHERE $subquery) AS path") ;
}
Примечание. Псевдоним "o" используется для первичного запроса.
Этот код позволит мне использовать
{foreach .... as $appartment}
{$appartment->path}
...
Что бы напечатать:
Australia, Victoria, Melbourne, ...other children...
Как сделать то же самое в D2? И как даже включить расширения доктрины в мой проект symfony2?
Ответы
Ответ 1
Если вы хотите использовать его в QueryBuilder, вы должны:
1) добавить функции DQL GroupConcat: GroupConcat
2) Регистрация GroupConcat: Пользовательские функции DQL
Другая альтернатива - использовать NativeQuery: Native SQL
В symfony2, регистрирующем функцию DQL, очень просто добавить GROUP_CONCAT в config.yml, например:
entity_managers:
default:
dql:
string_functions:
GROUP_CONCAT: YourBundle\Query\Mysql\GroupConcat
Для получения дополнительной информации посетите раздел "Регистрация пользовательских функций DQL"
Ответ 2
Если кто-то наткнулся на этот пост, теперь есть репозиторий Doctrine Extensions в Github. В репо есть инструкции по его использованию. Вы можете использовать композитор для его установки, а затем использовать любую интересующую вас функцию.
EDIT:
Для пользователя Tushar, как использовать GROUP_CONCAT в Doctrine2 DQL, просто установите Расширения Doctrine:
composer require beberlei/DoctrineExtensions
Чтобы включить его: добавьте в свой config.yml следующее:
doctrine:
orm:
dql:
string_functions:
group_concat: DoctrineExtensions\Query\Mysql\GroupConcat
Затем в вашем коде вы сможете теперь использовать Group Concat в своих DQL:
$this->createQueryBuilder('location')
->select('location')
->addSelect("GROUP_CONCAT(DISTINCT location.name SEPARATOR '; ') AS locationNames");
$result = $queryBuilder->getQuery()->getResult();
Или в случае для оригинального вопроса:
$query->addSelect("(SELECT GROUP_CONCAT(k99.name ORDER BY k99.level SEPARATOR '$separator') FROM Location k99 WHERE $subquery) AS path");
Ответ 3
Просто добавление к ответу @a.aitboudad:
Функция DQL, связанная с GroupConcat, имеет "ограниченную поддержку GROUP_CONCAT".
Вот полная версия поддержки:
Конфигурация, как он упоминал.
// -------------------------------------------------
// Complete support of GROUP_CONCAT in Doctrine2
// -------------------------------------------------
// Original Article: http://habrahabr.ru/post/181666/
// Automated translation to English: http://sysmagazine.com/posts/181666/
// Original github commit: https://github.com/denisvmedia/DoctrineExtensions/blob/d1caf21cd7c71cc557e60c26e9bf25323a194dd1/lib/DoctrineExtensions/Query/Mysql/GroupConcat.php
/**
* DoctrineExtensions Mysql Function Pack
*
* LICENSE
*
* This source file is subject to the new BSD license that is bundled
* with this package in the file LICENSE.txt.
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to [email protected] so I can send you a copy immediately.
*/
namespace DoctrineExtensions\Query\Mysql;
use Doctrine\ORM\Query\AST\Functions\FunctionNode,
Doctrine\ORM\Query\Lexer;
/**
* Full support for:
*
* GROUP_CONCAT([DISTINCT] expr [,expr ...]
* [ORDER BY {unsigned_integer | col_name | expr}
* [ASC | DESC] [,col_name ...]]
* [SEPARATOR str_val])
*
*/
class GroupConcat extends FunctionNode
{
public $isDistinct = false;
public $pathExp = null;
public $separator = null;
public $orderBy = null;
public function parse(\Doctrine\ORM\Query\Parser $parser)
{
$parser->match(Lexer::T_IDENTIFIER);
$parser->match(Lexer::T_OPEN_PARENTHESIS);
$lexer = $parser->getLexer();
if ($lexer->isNextToken(Lexer::T_DISTINCT)) {
$parser->match(Lexer::T_DISTINCT);
$this->isDistinct = true;
}
// first Path Expression is mandatory
$this->pathExp = array();
$this->pathExp[] = $parser->SingleValuedPathExpression();
while ($lexer->isNextToken(Lexer::T_COMMA)) {
$parser->match(Lexer::T_COMMA);
$this->pathExp[] = $parser->StringPrimary();
}
if ($lexer->isNextToken(Lexer::T_ORDER)) {
$this->orderBy = $parser->OrderByClause();
}
if ($lexer->isNextToken(Lexer::T_IDENTIFIER)) {
if (strtolower($lexer->lookahead['value']) !== 'separator') {
$parser->syntaxError('separator');
}
$parser->match(Lexer::T_IDENTIFIER);
$this->separator = $parser->StringPrimary();
}
$parser->match(Lexer::T_CLOSE_PARENTHESIS);
}
public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker)
{
$result = 'GROUP_CONCAT(' . ($this->isDistinct ? 'DISTINCT ' : '');
$fields = array();
foreach ($this->pathExp as $pathExp) {
$fields[] = $pathExp->dispatch($sqlWalker);
}
$result .= sprintf('%s', implode(', ', $fields));
if ($this->orderBy) {
$result .= ' ' . $sqlWalker->walkOrderByClause($this->orderBy);
}
if ($this->separator) {
$result .= ' SEPARATOR ' . $sqlWalker->walkStringPrimary($this->separator);
}
$result .= ')';
return $result;
}
}
// -------------------------------------------------
// Example of usage:
// -------------------------------------------------
$query = $this->createQueryBuilder('c')
->select("
c as company,
GroupConcat(b.id, ';', b.headOffice, ';', b.city, ';', s.name
ORDER by b.id
SEPARATOR '|') AS branches
")->leftJoin('c.branches', 'b')
->leftJoin('b.country', 's')
->groupBy('c.id')
->setFirstResult(0)
->setMaxResults(10)
->getQuery();
$result = $query->getResult();
Ответ 4
У меня была схожая проблема. GROUP_CONCAT не позволяет использовать CASE-WHEN внутри. Эта библиотека исправила мои проблемы, так что, возможно, это будет полезно.