Doctrine limit to a number ignoring duplicate - symfony

I'm using Symfony 3.4 and I have a request in my user repository.
I want the top 5 users based on score. So I have created my request with an order by on score column and a limit to 5 result.
$query = $this->createQueryBuilder('u')
->orderBy('u.score', 'DESC')
->setMaxResults(5)
->getQuery()
->getResult();
But I want the top 5 with taking the user with same score. For exmaple with:
User : Score
Jack : 100
Mick : 50
Joe : 10
Daniel : 25
Fred : 75
James : 100
Billy : 2
I want to return 6 result (because two user have the same score). What I want
Jack
James
Fred
Mick
Daniel
Joe
And If I have an other user with the same score than Mick, it should return 7 result.
The number of result return the top 5 best score but with all the user with this score.
How can I edit my query to do it ?

You should use a subquery selecting all the user that have the top5 score, as example:
$subQuery = $this->createQueryBuilder('u1')
->select('DISTINCT u1.score')
->orderBy('u1.score', 'DESC')
->setMaxResults(5)
->GetDQL();
$query = $this->createQueryBuilder('u2');
$query->where(
$query->expr()->in(
'u2.score', $subquery))
->orderBy('u2.score', 'DESC')
->getQuery()
->getResult();
Hope this help

I have found a solution with two request (feel free to say if you have better solution)
This is my code working but in my question I simplify the text, in reality my score has store on a user_session table, so it has more join and more condition but it can help.
First I get all the top 5 score with a distinct to group the same score.
$now = new \DateTime();
$leaderScore = $this->createQueryBuilder('u')
->select('us.score')
->join('u.sessionUsers', 'us')
->join('us.session', 's')
->where(':now BETWEEN s.start_date AND s.end_date')
->setParameter('now', $now)
->setMaxResults(self::NUMBER_LEADERS_DISPLAY)
->distinct()
->orderBy('us.score', 'DESC')
->getQuery()
->getResult();
I save the worst score of the top 5:
$minScore = min($leaderScore);
And I make another request to get all users with a higher or egal score than $minScore
$query = $this->createQueryBuilder('u')
->join('u.sessionUsers', 'us')
->join('us.session', 's')
->where(':now BETWEEN s.start_date AND s.end_date')
->andWhere('u.roles LIKE :role')
->andWhere('us.score >= :minScore')
->orderBy('us.score', 'DESC')
->setParameter('now', $now)
->setParameter('role', '%PLAYER%')
->setParameter('minScore', $minScore);
Hope this help :)

Related

Doctrine left join with priority on language field with querybuilder

