How to transform raw SQL into Yii2 like find query - mysql

I can't convert raw SQL query in to Yii2 like method. I'd like to implement grid view from my RAW sql with filtering and sorting. I'm using ActiveDataProvider with method in the ModelSearch as Yii default way.
I did try to use Model::findBySql but it is not letting me filter or sort my results in the grid view. I don't want to useSQLDataProvider because I have relations in my queries.
I see that changing Model::FindBySql($sql) to Model::find is letting me sort and filter but the results are not as expected. I have to transform this SQL to use Model::Find() method
My sql I struggle to change is
$sql = 'SELECT A.*, (6371 * acos(cos(radians("'.$mapSearch->gps_lat.'")) * cos(radians(gps_lat))*cos(radians(gps_long)-radians("'.$mapSearch->gps_long.'"))+sin(radians("'.$mapSearch->gps_lat.'"))*sin(radians(gps_lat)))) AS distance FROM address A JOIN contest_has_address CA On A.id = CA.address_id JOIN contest C On C.id = CA.contest_id JOIN contest_has_date CD On C.id = CD.contest_id JOIN date D On D.id = CD.date_id WHERE main = 1 AND C.status = 1 AND D.start_time > "'.$today.'" HAVING distance < "'.$mapSearch->distance.'" ORDER BY distance ASC';
my Controller:
if($mapSearch->save(false)) {
$lat = $mapSearch->gps_lat;
$long = $mapSearch->gps_long;
$sql = 'SELECT A.*, (6371 * acos(cos(radians("'.$mapSearch->gps_lat.'")) * cos(radians(gps_lat))*cos(radians(gps_long)- radians("'.$mapSearch->gps_long.'"))+sin(radians("'.$mapSearch->gps_lat.'") )*sin(radians(gps_lat)))) AS distance FROM address A JOIN contest_has_address CA On A.id = CA.address_id JOIN contest C On C.id = CA.contest_id JOIN contest_has_date CD On C.id = CD.contest_id JOIN date D On D.id = CD.date_id WHERE main = 1 AND C.status = 1 AND D.start_time > "'.$today.'" HAVING distance < "'.$mapSearch->distance.'" ORDER BY distance ASC';
$models = Address::findBySql($sql)->all();
$count = Yii::$app->db->createCommand($sql)->queryScalar();
$dataProvider = $searchModel->searchMapAddress(Yii::$app->request->queryParams, $sql);
return $this->render('map', [
'sql'=>$sql,
'searchModel'=>$searchModel,
'models'=>$models,
'dataProvider'=>$dataProvider,
'mapSearch'=>$mapSearch,
'lat'=>$mapSearch->gps_lat,
'long'=>$mapSearch->gps_long,
]);
My Model
$query = Address::findBySql($sql);
$query->joinWith(['contest']);
$dataProvider = new ActiveDataProvider([
'query' => $query,
]);
and view:
echo GridView::widget([
'dataProvider' => $dataProvider,
'filterModel' => $searchModel,
'layout'=> '{items}',

Assuming that your raw SQL query is working correctly you can use ActiveRecord or Query Builder to create your query.
For using MYSQL functions inside the query you must use \yii\db\Expresion, and while building the query you should use ->createCommand()->rawSQL at the end of the query replacing with ->one() or ->all(), and echo the query to see what the RAW SQL query is built and compare it with the original query.
You can use the following query:
$query=Address::find()->alias('A')
->select([new Expression('A.*, (6371 * acos(cos(radians("' . $mapSearch->gps_lat . '")) * cos(radians(gps_lat))*cos(radians(gps_long) -radians("' . $mapSearch->gps_long . '")) + sin(radians("' . $mapSearch->gps_lat . '"))*sin(radians(gps_lat)))) AS distance')])
->join('left join', '{{content_has_address}} CA', 'A.id = CA.address_id')
->join('left join', '{{contest}} C', 'C.id = CA.contest_id')
->join('left join', '{{contest_has_date}} CD', 'C.id = CD.contest_id')
->join('left join', '{{date}} D', 'D.id = CD.date_id')
->where(
['AND',
['=', 'main', 1],
['=', 'C.status', 1],
['>', 'D.start_time', $today]
]
)
->having(['<', 'distance', $mapSearch->distance])
->orderBy('distance asc');
$dataProvider = new ActiveDataProvider([
'query' => $query,
]);

Related

Yii2 Left Join in query builder

I have two tables, the report_details and the name. In the report_details table, I have for and from which is an id that is related to the table name. What is the proper syntax in Yii2 to get both the for and from the column on the name table? This is my query so far...
$query = new yii\db\Query;
$query->select('report_details.reference_no, report_details.subject, report_details.doc_for, report_details.doc_from, report_details.doc_date, report_details.doc_name, report_details.drawer_id, report_details.user_id, name.name_id, name.position, name.fname, name.mname, name.lname')
->from('report_details')
->join('LEFT JOIN', 'name', 'report_details.doc_for = name.name_id')
->where(['report_details.reference_no' => $model->reference_no]);
$results = $query->all();
you could use ->leftJoin( )
$query = new yii\db\Query;
$query->select('report_details.reference_no, report_details.subject,
report_details.doc_for, report_details.doc_from,
report_details.doc_date, report_details.doc_name,
report_details.drawer_id, report_details.user_id,
name.name_id, name.position, name.fname, name.mname, name.lname')
->from('report_details')
->leftJoin( 'name', 'report_details.doc_for = name.name_id')
->where(['report_details.reference_no' => $model->reference_no]);
$results = $query->all();

WordPress Custom Query for Taxonomy

I have to edit somebody's code and I am not familiar with querying posts using the method they are. I have tried editing the query to various alternatives, however I am having no luck. I want to change the following query to select data from a custom taxonomy called 'job_cat' where the value equals a POST value. The code is below:
$querystr = "
SELECT DISTINCT wposts.*
FROM $wpdb->posts wposts
LEFT JOIN $wpdb->postmeta wpostmeta ON wposts.ID = wpostmeta.post_id
LEFT JOIN $wpdb->postmeta wpostmeta2 ON wposts.ID = wpostmeta2.post_id
LEFT JOIN $wpdb->term_relationships ON (wposts.ID = $wpdb->term_relationships.object_id)
LEFT JOIN $wpdb->term_taxonomy ON ($wpdb->term_relationships.term_taxonomy_id = $wpdb->term_taxonomy.term_taxonomy_id)
WHERE wp_term_taxonomy.term_id = 94";
// If a job type is set and not equal to nothing or any jobs
if(isset($_POST['search-location']) && $_POST['search-location'] !== "" && $_POST['search-location'] !== "All") {
$querystr .= " AND (wpostmeta2.meta_key = 'region' AND wpostmeta2.meta_value = '".$_POST['search-location']."')";
}
$querystr .= " ORDER BY post_date DESC LIMIT 50";
//Execute
//echo $querystr;
$pageposts = $wpdb->get_results($wpdb->prepare($querystr, OBJECT));
Any help would be great!!
This code is extremely dangerous to use in a production WordPress site. You need to remove it and any other template file larded up with SQL queries like this.
Instead, use WP_Query which automatically sanitizes your SQL requests. It has a huge number of parameters you can set to query anything from WordPress. Here is an example using WP_Query to get posts by a given taxonomy, with tax_query.
<?php
// 1- Setup an argument to give to WP_Query
$taxonomy_args = array(
// 2- use the tax_query to examine a taxonomy
'tax_query' => array(
array(
'taxonomy' => 'job_cat',
'terms' => 'XYZ',
'field' => 'slug',
'include_children' => true,
'operator' => 'IN'
)
),
// 3- Make sure to define how many posts and what post-type they should be
'posts_per_page' => 50,
'post_type' => 'search-location',
);
$s = new WP_Query( $some_args );
if ( $s->have_posts() ) : $s->the_post();
// 4- Here you will manipulate the data you get back
endif;
?>

Symfony2 Doctrine error: Cannot count query that uses a HAVING clause. Use the output walkers for pagination

I am trying to get collections that are non-empty, i.e. have at least 1 object. Collection entity has OneToMany relationship with Object entity. I am using KNP paginator to paginate result. This is my function:
public function fetchAction(Request $request){
$em = $this->getDoctrine()->getManager();
$page = $request->get('page', 1);
$limit = 10;
$collections = $em->createQueryBuilder()
->select('c')
->add('from', 'CollectionBundle:Collection c LEFT JOIN c.object o')
->having('COUNT(o.id)>0')
->orderBy('c.date', 'DESC')
->getQuery();
$collections = $this->get("knp_paginator")->paginate($collections, $page, $limit);
return $this->render('CollectionBundle:Collection:fetch.html.twig', [
'collections' => $collections
]);
}
Error
I keep getting following error
Cannot count query that uses a HAVING clause. Use the output walkers for pagination
Without 'Having' clause everything works fine, but I must get non-empty collections.
wrap-queries solved this problem
$collections = $this->get("knp_paginator")->paginate($collections, $page, $limit,array('wrap-queries'=>true));
You can implement the Manual counting, as described here in the doc.
As example, you can modify your code as follow:
$count = $em->createQueryBuilder()
->select('COUNT(c)')
->add('from', 'CollectionBundle:Collection c LEFT JOIN c.object o')
->having('COUNT(o.id)>0')
->orderBy('c.date', 'DESC')
getSingleScalarResult();
$collections = $em->createQueryBuilder()
->select('c')
->add('from', 'CollectionBundle:Collection c LEFT JOIN c.object o')
->having('COUNT(o.id)>0')
->orderBy('c.date', 'DESC')
->getQuery();
$collections->setHint('knp_paginator.count', $count);
$collections = $this->get("knp_paginator")->paginate($collections, $page, $limit,array('distinct' => false));
return $this->render('CollectionBundle:Collection:fetch.html.twig', [
'collections' => $collections
]);
Hope this help
My solution is based on #Matteo's solution, since my query was a bit complicated I wanted to share my version also:
$qb = $this->createQueryBuilder('c');
$qb->select('count(c.id)')
->addSelect('COUNT(DISTINCT m.id) AS HIDDEN messageCount')
->addSelect('COUNT(DISTINCT f.id) AS HIDDEN fileCount')
->join('c.user', 'u')
->join('c.status', 's')
->join('c.company', 'comp')
->leftJoin('c.files', 'f')
->leftJoin('c.messages', 'm');
$this->_set_filters($filter, $qb);
$qb->groupBy('c.id');
$countQuery = $qb->getQuery();
/** wrap query with SELECT COUNT(*) FROM ($sql)
* I don't know what exactly does this block but
* I coppied it from Doctrine\ORM\Tools\Pagination\Paginator::getCountQuery()
*/
$platform = $this->getEntityManager()->getConnection()->getDatabasePlatform();
$rsm = new Query\ResultSetMapping();
$rsm->addScalarResult($platform->getSQLResultCasing('dctrn_count'), 'count');
$countQuery->setHint(Query::HINT_CUSTOM_OUTPUT_WALKER, CountOutputWalker::class);
$countQuery->setResultSetMapping($rsm);
return $countQuery->getSingleScalarResult(); //returns integer

Doctrine2 LEFT JOIN with 2 conditions

I'm trying to find a 'Product' by ID, and to left join all it's 'Photo' on two conditions: the locale AND the active state.
Here's my QueryBuilder :
$queryBuilder = $this->createQueryBuilder('p')
->select('p, photos, photoTranslation')
->leftJoin('p.photos', 'photos')
->leftJoin('photos.translations', 'photoTranslation')
->where('p.id = :id')
->andWhere('(photoTranslation.locale = :locale OR photoTranslation.locale IS NULL)')
->andWhere('(photoTranslation.active = :active OR photoTranslation.active IS NULL)')
->setParameters(array(
'id' => $id
'locale' => $this->getLocale(),
'active' => true
));
It works fine when there are no Photos or when there are ACTIVE photos, but not when there's an inactive Photo because it doesn't match one of the two conditions.
If I use only one condition, for instance only the locale part, it works fine :
$queryBuilder = $this->createQueryBuilder('p')
->select('p, photos, photoTranslation')
->leftJoin('p.photos', 'photos')
->leftJoin('photos.translations', 'photoTranslation')
->where('p.id = :id')
->andWhere('(photoTranslation.locale = :locale OR photoTranslation.locale IS NULL)')
->setParameters(array(
'id' => $id
'locale' => $this->getLocale()
));
For now, I loop on theses results and unset all inactive Photos... but I'd like a clean way to do in the QueryBuilder.
I also tried to put the conditions on the LEFT JOIN clause :
->leftJoin('photo.translations', 'phototTranslation', Doctrine\ORM\Query\Expr\JOIN::WITH, 'photoTranslation.locale = :locale AND photoTranslation.active = :active')
But it always returns the Photo, even if it's inactive.
For this problem a solution may be:
$em = $this->getEntityManager();
$qb = $em->createQueryBuilder();
$qb
->select('p', 'pp')
->from('Product', 'p')
->leftJoin('p.photos', 'pp')
->leftJoin('pp.translations', 'ppt', Doctrine\ORM\Query\Expr\Join::WITH, $qb->expr()->andX(
$qb->expr()->eq('ppt.locale', ':locale'),
$qb->expr()->eq('ppt.active', ':active')
))
->where('p.id', ':productId')
->setParameters(
array(
'productId', $productId,
'active', $active,
'locale', $locale
)
);
$query = $qb->getQuery();
return $query->getResult(); // or ->getSingleResult();
NOTE: this example is the way to do it in Symfony2 (2.3) entity repository

How to run raw SQL Query with Zend Framework 2

Is there a way to execute a SQL String as a query in Zend Framework 2?
I have a string like that:
$sql = "SELECT * FROM testTable WHERE myColumn = 5"
now I want to execute this string directly.
Just pass the sql string to your db adapter like this:
$resultSet = $adapter->query($sql, \Zend\Db\Adapter\Adapter::QUERY_MODE_EXECUTE);
And if you want to pass parameters:
$sql = "SELECT * FROM testTable WHERE myColumn = ?";
$resultSet = $adapter->query($sql, array(5));
EDIT: Please note that the query method does not always returns a resultset. When its a resultset producing query(SELECT) it returns a \Zend\Db\ResultSet\ResultSet otherwise(INSERT, UPDATE, DELETE, ...) it will return a \Zend\Db\Adapter\Driver\ResultInterface.
And when you leave the second Parameter empty you will get a \Zend\Db\Adapter\Driver\StatementInterface which you can execute.
use Zend\Db\Sql\Sql;
use Zend\Db\Adapter\Adapter;
$dbAdapterConfig = array(
'driver' => 'Mysqli',
'database' => 'dbname',
'username' => 'dbusername',
'password' => 'dbuserpassword'
);
$dbAdapter = new Adapter($dbAdapterConfig);
$sql = new Sql($dbAdapter);
$select = $sql->select();
$select->from('testTable');
$select->where(array('myColumn' => 5));
$statement = $sql->prepareStatementForSqlObject($select);
$result = $statement->execute();
S. docu: Zend\Db → Zend\Db\Sql
If you are using tableGateway, you can run your raw SQL query using this statement,
$this->tableGateway->getAdapter()->driver->getConnection()->execute($sql);
where $sql pertains to your raw query. This can be useful for queries that do not have native ZF2 counterpart like TRUNCATE / INSERT SELECT statements.
If you have EntityManager $em on your hands, you can do something like this:
$select = $em->getConnection()->executeQuery("
SELECT a.id, a.title, a.announcement, asvc.service_id, COUNT(*) AS cnt,
GROUP_CONCAT(asvc.service_id SEPARATOR \", \") AS svc_ids
FROM article AS a
JOIN articles_services AS asvc ON asvc.article_id = a.id
WHERE
asvc.service_id IN (
SELECT tsvc.service_id
FROM tender AS t
JOIN tenders_services AS tsvc ON tsvc.tender_id = t.id
WHERE t.id = :tenderId
)
GROUP BY a.id
ORDER BY cnt DESC, a.id DESC
LIMIT :articlesCount
", [
'articlesCount' => 5,
'tenderId' => $tenderId,
], [
'articlesCount' => \PDO::PARAM_INT,
]);
$result = $select->fetchAll(); // <-- here are array of wanted rows
I think this way to execute complex queries is best for Zend. But may be I'm not very smart in Zend still. Will glad to see if it helps to someone.