Order by within group by in Doctrine 2 - mysql

I'm using Symfony 2 PR12 with Doctrine 2 and MySQL. I have a database storing articles and views of those articles:
// ...
class Article {
/**
* #orm:Column(type="bigint")
* #orm:Id
* #orm:GeneratedValue
* #var int
*/
protected $id;
/**
* #orm:OneToMany(targetEntity="ArticleView",mappedBy="article")
* #var ArrayCollection
*/
protected $views;
// ...
}
// ...
class ArticleView {
/**
* #orm:Column(type="bigint")
* #orm:Id
* #orm:GeneratedValue
* #var int
*/
protected $id;
/**
* #orm:Column(type="bigint",name="DateRead",nullable=true)
* #var int
*/
protected $viewDate;
/**
* #orm:ManyToOne(targetEntity="Article",inversedBy="views")
* #var Article
*/
protected $article;
// ...
}
I want to get, for example, the 20 most-recently-viewed articles. My first thought would be something like:
$qb = <instance of Doctrine\ORM\QueryBuilder>;
$qb->select('a')
->from('Article', 'a')
->join('a.views', 'v')
->orderBy('v.viewDate', 'DESC')
->groupBy('a.id')
->setMaxResults(20)
;
However, when there's more than one view associated with an article, the order-by/group-by combination gives unpredictable results for the ordering.
This is expected behavior for MySQL, since grouping is handled before ordering, and there are working raw-query solutions to this problem at http://www.artfulsoftware.com/infotree/mysqlquerytree.php (Aggregates -> Within-group aggregates). But I can't figure out how to translate any of these solutions into DQL, since as far as I can tell there's no way to select from subqueries or perform self-exclusion joins.
Any ideas on how to solve the problem with reasonable performance?

I ended up solving it with a correlated subquery:
$qb
->select('a')
->from('Article', 'a')
->join('a.views', 'v')
->orderBy('v.viewDate', 'DESC')
->setMaxResults(20)
// Only select the most recent article view for each individual article
->where('v.viewDate = (SELECT MAX(v2.viewDate) FROM ArticleView v2 WHERE v2.article = a)')
That way the sort ignores ArticleView's other than the most recent for any given article. Though my guess is that this performs fairly poorly relative to the other raw SQL solutions - any answers with better performance would still be greatly appreciated :).

Related

MYSQL - How to search in JSON array?

