Join subquery with doctrine 2 DBAL - php

I'm refactoring a Zend Framework 2 application to use doctrine 2.5 DBAL instead of Zend_DB (ZF1). I have the following Zend_Db query:
$subSelect = $db->select()
->from('user_survey_status_entries', array('userSurveyID', 'timestamp' => 'MIN(timestamp)'))
->where('status = ?', UserSurveyStatus::ACCESSED)
->group('userSurveyID');
$select = $db->select()
// $selectColNames contains columns both from the main query and
// the subquery (e.g. firstAccess.timestamp AS dateFirstAccess).
->from(array('us' => 'user_surveys'), $selectColNames)
->joinLeft(array('firstAccess' => $subSelect), 'us.userSurveyID = firstAccess.userSurveyID', array())
->where('us.surveyID = ?', $surveyID);
This results in the following MySQL query:
SELECT `us`.`userSurveyID`,
// More columns from main query `us`
`firstAccess`.`timestamp` AS `dateFirstAccess`
FROM `user_surveys` AS `us`
LEFT JOIN (
SELECT `user_survey_status_entries`.`userSurveyID`,
MIN(timestamp) AS `timestamp`
FROM `user_survey_status_entries`
WHERE (status = 20)
GROUP BY `userSurveyID`
) AS `firstAccess` ON us.userSurveyID = firstAccess.userSurveyID
WHERE (us.surveyID = '10')
I can't figure out how to join the subquery using the doctrine 2.5 query builder. In the main query, I need to select columns from the subquery.
I have read here that doctrine does not support joining subqueries. If that's still true, can I write this query in another way using the SQL query builder of doctrine DBAL? Native SQL may not be a good solution for me, as this query will be dynamically extended later in the code.

I've found a solution by adapting this DQL example to DBAL. The trick is to get the raw SQL of the subquery, wrap it in brackets, and join it. Parameters used in the subquery must be set in the main query:
Important it's the createQueryBuilder of connection not the one of the entity manager.
$subSelect = $connection->createQueryBuilder()
->select(array('userSurveyID', 'MIN(timestamp) timestamp'))
->from('user_survey_status_entries')
// Instead of setting the parameter in the main query below, it could be quoted here:
// ->where('status = ' . $connection->quote(UserSurveyStatus::ACCESSED))
->where('status = :status')
->groupBy('userSurveyID');
$select = $connection->createQueryBuilder()
->select($selectColNames)
->from('user_surveys', 'us')
// Get raw subquery SQL and wrap in brackets.
->leftJoin('us', sprintf('(%s)', $subSelect->getSQL()), 'firstAccess', 'us.userSurveyID = firstAccess.userSurveyID')
// Parameter used in subquery must be set in main query.
->setParameter('status', UserSurveyStatus::ACCESSED)
->where('us.surveyID = :surveyID')->setParameter('surveyID', $surveyID);

