query builder, one to many where many is empty - php

I have an entity Category, and this category has a recursive relationship with itself where each category can be a parent to several other categories. The relationship looks like this:
/**
* #var parent
* #ORM\ManyToOne(targetEntity="Category")
* #ORM\JoinColumn(referencedColumnName="id", onDelete="CASCADE")
*/
private $parent;
/**
* #ORM\OneToMany(targetEntity="Category", mappedBy="parent")
*/
private $children;
I need to make a query builder query that selects all categories that are either children(have a parent) Or the categories that have no parent and also have no children (in other words all categories except the parents that have children) I can't seem to be able to do it. Please help.

You need next DQL query:
$query = 'SELECT c FROM AcmeBundle:Category c LEFT JOIN c.parent p LEFT JOIN c.children ch WHERE p IS NOT NULL OR (ch IS NULL AND p IS NULL)';
If you need QueryBuilder sequence for this query you can use next code:
$qb = $em->createQueryBuilder();
$query = $qb
->select('c')
->from('AcmeBundle:Category', 'c')
->leftJoin('c.parent', 'p')
->leftJoin('c.children', 'ch')
->where($qb->expr()->orX(
$qb->expr()->isNotNull('p'),
$qb->expr()->andX(
$qb->expr()->isNull('ch'),
$qb->expr()->isNull('p'),
)
))
->getQuery();

Related

Symfony3 ManyToMany join

