Symfony queryBuilder, can`t rewrite sql to queryBuidler - php

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();
}

Related

Yii2 query with all() returns less data than expected

I want to find all data from database with this query:
$dataSearch = House::find()
->select(["number","DATE(reg_date) AS date","((value) > 100) AS result","info"])
->join("INNER JOIN", Owner::tableName(), Owner::tableName().'.number_ow= '.House::tableName().'.number_ow')
->andWhere([House::tableName().'.number_ow'=>$house->number_ow])
->andFilterWhere([House::tableName().'.space'=>$space])
->orderBy([House::tableName().'.reg_date'=>SORT_DESC,'info'=>SORT_ASC]);
$data = $dataSearch->asArray()->all();
If I perform the query with all() return only a few part of the results, but if I perform the raw SQL query on the database, I get all the results. I don't understand where the problem is.
This is raw SQL:
SELECT `number`, DATE(reg_date) AS date, ((value) > 100) AS result, `info`
FROM `ho_house` INNER JOIN `ho_owner` ON ho_owner.number_ow = oh_ho_house.number_ow
WHERE (`ho_house`.`number_ow`=2100174106) AND (`ho_house`.`space`='m')
ORDER BY `ho_house`.`reg_date` DESC, `info`
That is because ActiveQuery tries to remove duplicated models from results of query with JOIN (for example if house have 2 owners, house data will be repeated twice in results set). In your case, if you want to raw data from database, you should use Query:
$dataSearch = (new Query())
->select(["number","DATE(reg_date) AS date","((value) > 100) AS result","info"])
->from(House::tableName())
->join("INNER JOIN", Owner::tableName(), Owner::tableName().'.number_ow= '.House::tableName().'.number_ow')
->andWhere([House::tableName().'.number_ow'=>$house->number_ow])
->andFilterWhere([House::tableName().'.space'=>$space])
->orderBy([House::tableName().'.reg_date'=>SORT_DESC,'info'=>SORT_ASC]);
$data = $dataSearch->all();
But if you really need ActiveQuery, you should configure $indexBy to return unique ID for every row.
Replace andWhere with Where Check this code
$dataSearch = House::find()
->select(["number","DATE(reg_date) AS date","((value) > 100) AS result","info"])
->join("INNER JOIN", Owner::tableName(), Owner::tableName().'.number_ow= '.House::tableName().'.number_ow')
->where([House::tableName().'.number_ow'=>$house->number_ow])
->andFilterWhere([House::tableName().'.space'=>$space])
->orderBy([ClosedOperation::tableName().'.reg_date'=>SORT_DESC,'info'=>SORT_ASC]);
$data = $dataSearch->asArray()->all();

Cross Join with php codeigniter query

Is there away to cross join from php. In example:
Currently I query a database like so:
$company_id = 20;
$templates_data = $this->db->select('template_id')
->from('dr_template_relational')
->where('dr_template_relational.company_id',$company_id)
->get()
->result_array();
What I'm looking to do is something like this:
->from('dr_template_relational')
->cross_join()
There's several responses to this question on SO but post reference a regular sql query like so:
"SELECT * FROM citys LEFT JOIN comments ON comments.city=citys.city WHERE citys.id=$id";
This would be the way to do it in SQL query but the point here is to do it in php and get the data returned with a cross join. I also realize the query can be made with in php to have it select the data and join it with code but my question is related to is there away to simply add ->cross_join() or something like that.
You can run raw query in codeigniter to solve your problem as below:
$sql 'your query here with cross join';
$query = $this->db->query($sql);
return $query->result_array();
Hope it helps you :)
You can use CI join method.
$company_id = 20;
$templates_data = $this->db->select('dr_template_relational.template_id')
->where('dr_template_relational.company_id',$company_id)
->join('table','dr_template_relational.company_id=table.company_id','LEFT')
->get()
->result_array();
where 'LEFT' is the join type

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

Join subquery with doctrine 2 DBAL

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

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