To answer this part of your question:
I can't figure out how to join the subquery using the doctrine 2.5 query builder
You can make 2 query builder instances and use the DQL from the second one inside a clause of your first query. An example:
->where($qb->expr()->notIn('u.id', $qb2->getDQL())
Check examples here or here or find more using Google

Related

Symfony queryBuilder, can`t rewrite sql to queryBuidler

I can`t rewrite SQL query to queryBuilder, for my task need only queryBuilder object.
I have this SQL query, I need take from database per each user, orders which be last and have isPaid = 0.
SELECT
*
FROM
orders o
JOIN
(
SELECT
owner_id,
MAX(created_at) max_date
FROM
orders
GROUP BY
owner_id
) max_dates
ON
o.owner_id = max_dates.owner_id AND o.created_at = max_dates.max_date
WHERE
is_paid = 0
Since you join a subquery it might be a bit tricky to transfer this SQL query to DQL. Fortunately, you don't have to. Doctrine ORM allows you to perform a regular SQL query and then map the results back to an object, just like it would with DQL.
You can have a look at Native Queries and ResultSetMapping for this:
https://www.doctrine-project.org/projects/doctrine-orm/en/2.7/reference/native-sql.html
In your case it could look something roughly like this in a repository find-method:
public function findLatestUnpaidOrders()
{
$sql = '...'; // Your query
$rsm = new ResultSetMappingBuilder($this->em);
$rsm->addRootEntityFromClassMetadata(Order::class, 'order');
$query = $this->em->createNativeQuery($sql, $rsm);
// $query->setParameter('owner_id', $user->getId()); // if you later want to pass parameters into your SQL query
return $query->getResult();
}

Laravel query with temporary table

How to convert this query to laravel db query.
SELECT * FROM {
Select * from organizers
Order by organizers.rank
} Group by t.department
This is simplified version of query. In real the inner query has more where clause and built using laravel db query.
Edit: I am aware of raw query. But that's not what I am looking for. Inner query is complex and has lots of conditional where clause. I would like to retain the db query object I used there.
You can have 2 different query builders and merge their binding like below :
$innerQuery = DB::table('organizers')->orderBy('organizers.rank');
$mainQuery = DB::table(DB::raw('(' . $innerQuery->toSql() . ') as t'))
->mergeBindings($innerQuery->getQuery())
->groupBy('t.department')
->get();
This will also help you retail the $innerQuery builder instance for your later use as you have mentioned in the question.
I think you will have to execute a raw query.
$result = DB::select("SELECT * FROM (
Select * from organizers
Order by organizers.rank
) Group by t.department");
reference: https://laravel.com/docs/5.7/queries#raw-expressions

How to convert mysql query to codeigniter using query builder?

The following is my mysql query:
select * from db_posts LEFT JOIN db_followers ON db_posts_user_id = db_followers_following AND db_followers_user_id = 276
How can I convert it to codeigniter using query builder?
$this->db->where('db_posts_user_id', 'db_followers_following');
$this->db->where('db_followers_user_id', 276);
$this->db->order_by('db_posts.db_posts_id', 'DESC');
$this->db->join('db_followers', 'db_followers.db_followers_following = db_posts.db_posts_user_id');
$query2 = $this->db->get('db_posts')->result_array();
I get the required result when I use mysql query. But I get a blank array when I use codeigniter queries. What is wrong in my CI Query?
why are you using those where clause? if you are using it as condition for your 'JOIN' process, try it like this:
$this->db->order_by('db_posts.db_posts_id', 'DESC');
$this->db->join('db_followers', 'db_followers.db_followers_following = db_posts.db_posts_user_id and db_followers_user_id = 276', 'left');
$query2 = $this->db->get('db_posts')->result_array();
Points to Note
1)as previous comments said, you should 'Left Join' if you want to get all posts from 'db_posts'.
2) You can add Multiple conditions in ON Clause using and but within ''. all your conditions should be specified before second comma(,) which is before mentioning 'LEFT'.
Hope this will help. Lemme know if this help
Try adding the "Left" option to tell it what kind of join to do:
$this->db->join('db_followers', 'db_followers.db_followers_following = db_posts.db_posts_user_id', 'Left');
Also I'm not sure you need
$this->db->where('db_posts_user_id', 'db_followers_following');
this is already covered by your JOIN statement.
Source:
http://www.bsourcecode.com/codeigniter/codeigniter-join-query/#left-join and https://www.codeigniter.com/userguide3/database/query_builder.html

DQL Select every rows having one column's MAX value