I need to get liste of articles who have the id of categorie with join ManyToMany I tried all day but it won't work. with dql query or anything pelase help I am despread.
I want to get liste of articles who have category id with many to many relation
/**
*
* #ORM\ManyToMany(targetEntity="Categorie",mappedBy="cat")
* #ORM\JoinTable(name="article_categorie",
* joinColumns={#ORM\JoinColumn(name="article_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="categorie_id", referencedColumnName="id",onDelete="CASCADE")}
* )
*
*/
private $categorie;
my first try
$qp=$this->createQueryBuilder('p');
$qp->select("p")
->from(Categorie::class,"c")
->where($qp->expr()->eq("c.id","$id"))->setMaxResults(3)
->getQuery()->execute();
return $qp;
my second try
$em = $this->getEntityManager();
$query = $em->createQuery("SELECT article
FROM article t
INNER JOIN article_categorie jt ON(t.id = jt.article_id)
INNER JOIN categorie g ON(g.id = jt.categorie_id)
WHERE_id g.id=9");return $query->getResult();
my third try
$this->createQueryBuilder()
->select('s')
->from('ArticleBundle:Categorie', 's')
->innerJoin('s.Category c ON c.category_id = s.')
->where('s.name = :superCategoryName')
->setParameter('superCategoryName', $superCategoryName)
->getQuery()
->getResult();
Dosn't work
You can try this:
/**
* #ORM\Entity
*/
class Article
{
/**
* #ORM\ManyToMany(targetEntity="AppBundle\Entity\Category", cascade={"persist"})
*/
private $categories;
// …
}
and in your repository:
public function getArticlesWithCategories(array $categoryNames)
{
$qb = $this->createQueryBuilder('a');
// We're making a joint with the Category entity with alias "c."
$qb
->innerJoin('a.categories', 'c')
->addSelect('c')
;
// Then we filter on the names of the categories using an IN
//If you really need to take the id, replace the $categorieName variable with $categorieID and "c. name" with "c. id".
$qb->where($qb->expr()->in('c.name', $categoryNames));
// The syntax of the IN and other expressions can be found in the Doctrine documentation
// Finally, we return the result
return $qb
->getQuery()
->getResult()
;
}

DQL Query Doctrine places joined element OrderBy clause in another element's juncture condition's subQuery

Here is my situation:
I gotta an entity User, with several joined entitites collections:
class User
{
private $id;
/**
* #var UserData
* #ORM\OneToMany(targetEntity="UserData", mappedBy="user")
*/
private $userDatas;
/**
* #var ServiceAccess
* #ORM\OneToMany(targetEntity="ServiceAccess", mappedBy="user")
*/
private $serviceAccesses;
...
}
ServiceAccess has also a OneToMany junction on Offers, with an OrderBy instruction to Doctrine ORM on its level field:
class ServiceAccess
{
private $id;
/**
* Offers
* #ORM\OneToMany(targetEntity="Offer",
* mappedBy="access",
* cascade={"persist","remove","refresh"})
* #ORM\OrderBy({"level" = "DESC"})
*/
private $offers;
/**
* #ORM\ManyToOne(
* targetEntity="User"
* )
* #ORM\JoinColumn(name="user_id", referencedColumnName="id", nullable=false)
*/
private $user;
...
}
My Offer class:
class Offer
{
private $id;
/**
* The user access
*
* #var ServiceAccess
* #ORM\ManyToOne(targetEntity="Fitnext\ServiceAccessBundle\Domain\Entity\UserServiceAccess", inversedBy="offers")
* #ORM\JoinColumn(name="access_id", referencedColumnName="id", nullable=false)
*/
private $access;
/**
* #ORM\Column(name="level", type="integer", nullable=false, options={"default"=0})
*/
private $level;
...
}
I am implementing the DQL query to fetch one User with several dependencies, including its UserDatas, its ServiceAccesses, and the ServiceAccesses.offers.
The main trick here is the condition for the User.userDatas juncture:
UserData has a name and a createdAt field, and can contain several rows having the same name.
My goal is to query the the latest row for each defined name group.
For that, I use a subquery (which was sent to me here on Stackoverflow :) ), which works perfectly.
Also, I have several more classical junctures, in which one on User.serviceAccesses and then one on ServiceAccesses.offers, which causes me the issue, when combined with the UserData subquery...
Here is my QueryBuilder:
// First I define the subquery for the UserDatas juncture condition
$queryDatas = $this->_em->createQueryBuilder()
->select('a')
->from('UserData', 'a')
->leftJoin( 'UserData', 'b', 'WITH', 'a.name = b.name AND a.createdAt < b.createdAt' )
->where( 'b.createdAt IS NULL' )
->andWhere('a.name IN (:names)')
->andWhere('a.user = u')
->orderBy( 'a.createdAt', 'DESC' )
->getDQL();
// Notice the orderBy clause here, which is part of the trick in order to get the latest registration of each named data group, and which is also part of the issue...
// And here is the main QueryBuilder
$this->createQueryBuilder('u')
->where('u.id = :id')
->setParameter('id', $id)
// UserDatas
->leftJoin('u.userDatas', 'd', 'WITH', 'd IN ('. $queryDatas .')')
->addSelect('d')
->setParameter('names', ['height', 'weight'])
// ServiceAccess
->leftJoin('u.serviceAccesses', 'svacc', 'WITH', 'svacc.activated = 1 AND svacc.type = :type' )
->setParameter('type','default')
->addSelect('svacc')
// ServiceAccessOffers
->leftJoin('svacc.offers', 'offers', 'WITH', 'offers.activated = 1' )
->addSelect('offers')
...
This returns me a Doctrine\DBAL\Exception\InvalidFieldNameException:
SQLSTATE[42S22]: Column not found: 1054 Unknown column 'u7_.level' in 'order clause'
And Here is the generated SQL code part causing the issue:
FROM users u0_
LEFT JOIN user_data e1_ ON u0_.id = e1_.user_id
AND (e1_.id IN (
SELECT e8_.id
FROM user_data e8_
LEFT JOIN user_data e9_ ON (e8_.name = e9_.name AND e8_.created_at < e9_.created_at)
WHERE e9_.created_at IS NULL AND e8_.name IN ('height', 'weight')
AND e8_.user_id = u0_.id
ORDER BY e8_.created_at DESC, u7_.level DESC
)
)
LEFT JOIN service_access u6_ ON u0_.id = u6_.user_id AND (u6_.activated = 1 AND u6_.type = 'default')
LEFT JOIN offer u7_ ON u6_.id = u7_.access_id AND (u7_.activated = 1)
Doctrine takes the OrderBy clause defined in Access.offers attribute (orderBy level DESC), and adds it to the subquery orderBy!! Which doesn't know about the offers table and its level field of course, as these are not in the subquery scope!
It works as if Doctrine adds the orderBy to the only/first OrderBy it finds in the Query, which in this case can't work..
IMO this is a Doctrine bug.
I'm currently under "symfony/symfony": "^3.4",
"doctrine/orm": "2.5.12",.
Any idea someone? Am I the first to encounter this issue?
Thank you very much.
Well, I feel kind of noob now.. but I solved this issue..
I simply reorganized my QueryBuilder methods order to place the Offers juncture before the other one, so that it generates the first orderBy clause in the right place, instead of adding it to the one created in the subquery.
It works.
Still thinking it is kind of weird, as QueryBuilder methods are supposed to work no matter the order they are called..