I'm using the query below:
use Doctrine\ORM\Query\Expr\Join;
$query = $this->createQueryBuilder('ad')
->select('ad.id, ad.title, ad.year, ad.hours, ad.status')
->addSelect('rem.remark')
->leftJoin('ad.remark', 'rem', Join::WITH, "rem.language = 'NL'")
->getQuery()
->getResult();
This query is working fine and returns the remark of a ad in the Dutch language. The ad has a one-to-many relation with its remark.
Only I also have ads that have for example an English remark and not a Dutch one. The I will like to get the English remark of that one and on the others in the list still the Dutch remark. So too summarise making a priority list on the languages that are returned?
One way to solve this is to use an extra join without relation:
$query = $this->createQueryBuilder('ad')
->select('ad.id, ad.title, ad.year, ad.hours, ad.status')
->addSelect('rem.remark')
->leftJoin('ad.remark', 'rem', Join::WITH, "rem.language = 'NL' OR rem.language = 'EN'")
->leftJoin(Remark::class, 'customRem', Join::WITH,
"rem.id <> customRem.id
AND rem.ad = customRem.ad
AND customRem.language = 'NL'")
->where('customRem.id IS NULL')
->getQuery()
->getResult();
The idea is
if NL language remark exists for an ad, add this remark joined to each result row of this ad expect to itself(will be null)
if NL language remark does not exist and an EN exists, then the joined row will be null
Finally, the condition customRem.id IS NULL makes this work.
Multiple languages solution
In the case of 3 supported languages, because DE > EN > NL, you could do:
->leftJoin(Remark::class, 'customRem', Join::WITH,
"rem.id <> customRem.id AND rem.ad =
customRem.ad AND rem.language < customRem.language")
For multiple languages and suppose a "customized" ability to order the languages, you could use:
"rem.id <> customRem.id
AND rem.ad = customRem.ad AND
(case when rem.language = 'NL' THEN 3 " .
"when rem.language = 'EN' THEN 2 " .
"when rem.language = 'DE' THEN 1 ELSE 0 END) < (case when customRem.language = 'NL' THEN 3 " .
"when customRem.language = 'EN' THEN 2 " .
"when customRem.language = 'DE' THEN 1 ELSE 0 END)"
Alternatively, you could create "lang_position" table(id, language, position) and join twice to get the position from the language.

How to fetch row count of related entities?

I have two entities, Class and Student. One Class can have multiple Students (oneToMany):
# YAML notation for Entity 'Class'
...
oneToMany:
students:
targetEntity: MyBundle\Entity\Student
mappedBy: class
For fetching all Classes, I'm writing my own query like this:
SELECT c
FROM MyBundle:Class c
WHERE c.whatever = :parameter
ORDER BY c.id DESC
Now I'm trying to fetch a list of Classes, ordered (DESC) by the count of related Students. So that the result would look like:
Class.id Class.count(Student)
-------- --------------------
3 109
1 81
4 58
2 21
How would I go there? I tried something somewhat like this:
SELECT
c,
COUNT(c.students) AS students
FROM MyBundle:Class c
WHERE c.whatever = :param
GROUP BY c.id
ORDER BY students DESC
(Note: I implement DoctrineExtensions' Date function)
But I'm getting an error:
[Semantical Error] line 0, col 26 near 'students)': Error: Invalid PathExpression. StateFieldPathExpression or SingleValuedAssociationField expected.
Try to use this in your Class repository:
public function getAllClassesOrderedByNumberOfStudents()
{
$qb = $this->createQueryBuilder('class');
$buffer = $qb
->select('class, COUNT(students) AS students_per_class')
->innerJoin('class.students', 'students')
->groupBy('class.id')
->orderBy('students_per_class', 'DESC');
$q = $buffer->getQuery();
return $q->getResult();
}

Doctrine - select last 4 rows from table

I'm using Symfony/Doctrine.
I'm trying to select last 4 rows from table, but im getting error.
$em = $this->getDoctrine()->getEntityManager();
$query = $em->createQuery(
'SELECT c FROM DprocMainBundle:Courses c ORDER BY id DESC LIMIT 4'
);
$course = $query->getResult();
This is my query but it shows error.
Expected end of string, got 'LIMIT'
How should i use limit, and get the LAST 4 rows?
thanks!
Use setMaxResults() to limit the number of results.
$course = $query->setMaxResults(4)->getResult();
If you want to use this for pagination you can add a setFirstResult() call.
$course = $query->setMaxResults(4)->setFirstResult(10)->getResult();

symfony2 doctrine select IFNULL

Ok i have this code:
SELECT
IFNULL(s2.id,s1.id) AS effectiveID,
IFNULL(s2.status, s1.status) AS effectiveStatus,
IFNULL(s2.user_id, s1.user_id) as effectiveUser,
IFNULL(s2.likes_count, s1.likes_count) as effectiveLikesCount
FROM statuses AS s1
LEFT JOIN statuses AS s2 ON s2.id = s1.shared_from_id
WHERE s1.user_id = 4310
ORDER BY effectiveID DESC
LIMIT 15
And i need to rewrite it to querybuilder. Something like that?
$fields = array('IFNULL(s2.id,s1.id) AS effectiveID','IFNULL(s2.status, s1.status) AS effectiveStatus', 'IFNULL(s2.user_id, s1.user_id) as effectiveUser','IFNULL(s2.likes_count, s1.likes_count) as effectiveLikesCount');
$qb=$this->_em->createQueryBuilder()
->select($fields)
->from('WallBundle:Status','s1')
->addSelect('u')
->where('s1.user = :user')
->andWhere('s1.admin_status = false')
->andWhere('s1.typ_statusu != :group')
->setParameter('user', $user)
->setParameter('group', 'group')
->leftJoin('WallBundle:Status','s2', 'WITH', 's2.id=s1.shared_from_id')
->innerJoin('s1.user', 'u')
->orderBy('s1.time', 'DESC')
->setMaxResults(15);
var_dump($query=$qb->getQuery()->getResult());die();
This error is
[Syntax Error] line 0, col 7: Error: Expected known function, got 'IFNULL'
Use COALESCE instead of IFNULL like this
$fields = array('COALESCE(s2.id,s1.id) AS effectiveID','COALESCE(s2.status, s1.status) AS effectiveStatus', 'COALESCE(s2.user_id, s1.user_id) as effectiveUser','COALESCE(s2.likes_count, s1.likes_count) as effectiveLikesCount');
COALESCE return the first value not null in the list, so if A is null and B not null, then COALESCE(A,B) will return B.
There is a Doctrine extension that adds this among others.
This is the DQL file from IFNULL.
https://github.com/beberlei/DoctrineExtensions/blob/master/src/Query/Mysql/IfNull.php
This chapter explains how you use them.
http://symfony.com/doc/2.0/cookbook/doctrine/custom_dql_functions.html

doctrine2 findby two columns OR condition

My action:
$matches_request = $em->getRepository('Bundle:ChanceMatch')->findByRequestUser(1);
$matches_reply = $em->getRepository('Bundle:ChanceMatch')->findByReplyUser(1);
Is it possible to join the querys with an or condition with getRepository, eg.
$matches_reply = $em->getRepository('FrontendChancesBundle:ChanceMatch')->findBy(array('requestUser' => 1, 'replyUser' => 1);
//this of course gives me the a result when `requestUser` and `replyUser` is `1`.
My table
id | requestUser | replyUser
....
12 | 1 | 2
13 | 5 | 1
My query should return the id 12 & 13.
Thanks for help!
You can use QueryBuilder or create a custom repository for that entity and create a function that internally use QueryBuilder.
$qb = $em->getRepository('FrontendChancesBundle:ChanceMatch')->createQueryBuilder('cm');
$qb
->select('cm')
->where($qb->expr()->orX(
$qb->expr()->eq('cm.requestUser', ':requestUser'),
$qb->expr()->eq('cm.replyUser', ':replyUser')
))
->setParameter('requestUser', $requestUserId)
->setParameter('replyUser', $replyUserId)
;
$matches_reply = $qb->getQuery()->getSingleResult();
// $matches_reply = $qb->getQuery()->getResult(); // use this if there can be more than one result
For more information on custom Repository see official documentation:
http://symfony.com/doc/2.0/book/doctrine.html#custom-repository-classes
It's possible to use Criteria for the complex queries with getRepository():
$criteria = new \Doctrine\Common\Collections\Criteria();
$criteria
->orWhere($criteria->expr()->contains('domains', 'a'))
->orWhere($criteria->expr()->contains('domains', 'b'));
$domain->ages = $em
->getRepository('Group')
->matching($criteria);

Resources