I have two entities, Group and User:
class Group
{
/**
* #ORM\ManyToMany(targetEntity="Group", inversedBy="groups")
* #ORM\JoinTable(name="admin_group_user",
* joinColumns={#ORM\JoinColumn(name="fk_group", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="fk_user", referencedColumnName="id")}
* )
*/
protected $users;
...
}
class User
{
/**
* #ORM\ManyToMany(targetEntity="Group", inversedBy="users")
* #ORM\JoinTable(name="admin_group_user",
* joinColumns={#ORM\JoinColumn(name="fk_user", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="fk_group", referencedColumnName="id")}
* )
*/
protected $groups;
...
}
I would like to get result like
Group 1 has user A, user B, user C
Group 2 has user D, user E, user F.
Generally something like
SELECT admin_group.id AS group_id, admin_group.name, agu.fk_user, fu.username
FROM admin_group
JOIN admin_group_user agu ON (admin_group.id = agu.fk_group)
JOIN front_user fu ON (agu.fk_user = fu.id);
Does anyone know how to achieve this with Doctrine?
Following documentation about many-to-many bidirectional mapping on doctrine helps you solve your problem:
http://docs.doctrine-project.org/en/latest/reference/association-mapping.html#many-to-many-bidirectional
Related
A little bit of background:
I've got Office entities.
Offices may have articles.
Articles have tags.
Offices can share a tag with other offices (and therefore articles).
Now I'm trying to build a query that says: "Fetch all articles that either belong to my office or have tags that have been shared with my office".
I've tried this in MySQL and the following query works as expected:
SELECT
*
FROM
`articles` `a`
WHERE
(
`office_id` = 2
OR
`a`.`id` IN (
SELECT
`at`.`article_id`
FROM
`article_tags` `at`
WHERE
`at`.`article_id` = `a`.`id`
AND
`at`.`tag_id` IN
(
SELECT
`st`.`tag_id`
FROM
`shared_tags` `st`
WHERE
`st`.`shared_with_id` = 2
AND
`st`.`article` = 1
)
)
)
AND
`status` = 1
My entities are as follows:
Article
/**
* #ORM\ManyToMany(targetEntity="Tag", inversedBy="articles")
* #ORM\JoinTable(name="article_tags")
* #var ArrayCollection|Tag[]
*/
protected $tags;
Tag
/**
* #ORM\ManyToMany(targetEntity="Article", mappedBy="tags")
* #var ArrayCollection|Article[]
*/
protected $articles;
SharedTag
class SharedTag extends Entity
{
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
* #var int
*/
protected $id;
/**
* #ORM\ManyToOne(targetEntity="Office", inversedBy="sharedTags")
* #ORM\JoinColumn(name="shared_with_id", referencedColumnName="id")
* #var Office
*/
protected $sharedWith;
/**
* #ORM\ManyToOne(targetEntity="Office", inversedBy="sharedTags")
* #ORM\JoinColumn(name="shared_by_id", referencedColumnName="id")
* #var Office
*/
protected $sharedBy;
/**
* #ORM\ManyToOne(targetEntity="Tag", inversedBy="sharedTags")
* #var Tag
*/
protected $tag;
/**
* #ORM\Column(type="boolean", options={"default" = 0})
* #var bool
*/
protected $template = 0;
/**
* #ORM\Column(type="boolean", options={"default" = 0})
* #var bool
*/
protected $article = 0;
/**
* #ORM\Embedded(class = "\ValueObjects\CreatedAt", columnPrefix=false)
* #var CreatedAt
*/
protected $createdAt;
}
So, how can I query this in DQL or with the QueryBuilder? I've tried several methods but I can't seem to use the tag_id from the relationship articles.tags in a WHERE IN() query.
Thank you in advance!
Edit:
After some trial and error and thanks to Jean's answer I was able to query it like this:
SELECT
a
FROM
Article a
LEFT JOIN a.tags t
LEFT JOIN a.office o
LEFT JOIN o.sharedTags st
WHERE
(
a.office = :office
OR
(
t = st.tag
AND
st.sharedWith = :office
AND
st.article = 1
)
)
AND
a.status.status = :status
AND
a.template IS NULL
GROUP BY a.id
You should use a join and not subqueries. This way you have better performance and less complicated DQL code:
SELECT DISTINCT `a`
FROM `articles` `a`
LEFT JOIN `a.tags` `t`
LEFT JOIN `t.shared_with_id` `o`
WHERE (`a`.`office_id` = 2 OR `o`.`id` = 2)
AND `status` = 1
I don't find an example here in SO, so i post my question:
I have a group entity, a shop entity, and a transaction entity.
A group has many shops, and a shop can belong to many groups. In Group.php:
/**
* #ORM\ManyToMany(targetEntity="Shop", inversedBy="groups")
* #ORM\JoinTable(name="group_shop",
* joinColumns={#ORM\JoinColumn(name="group_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="shop_id", referencedColumnName="id")}
* )
**/
private $shops;
And in Shop.php
/**
* #ORM\ManyToMany(targetEntity="Group", mappedBy="shops")
*/
private $groups;
Then, a shop makes transactions. In Transaction.php:
/**
* #ORM\ManyToOne(targetEntity="Shop", inversedBy="transactions")
* #ORM\JoinColumn(name="shop_id", referencedColumnName="id")
* */
private $shop;
And in Shop.php:
/**
* #ORM\OneToMany(targetEntity="Transaction", mappedBy="shop")
**/
private $transactions;
What I want to query is all transactions from a group. This must be very simple, buy i'm blinded.
What I have:
$query4 = $em->createQuery("SELECT t FROM MGFAppBundle:Transaction t
WHERE t.date > :from AND t.date < :to AND t.shop IN (/* HERE I'M STUCK */)")- >setParameters(array(
'from' => $from
'to' => $to
));
I don't know if this is the correct approach or... well, dql is kinda hard to get for me.
How do I write this dql query?
Thanks in advance.
In your repository do something like this:
public function findTransactionsByGroup(GroupInterface $group)
{
return $this->createQueryBuilder('g')
->select('s.transactions')
->leftJoin('g.shops','s')
->where('s.group = :groupid')
->setParameter('groupid', $group->getId())
->getQuery()
->getResult();
}
i have 2 entity with one-to-many relation : Article & ArticleCategory
class Article {
/**
* #var integer
*
* #ORM\Column(name="rate", type="integer",options={"default" : 0})
*/
private $rate = 0;
/**
* #var \ArticleCategory
*
* #ORM\ManyToOne(targetEntity="ArticleCategory",inversedBy="articles")
* #ORM\JoinColumn(name="article_category_id", referencedColumnName="id")
*/
private $category;
}
class ArticleCategory {
/**
*
* #var \Article
* #ORM\OneToMany(targetEntity="Article", mappedBy="category")
*/
protected $articles;
}
now i want to fetch categories which has much articles with top rates..
" i mean top N categories ordered by highest rated article in them (categories which has more articles with rate above the average rate)"
how can i do this?
finally i found this for my question, maybe it can be useful for others! :)
$query = $em->createQuery('SELECT c, avg(a.rate) x
FROM YoutabsGeneralModelBundle:ArticleCategory c
JOIN c.articles a
GROUP BY c.id
ORDER BY x DESC');
i add ORDER BY because i wanted to set limit on this :
$query->setMaxResults($limit);
I'm using Symfony 2 with Doctrine, and I've got two entities joined in a many to many association.
Let's say I have two entities: User and Group, and the related tables on db are users, groups and users_groups.
I'd like to get the top 10 most populated groups in DQL, but I don't know the syntax to perform queries on the join table (users_groups). I already looked on the Doctrine manual but I didn't found the solution, I guess I still have a lot to learn about DQL.
In plain sql that would be:
select distinct group_id, count(*) as cnt from users_groups group by group_id order by cnt desc limit 10
Can you please help me to translate this to DQL?
Update (classes):
/**
* Entity\E_User
*
* #ORM\Table(name="users")
* #ORM\Entity
*/
class E_User
{
/**
* #ORM\ManyToMany(targetEntity="E_Group", cascade={"persist"})
* #ORM\JoinTable(name="users_groups",
* joinColumns={#ORM\JoinColumn(name="user_id", referencedColumnName="id", onDelete="cascade")},
* inverseJoinColumns={#ORM\JoinColumn(name="group_id", referencedColumnName="id", onDelete="cascade")}
* )
*/
protected $groups;
/**
* #var integer $id
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string $name
*
* #ORM\Column(name="name", type="string", length=255)
*/
private $name;
/* ... other attributes & getters and setters ...*/
}
/**
* Entity\E_Group
*
* #ORM\Table(name="groups")
* #ORM\Entity
*/
class E_Group
{
/**
* #var integer $id
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string $name
*
* #ORM\Column(name="text", type="string", length=255)
*/
private $name;
/* ... other attributes & getters and setters ...*/
}
It's not easy without seeing the actual classes, but by guessing you have a many-to-many bidirectional relationship:
$dql = "SELECT g.id, count(u.id) as cnt FROM Entity\Group g " .
"JOIN g.users u GROUP BY g.id ORDER BY cnt DESC LIMIT 10;";
$query = $em->createQuery($dql);
$popularGroups = $query->getArrayResult();
UPDATE:
You don't have to use a bidirectional relationship, you can query the other way around:
$dql = "SELECT g.id, count(u.id) as cnt FROM Entity\User u " .
"JOIN u.groups g GROUP BY g.id ORDER BY cnt DESC LIMIT 10;";
For those who want to build the query with Doctrine's QueryBuilder instead of using DQL directly take this solution.
Please note that my problem wasn't to get the top user groups, but technically the problem is pretty similar to mine. I work with posts (like articles/blog posts) and tags that are added to posts. I needed to determine a list of related posts (identified by same tags). That list had to be sorted by relevance (the more same tags another post has the more relevant it is).
This is the method of my PostRepository class:
/**
* Finds all related posts sorted by relavance
* (from most important to least important) using
* the tags of the given post entity.
*
* #param Post $post
*
* #return POST[]
*/
public function findRelatedPosts(Post $post) {
// build query
$q = $this->createQueryBuilder('p')
->addSelect('count(t.id) as relevance')
->innerJoin('p.tags', 't')
->where('t.id IN (:tags)')
->setParameter('tags', $post->getTags())
->andWhere('p.id != :post')
->setParameter('post', $post->getId())
->addGroupBy('p.id')
->addOrderBy('relevance', 'DESC')
->getQuery();
// execute query and retrieve database result
$r = $q->execute();
// result contains arrays, each array contains
// the actual post and the relevance value
// --> let's extract the post entities and
// forget about the relevance, because the
// posts are already sorted by relevance
$r = array_map(function ($entry) {
// first index is the post, second one
// is the relevance, just return the post
return reset($entry);
}, $r);
// array of posts
return $r;
}
Thank you #Tom Imrei for you solution. Also the answer #26549597 was very helpful.
To improve Tom's answer, you could use DQL's HIDDEN keyword. This way, the result doesn't contain the useless cnt column and Arvid's array_map solution isn't needed (which could speed up the result significantly for larger queries).
And the OP's question was to get the top 10 groups, not just their IDs.
It would look something like this:
$query = 'SELECT g, HIDDEN COUNT(u.id) AS cnt FROM Entity\Group g LEFT JOIN g.users u ORDER BY cnt DESC';
$groups = $em->createQuery($query)->setMaxResults(10)->getResult();
Also, note the use of LEFT JOIN that ensures that empty groups are not dismissed.
I'm trying to do an association of 5 objects with Doctrine2 (PHP).
I'm using PostgreSQL.
Here is the Database schema:
Database schema
A company may have many Hubs, each Hub have one Harbor.
A company may have many Line, each Line have a Linelist.
A Linelist have 2 Harbors.
For example, a Linelist is "Los Angeles-Seattle", and multiple companies may own it thanks to the Line table.
I'm trying to query all the Hub, Harbor, Linelist, and Line for one company.
I have the SQL query:
SELECT *
FROM hub h
JOIN harbor a
ON a.id = h.harbor_id
JOIN linelist l
ON (l.harborstart_id = a.id OR l.harborend_id = a.id)
JOIN line m
ON m.linelist_id = l.id
WHERE h.company_id = 41
AND m.company_id = 41"
I'm trying to do the same using DQL.
I tried this, but it doesn't worked:
$query = $this->getEntityManager()
->createQuery('SELECT h, a, l, m
FROM AmGameBundle:Hub h
JOIN h.harbor a
JOIN a.linelist l
JOIN l.line m
WHERE h.company = :company_id
AND m.company = :company_id')
->setParameter('company_id', $company_id);
As a result, I only have the LineList and Line objects matching harborstart_id, but I want the one matching either harborstart_id or harborend_id.
Do you think this is possible in DQL?
It might be better to change the relation between Harbor and Linelist for a many to many?
I think that's a matter of defining 2 relations from harbor to linelist in your Entities. I imagine you have something like
<?php
/**
* #Entity
* #Table(name="LineList")
*/
class LineList {
/**
* #var object $startHarbor
* #ManyToOne(targetEntity="Harbor", inversedBy="startHarbors")
* #JoinColumn(name="harborstart_id", referencedColumnName="id", nullable=FALSE)
*/
protected $startHarbor;
}
/**
* #Entity
* #Table(name="Harbor")
*/
class Harbor {
/**
* #var object $startHarbors
* #OneToMany(targetEntity="LineList", mappedBy="startHarbor")
*/
protected $startHarbors;
}
That will let you join Harbors to LineLists via harborstart_id (you named the variable linelist, but I think now it's better to change the identifiers as there will be 2 referring to the same foreign table), then if you want to harborend_id
<?php
/**
* #Entity
* #Table(name="LineList")
*/
class LineList {
/**
* #var object $startHarbor
* #ManyToOne(targetEntity="Harbor", inversedBy="startHarbors")
* #JoinColumn(name="harborstart_id", referencedColumnName="id", nullable=FALSE)
*/
protected $startHarbor;
/**
* #var object $endHarbor
* #ManyToOne(targetEntity="Harbor", inversedBy="endHarbors")
* #JoinColumn(name="harborend_id", referencedColumnName="id", nullable=FALSE)
*/
protected $endHarbor;
}
/**
* #Entity
* #Table(name="Harbor")
*/
class Harbor {
/**
* #var object $startHarbors
* #OneToMany(targetEntity="LineList", mappedBy="startHarbor")
*/
protected $startHarbors;
/**
* #var object $endHarbors
* #OneToMany(targetEntity="LineList", mappedBy="endHarbor")
*/
protected $endHarbors;
}
Now you can change the DQL to:
$query = $this->getEntityManager()
->createQuery('SELECT h, a, sh, eh, m
FROM AmGameBundle:Hub h
JOIN h.harbor a
JOIN a.startHarbors sh
JOIN a.endHarbors eh
JOIN l.line m
WHERE h.company = :company_id
AND m.company = :company_id')
->setParameter('company_id', $company_id);
That should get you in the right direction. If it becomes troublesome though, a many-to-many approach as you speculated should be a well documented solution.