Symfony Doctrine Query For Many to Many Releationships

I have Many to Many relationship between Institutes and Courses. I want to build query that returns only the institutes list whom some courses has been assigned. I have wrote queries in this situation for one to many. but for not many to many. here is the relationships,
class Institutes {
/**
* #ORM\ManyToMany(targetEntity="Courses", inversedBy="institutes")
* #ORM\JoinTable(name="institute_courses",
* joinColumns={#ORM\JoinColumn(name="institute_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="course_id", referencedColumnName="id")}
* )
*/
protected $courses;
}
class Courses {
/**
* #ORM\ManyToMany(targetEntity="Institutes", mappedBy="courses")
*/
protected $institutes;
}
here is the query that i have written, but didn't work properly.
$repository->createQueryBuilder('s')
->leftJoin('CoursesBundle:Courses','c', 'ON c.institutes = s.courses')
->where('s.active = :active')
->andWhere('s.verified = :active')
->setParameter('active', true)
->orderBy('s.name', 'ASC');
This should do the trick:
$repository->createQueryBuilder('i')
->innerJoin('i.courses','c')
->where('i.active = TRUE')
->andWhere('i.verified = TRUE')
->orderBy('i.name', 'ASC');
You can use a JOIN as you would with other kinds of associations. The following query will find all courses which have been assigned at least to one institute:
SELECT c FROM SomeBundle:Courses c JOIN c.institutes i
You can filter the results further by adding a join condition:
SELECT c FROM SomeBundle:Courses c JOIN c.institutes i WITH i.something = :someParam

Join DQL when intermediate table has not entity mapped

I'm building a repository method to look if a fos_group is assigned to any fos_user before allowing group to be deleted. Basically this is the raw query I'm trying to make using DQL:
SELECT
COUNT(*)
FROM
fos_user u
LEFT JOIN fos_user_user_group fug ON (fug.user_id = u.id)
WHERE
fug.group_id = :group_id
This is how I setup the relationship between Users and Groups:
/**
* #ORM\ManyToMany(targetEntity="Group")
* #ORM\JoinTable(name="fos_user_user_group",
* joinColumns={#ORM\JoinColumn(name="user_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="group_id", referencedColumnName="id")}
* )
*/
protected $groups;
And this is what I have done til now:
$qb = $this->getEntityManager()->createQueryBuilder();
$qb
->select('COUNT(gr.id)')
->from("UserBundle:Group", "gr");
return $qb->getQuery()->getSingleScalarResult();
But I'm stuck since I do not know how to add the join sentence with not mapped table/entity (fos_user_user_group), can anyone give me some push?
You have mapped your property there you are in right way just join the property $groups in user entity it will join your junction table fos_user_user_group and the use what ever you where clause is
$qb
->select('COUNT(g.id)')
->from('UserBundle:User', 'u')
->join('u.groups g')
->where('g.id = :val')
->setParameter('val',$your_group_id);

How to JOIN without relational table in Symfony Doctrine with QueryBuilder between 2 entities

