Doctrine left join with priority on language field with querybuilder - symfony

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.

Related

Doctrine limit to a number ignoring duplicate

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 :)

Counting 2 Columns from Separate Tables Between 2 Dates

I have a query that Counts 2 columns from 2 separate tables using subqueries, which works. Now I have to implement into this query the ability to filter out these results based on the Date of a Call Record. I will post the query in which I am working with:
SELECT (m.FirstName || " " || m.LastName) AS Members,
(
SELECT count(CallToLineOfficers.MemberID)
FROM CallToLineOfficers
WHERE CallToLineOfficers.MemberID = m.MemberID
)
+ (
SELECT count(CallToMembers.MemberID)
FROM CallToMembers
WHERE CallToMembers.MemberID = m.MemberID
) AS Tally
FROM Members AS m, Call, CallToMembers, CallToLineOfficers
Join Call on CallToMembers.CallID = Call.CallID
and CallToLineOfficers.CallID = Call.CallI
WHERE m.FirstName <> 'None'
-- and Call.Date between '2017-03-21' and '2017-03-22'
GROUP BY m.MemberID
ORDER BY m.LastName ASC;
Ok, so table Call stores the Date and its PK is CallID. Both CallToLineOfficers and CallToMembers are Bridge Tables that also contain only CallID and MemberID. With the current query, where the Date is commented out, that Date range should only return all names, but a count of 1 should appear under 1 person's name.
I have tried joining Call.CallID with both Bridge Tables' CallIDs without any luck, though I think this is the right way to do it. Could someone help point me in the right direction? I am lost. (I tried explaining this the best I could, so if you need more info, let me know.)
UPDATED: Here is a screenshot of what I am getting:
Based on the provided date in the sample, the new results, with the Date, should be:
Bob Clark - 1
Rob Catalano - 1
Matt Butler - 1
Danielle Davidson - 1
Jerry Chuska - 1
Tom Cramer - 1
Everyone else should be 0.
At the moment, the subqueries filter only on the member ID. So for any member ID in the outer query, they return the full count.
To reduce the count, you have to filter in the subqueries:
SELECT (FirstName || " " || LastName) AS Members,
(
SELECT count(*)
FROM CallToLineOfficers
JOIN Call USING (CallID)
WHERE MemberID = m.MemberID
AND Date BETWEEN '2017-03-21' AND '2017-03-22'
)
+ (
SELECT count(*)
FROM CallToMembers
JOIN Call USING (CallID)
WHERE MemberID = m.MemberID
AND Date BETWEEN '2017-03-21' AND '2017-03-22'
) AS Tally
FROM Members AS m
WHERE FirstName <> 'None'
ORDER BY LastName ASC;

Where and how to use NULLIF(X,Y) function of SQLITE?

I know that NULLIF(X,Y) function of SQLITE work equivalent to:
CASE
WHEN
X = Y
THEN
NULL
ELSE
X
END
and IFNULL(X,Y) function work equivalent to:
CASE
WHEN
X IS NULL
THEN
Y
ELSE
X
END
IFNULL(X,Y) function of SQLITE is used for replacing the NULL values of X to the Y but I can't understand the use of NULLIF(X,Y) function of SQLITE.
Please explain with examples, so it is more useful.
The IFNULL function is used when the database contains NULL values, but you want to handle those values as something else; for example:
SELECT Name, IFNULL(Age, 'unknown') AS Age FROM People
The NULLIF function is used when the database contains special values that are not NULL, but that you want to handle as NULL.
This is useful especially for aggregate functions. For example, to get the number of employees that get bonuses, use:
SELECT COUNT(NULLIF(Bonus, 0)) FROM Employees
This is the same as:
SELECT COUNT(*) FROM Employees WHERE Bonus != 0
In practice, NULLIF is not used as often as IFNULL.
I use NULLIF() when UPDATing or INSERTing rows containing NULLable fieds.
Basically with PHP :
$value1 = 'foo';
$value2 = '';
$sql = 'INSERT INTO table(field1, field2) '
. "VALUES(NULLIF('$value1', ''), NULLIF('$value2', ''))";
// => INSERT INTO table(field1, field2) VALUES(NULLIF('foo', ''), NULLIF('', ''))
// => INSERT INTO table(field1, field2) VALUES('foo', NULL)
It saves me doing things like :
$value1forSQL = ($value1 === '' || $value1 === NULL) ? 'NULL' : "'$value1'";
...
$sql = ...
. "VALUES($value1forSQL, ...)";

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

Drupal 7 altering query in view

I'm trying to alter a query in a view using mymodule_views_pre_execute and have used devel to find the sql query it is currently using, which is below:
SELECT node.nid AS nid FROM node node LEFT JOIN field_data_field_date
field_data_field_date ON node.nid = field_data_field_date.entity_id AND
(field_data_field_date.entity_type = :views_join_condition_0 AND
field_data_field_date.deleted = :views_join_condition_1)
WHERE ((
(DATE_FORMAT(field_data_field_date.field_date_value, '%Y-%m-%d\T%H:%i') > :node_date_filter) )AND
(( (node.status = :db_condition_placeholder_2) )))
LIMIT 10 OFFSET 0
I am then re-doing this using the following:
$query = db_select("node", "n");
$query->addField("n", "nid");
$query->leftJoin("{field_data_field_date}", "{field_data_field_date}",
"n.nid = field_data_field_date.entity_id AND field_data_field_date.entity_type = 'node'
AND field_data_field_date.deleted = '0'");
$query->where("(DATE_FORMAT(field_data_field_date.field_date_value, '%Y-%m-%d\T%H:%i') > NOW())");
$query->where("n.status = '1'");
I've had to replace :views_join_condition_0 with 'node', :views_join_condition_1 with '0' and :node_date_filter to NOW() although i'm not sure if this is the correct way? If I leave :views_join_condition_0, :views_join_condition_1 and :node_date_filter in though it doesn't work?!
Use hook_view_query_alter(&$view, &$query) instead.

Resources