Working with Symfony 2 and Doctrine, I'm searching for a way to select every rows having the max value in a specific column.
Right now, I'm doing it in two queries:
One to get the max value of the column in the table
Then I select rows having this value.
I'm sure this can be done with one query.
Searching, I have found this answer in a thread, that seems to be what I am searching for, but in SQL.
So according to the answer's first solution, the query I'm trying to build would be something like that:
select yt.id, yt.rev, yt.contents
from YourTable yt
inner join(
select id, max(rev) rev
from YourTable
group by id
) ss on yt.id = ss.id and yt.rev = ss.rev
Does anybody know how to make it in Doctrine DQL?
For now, here is the code for my tests (not working):
$qb2= $this->createQueryBuilder('ms')
->select('ms, MAX(m.periodeComptable) maxPeriode')
->where('ms.affaire = :affaire')
->setParameter('affaire', $affaire);
$qb = $this->createQueryBuilder('m')
->select('m')
//->where('m.periodeComptable = maxPeriode')
// This is what I thought was the most logical way of doing it:
->innerJoin('GAAffairesBundle:MontantMarche mm, MAX(mm.periodeComptable) maxPeriode', 'mm', 'WITH', 'm.periodeComptable = mm.maxPeriode')
// This is a version trying with another query ($qb2) as subquery, which would be the better way of doing it for me,
// as I am already using this subquery elsewhere
//->innerJoin($qb2->getDQL(), 'sub', 'WITH', 'm.periodeComptable = sub.maxPeriode')
// Another weird try mixing DQL and SQL logic :/
//->innerJoin('SELECT MontantMarche mm, MAX(mm.periodeComptable) maxPeriode ON m.periodeComptable = mm.maxPeriode', 'sub')
//->groupBy('m')
->andWhere('m.affaire = :affaire')
->setParameter('affaire', $affaire);
return $qb->getQuery()->getResult();
The Entity is GAAffairesBundle:MontantMarche, so this code is in a method of the corresponding repository.
More generally, I'm learning about how to handle sub-queries (SQL & DQL) and DQL syntax for advanced queries.
Thx!
After some hours of headache and googling and stackOverflow readings...
I finally found out how to make it.
Here is my final DQL queryBuilder code:
$qb = $this->createQueryBuilder('a');
$qb2= $this->createQueryBuilder('mss')
->select('MAX(mss.periodeComptable) maxPeriode')
->where('mss.affaire = a')
;
$qb ->innerJoin('GAAffairesBundle:MontantMarche', 'm', 'WITH', $qb->expr()->eq( 'm.periodeComptable', '('.$qb2->getDQL().')' ))
->where('a = :affaire')
->setParameter('affaire', $affaire)
;
return $qb->getQuery()->getResult();
For me when i trying to make a subquery i make:
->andWhere($qb->expr()->eq('affaire', $qb2->getDql()));
To achieve this using pure DQL and without use of any aggregate function you can write doctrine query as
SELECT a
FROM GAAffairesBundle:MontantMarche a
LEFT JOIN GAAffairesBundle:MontantMarche b
WITH a.affaire = b.affaire
AND a.periodeComptable < b.periodeComptable
WHERE b.affaire IS NULL
ORDER BY a.periodeComptable DESC
The above will return you max record per group (per affaire)
Expalnation
The equivalent SQL for above DQL will be like
SELECT a.*
FROM MontantMarche a
LEFT JOIN MontantMarche b
ON a.affaire = b.affaire
AND a.periodeComptable < b.periodeComptable
WHERE b.affaire IS NULL
ORDER BY a.periodeComptable DESC
Here i assume there can be multiple entries in table e.g(MontantMarche) for each affaire, so here i am trying to do a self join on affaire and another tweak in join is i am trying to join only rows from right table(b) where a's periodeComptable < b's periodeComptable, So the row for left table (a) with highest periodeComptable will have a null row from right table(b) thus to pick the highest row per affaire the WHERE right table row IS NULL necessary.
Similarly using your posted sample query with inner join can be written as
select yt.id, yt.rev, yt.contents
from YourTable yt
left join YourTable ss on yt.id = ss.id and yt.rev < ss.rev
where ss.rev is null
Hope it makes sense

Using native SQL query without entity class

I need to create native SQL query with couple of unions and subqueries. It'll look approximately like this:
SELECT res.id, COUNT(*) as count_ids
FROM (
SELECT a.id FROM ... a WHERE ... LIKE ('%:param%')
UNION ALL
SELECT b.id FROM ... b WHERE ... LIKE ('%:param%')
UNION ALL
...
) res
GROUP BY res.id
ORDER BY count_ids asc
Result won't match any Entity I use in my application. Is it possible to create ResultSetMapping with "anonymous" object? Or is it, at least, possible to create an Entity that wouldn't create table next time I update schema, so I can map results to it?
Or is there any other Doctrine-friendly way to deal with such query? Making changes to database isn't possible though, as I'm dealing with legacy stuff that cannot be touched. I'd also strongly prefer if I did everything on database side, not involving much of PHP in it.
Do you have a particular need to map results to a domain object? If not, you could use the DBAL to make a plain old query, which will return an array, as detailed in the Symfony2 cookbook and the Doctrine DBAL documentation:
$conn = $this->container->get('database_connection');
$sql = 'SELECT res.id, COUNT(*)...';
$rows = $conn->query($sql);
Use addScalarResult method of ResultSetMapping
$rsm = new ResultSetMapping();
$rsm->addScalarResult('cnt', 'cnt');
$rsm->addScalarResult('id', 'id');
$query = $this->em->createNativeQuery('SELECT count(*) AS cnt, id_column as id FROM your_table group by id', $rsm);
$result = $query->getResult();
var_dump($result);
Result array:
array (size=1)
0 =>
array (size=2)
'cnt' => int 1
'id' => int 15

Categories