I have an Entity Video related with a Entity Category and I need to run this SQL with Doctrine QueryBuilder, with this I can get the most used categories in all videos (1000+):
SELECT c.*
FROM Video v
INNER JOIN video_category vc ON vc.video_id = v.id
INNER JOIN Category c ON vc.category_id = c.id
GROUP BY c.id
HAVING COUNT(v.id) > 1000
ORDER BY c.name ASC;
My querybuilder:
$queryBuilder = $this->getEntityManager()
->createQueryBuilder()
->select('c')
->from('AcmeVideoBundle:Video', 'v')
// Can Doctrine join itself silently with relational info in the Entities?
->join('AcmeCategoryBundle:Category', 'c', Expr\Join::WITH, 'v.id = c.id')
->groupBy('c.id')
->having('COUNT(v.id) > 1000')
->orderBy('c.name', 'ASC')
->getQuery();
But the SQL query output by queryBuilder is this:
SELECT c0_.id AS id0, c0_.NAME AS name1
FROM Video v1_
INNER JOIN Category c0_ ON (v1_.id = c0_.id)
GROUP BY c0_.id
HAVING COUNT(v1_.id) > 1000
ORDER BY c0_.NAME ASC
Without the relational table (video_category)
The Entities mapping:
/**
* Video
*
* #ORM\Table
* #ORM\Entity(repositoryClass="Acme\VideoBundle\Entity\VideoRepository")
*/
class Video
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\ManyToMany(targetEntity="Acme\CategoryBundle\Entity\Category", cascade={"persist"})
*/
private $category;
// More fields, getters and setters etc...
}
/**
* Category
*
* #ORM\Table
* #ORM\Entity(repositoryClass="Acme\CategoryBundle\Entity\CategoryRepository")
*/
class Category
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\Column(type="string", length=255)
*/
private $name;
// More fields, getters and setters etc...
}
How can I use the relation table to run the original SQL query with doctrine Querybuilder? I missed something?
INFO: When I findBy{field}, persist, flush, clear on all entities works fine, the Doctrine relations are ok, I have a Video, Category and video_category tables fine, the original SQL query works perfect.
// Can Doctrine join itself silently with relational info in the Entities?
->join('AcmeCategoryBundle:Category', 'c', Expr\Join::WITH, 'v.id = c.id')
Yes! In fact that is one of the major reasons for using an ORM such as Doctrine 2.
Try:
->leftJoin('v.category','c')
The manual goes into more details though oddly enough is does not seem to have a join example. Hence the common confusion.
http://docs.doctrine-project.org/en/latest/reference/query-builder.html
And you may not be aware of this but the United Nations has passed a resolution outlawing the use of abbreviations for aliases. Just to be safe, try:
$queryBuilder = $this->getEntityManager()
->createQueryBuilder()
->addSelect('category')
->from('AcmeVideoBundle:Video', 'video')
->leftJoin('video.category', 'category')
->groupBy('category.id')
->having('COUNT(video.id) > 1000')
->orderBy('category.name', 'ASC')
->getQuery();
Ok, solved, the problem was that the Entities isn't fully mapped for ManyToMany bidirectional relationship.
The Entities are now:
class Video
{
/**
* #var ArrayCollection
*
* #ORM\ManyToMany(targetEntity="Acme\CategoryBundle\Entity\Category", inversedBy="video") // Note the inversedBy key
*/
private $category;
}
class Category
{
/**
* #var Video
*
* #ORM\ManyToMany(targetEntity="Acme\VideoBundle\Entity\Video", mappedBy="category") // Again the inversed
*/
private $video; // New field for bidirectional ManyToMany
}
And the final QueryBuilder working (now with full alias version :P):
$queryBuilder = $this->getEntityManager()
->createQueryBuilder()
->select('category')
->from('AcmeCategoryBundle:Category', 'category')
->join('category.video', 'video')
->groupBy('category.id')
->having('COUNT(video.id) > 1000')
->orderBy('category.name', 'ASC')
->getQuery();
Best regards

Categories