On my Symfony 5 app, i've a database with a candidate table that contains a json field.
candidate 1 : [{"end": "30/04/2020", "start": "01/03/2020"},{"end": "31/07/2020", "start": "01/07/2020"}]
candidate 2 : [{"end": "31/03/2020", "start": "01/03/2020"},{"end": "31/07/2020", "start": "01/07/2020"}]
Is it possible with query builder to find a candidate where this field corresponds to the arguments ?
ex: I would like to find all the candidates who are available between 10/03/2020 and 10/04/2020.
This case should just return the candidate 1.
I guess it's not possible to do this with query builder so i'm trying to use native SQL but... what's the sql syntax ?
I tried with availability_dates`->"$.start" = "01/03/2020" but it does not work because it's a "collection".
This is a poorly-conceived database structure. Clearly, the JSON string represents a "repeating group" of related data, which violates the principles of so-called "normal forms."
https://en.wikipedia.org/wiki/Database_normalization
You should be storing the start/end dates in a separate table, say, candidate_dates, with columns like candidate_id, start, end. This has a so-called "one-to-many relationship" to the parent table, candidates.
Now, you can write a simple query which JOINs the two tables to get the answers you need.
Entity like that ?Entity like that ?
One candidate can have one or more available dates and one available dates can only be linked to one candidate.
<?php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
/**
* #ORM\Table(name="candidate_available_dates", uniqueConstraints={
* #ORM\UniqueConstraint(name="unique_candidate_available_dates", columns={"candidate_id", "start", "end"})
* })
*
* #ORM\Entity(repositoryClass="App\Repository\CandidateAvailableDatesRepository")
*/
class CandidateAvailableDates
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Candidate", inversedBy="candidateAvailableDates")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="candidate_id", referencedColumnName="candidate_id", nullable=false)
* })
*/
private $candidate;
/**
* #ORM\Column(type="date")
* #Assert\NotBlank
*/
private $start;
/**
* #ORM\Column(type="date")
* #Assert\NotBlank
*/
private $end;
[...]
// GETTER and SETTER
And in Candidate entity, the reversed side
/**
* #ORM\OneToMany(targetEntity="App\Entity\CandidateAvailableDates", mappedBy="candidate")
*/
private $candidateAvailableDates;

Doctrine2 multiple join

I have problem with my User Entity. I have code generated by Doctrine it is below:
/**
* #var \Doctrine\Common\Collections\Collection
*
* #ORM\ManyToMany(targetEntity="Frontend\UserBundle\Entity\SfGuardPermission", inversedBy="user")
* #ORM\JoinTable(name="sf_guard_user_permission",
* joinColumns={
* #ORM\JoinColumn(name="user_id", referencedColumnName="id")
* },
* inverseJoinColumns={
* #ORM\JoinColumn(name="permission_id", referencedColumnName="id")
* }
* )
*/
protected $permission;
Problem is with join because I can't join user and permission. What I must to do? I must join sf_guard_user with sf_guard_user_group with sf_guard_grop with sf_guard_group_permission with sf_guard_permission. Because I need to get User permission. I do not no how to write join like this in code above. Is it possible?
You can not write this join in one annotation. In fact you gone have three entity tables sf_guard_user, sf_guard_group and sf_guard_permission and two cross tables which you can write as you already started, sf_guard_user_group and sf_guard_group_permission.
But since it looks like you try to migrate some symfony 1.x stuff to symfony 2.x:
The sf_guard_user_permisson table in symfony 1.x is a cross table between users and permission, containing extraordinaire permission for a user which are not granted through the groups the user is in, so you are already done.
SBH thx for replay, of course you have right with everything what you have written. But my sf_guard_user_permisson is empty so I can't use it. I can generate this table, this is no problem, but then I will must maintain it. This is next work for me so i wrote code below:
namespace Frontend\UserBundle\Entity;
// ...
/**
* #var \Doctrine\Common\Collections\Collection
*
*/
protected $permissions;
/**
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getPermissions()
{
$groups = $this->getSfGuardGroups();
foreach ($groups as $group)
{
$groupPermisions = $group->getPermission();
foreach ($groupPermisions as $groupPermision)
{
if (!in_array($groupPermision, $this->permissions)) {
$this->permissions[] = $groupPermision;
}
}
}
return $this->permissions;
}
/**
* #param string $permissionName
* #return boolean
*/
public function hasPermission($permissionName)
{
$this->getPermissions();
foreach ($this->permissions as $permission)
{
if($permission->getName() === $permissionName) {
return true;
}
}
return false;
}
// ..
What do you think about it? Your opinion is very important for me.
Edit:
Thx for SBH help, I have got answer for my question. I have hope it will help other people. If you do not understand something please look at SBH answer.

Symfony Doctrine - Joins without foreign keys

I'm trying to set up a schema to capture twitter users and their followers.
I have two tables. TwitterUser and Follower. Follower has 3 fields - id, twitterUser, follower.
When a twitter user is a added to the table, I also add a row to Followers to join them with other users I may be interested in.
However, If I get Symfony/Doctrine to build the tables using something like the following-
/**
* #ORM\Entity
* #ORM\Table(name="follower")
* #ORM\HasLifecycleCallbacks
*/
class Follower
{
public function __construct()
{
}
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\ManyToOne(targetEntity="TwitterUser", inversedBy="followers")
* #ORM\JoinColumn(name="user_id", referencedColumnName="id")
*/
protected $twitterUser;
/**
* #ORM\ManyToOne(targetEntity="TwitterUser", inversedBy="following")
* #ORM\JoinColumn(name="follower_id", referencedColumnName="id")
*/
protected $follower;
...
It insists on creating a Foreign Key for follower that I don't want, as I don't want to have to get ALL twitter users to ensure that my joins always work.
The only way I can think to do it, is to remove the annotation and create the SQL for the join myself. Is there a smarter way to do it?
I suggest using a ManyToMany self-referencing relationship as describe below:
http://docs.doctrine-project.org/en/2.0.x/reference/association-mapping.html#many-to-many-self-referencing

Doctrine 2 with multiple indexes

I am developing using zend framework and doctrine2.1.
I have generated entities from database.
But the problem is: Doctrine doesn't recognize my indexes. They are not marked in entity annotations at all.
And when I go to validate-schema and dump sql from orm:schema-tool:update --dump-sql it generates sql to drop all my indexes across whole database.
I found that Doctrine has following annotation used for defining indexes:
indexes={#index(name="index_name",
columns={"database_column1","database_column2"}
)}
But this allows me to define one index for multiple columns and I don't really need that.
What I want is the ability to define multiple indexes on multiple columns, one index per column.
Is there a way I can achieve this? Is there a way that I can have annotation that defines multiple indexes.
I would say you can insert multiple indexes in the indexes property (but I haven't had the time to test it):
indexes={
#ORM\Index(name="index_name", columns={"database_column1","database_column2"}),
#ORM\Index(name="index_name2", columns={"database_column1"}),
#ORM\Index(name="index_name3", columns={"database_column2"})
}
Hope this helps you
Here is an example:
/**
* #Entity
* #Table(name="serial_number",indexes={
* #index(name="product_idx", columns={"product_id"}),
* })
*/
class SerialNumber { // Entity Class
/**
* #var int
*
* #Id
* #GeneratedValue
* #Column(type="integer")
*/
protected $id;
/**
* #Column(name="created_at", type="datetime")
* #var \DateTime
* */
protected $created;
/**
* #Column(name="updated_at", type="datetime")
* #var \DateTime
* */
protected $updated;
/**
* #Column(name="product_id", type="integer")
*/
protected $productID;
}

In doctrine 2.1, how do I get all entities using many to many associations - all articles in several categories?

I have a zend framework 1.11.11 application, using doctrine 2.1 (via bisna).
I have an Article and Category entities. Each article can be associated to many categories, each category can hold many articles.
I've created the following association in each of them:
Article:
/**
* #ManyToMany(targetEntity="Category", inversedBy="categoryArticles")
* #JoinTable
* (
* name="categories_articles",
* joinColumns={#JoinColumn(name="article_id", referencedColumnName="id")},
* inverseJoinColumns={#JoinColumn(name="category_id", referencedColumnName="id")}
* )
* #var \Doctrine\Common\Collections\ArrayCollection
*/
protected $categories;
Category:
/**
* #ManyToMany(targetEntity="Article", mappedBy="categories", cascade={"persist"})
* #var ArrayCollection
*/
private $categoryArticles;
How can I get all the articles that belongs to a given array of categories ?
For example, I want to get all the articles that are associated with categories ids 1, 5 and 20.
The standard repository way ($this->findBy(array('categories' => array(1, 5, 20)))) doesn't work, dql way doesn't work (according to the tries I made) and I can't seem to think of another way.
Help will be much appreciated.
One way to do it is with join/in:
<?php
$category_ids = array(1,2,5);
$qb = $em->createQueryBuilder();
$qb->select('a')
->from('Article','a')
->join('a.categories','c')
->where($qb->expr()->in('c.id',$category_ids));
$articles = $qb->getQuery()->execute();