Need Help for build query with activedataprovider - mysql

I want to perform following WHERE condition in yii2 ActiveDataProvider
Expected Where Condition :
$query="WHERE VoterName Like '%s%' AND (contactno != '' OR whatsapp_no!= '')";
My current where Condition:
$query->andFilterWhere(['like', 'VoterName', $this->VoterName]);
$query->orWhere(['<>', 'contactno', ''])->orWhere(['<>', 'whatsapp_no', '']);
I want to fetch only those records who have contactno or whatsapp_no.

When you need to set multi condition, you must use andWhere, for example for your question:
$query->andFilterWhere(['like', 'VoterName', $this->VoterName]);
$query->andWhere(['OR',['<>', 'contactno', ''],['<>', 'whatsapp_no', '']]);

Reference: \yii\db\QueryInterface::where()
Can the following query meet your needs?
$query->andWhere(['like', 'VoterName', $this->VoterName])
->andWhere(['or',
['!=', 'contactno', ''],
['!=', 'whatsapp_no', '']
]);

Related

Multiple Fields with a GroupBy Statement in Laravel

Already received a great answer at this post
Laravel Query using GroupBy with distinct traits
But how can I modify it to include more than just one field. The example uses pluck which can only grab one field.
I have tried to do something like this to add multiple fields to the view as such...
$hats = $hatData->groupBy('style')
->map(function ($item){
return ['colors' => $item->color, 'price' => $item->price,'itemNumber'=>$item->itemNumber];
});
In my initial query for "hatData" I can see the fields are all there but yet I get an error saying that 'colors', (etc.) is not available on this collection instance. I can see the collection looks different than what is obtained from pluck, so it looks like when I need more fields and cant use pluck I have to format the map differently but cant see how. Can anyone explain how I can request multiple fields as well as output them on the view rather than just one field as in the original question? Thanks!
When you use groupBy() of Laravel Illuminate\Support\Collection it gives you a deeper nested arrays/objects, so that you need to do more than one map on the result in order to unveil the real models (or arrays).
I will demo this with an example of a nested collection:
$collect = collect([
collect([
'name' => 'abc',
'age' => 1
]),collect([
'name' => 'cde',
'age' => 5
]),collect([
'name' => 'abcde',
'age' => 2
]),collect([
'name' => 'cde',
'age' => 7
]),
]);
$group = $collect->groupBy('name')->values();
$result = $group->map(function($items, $key){
// here we have uncovered the first level of the group
// $key is the group names which is the key to each group
return $items->map(function ($item){
//This second level opens EACH group (or array) in my case:
return $item['age'];
});
});
The summary is that, you need another loop map(), each() over the main grouped collection.

print last executed query in controller cakephp 3

suppose i have a query in cakephp 3
$post = $this->Posts->get($id, [
'contain' => ['Postmeta']
]);
I want to print it like plain mysql query for example
SELECT * FROM posts....
can anyone please explain this how can i achieve this. Please answer it only in cakephp 3 environment. Is it possible to print it in controller as normal mysql query ? please do not mention queries.log file solution. beacause it is time taking to open file and see the query after every executed query in queries.log file in that is look in cakephp style
Thanks
In Controller, We need to write two lines after query code as follows
$post = $this->Posts->get($id, [
'contain' => ['Postmeta']
]);
echo "<pre>";
print_r(debug($post));die;
It will show all result along with sql query syntax.
Here we are using debug for show result along with sql query.
Query:
$post = $this->Posts->get($id, [
'contain' => ['Postmeta']
]);
For Print/Get SQL Statement of above query you can use debug() function as follow in controller:
$post = $this->Posts->get($id, [
'contain' => ['Postmeta']
]);
debug($post);
Just write:
die(print_r($post));

Use IN clause in updateAll() method in CakePHP 3.x

I am using Cakephp 3.x and i want to update my single field for multiple ids. Something like this..
UPDATE mytable SET `status` = '1' WHERE ID IN (1,2,3)
Currently i am using query to perform this action using cakpehp
$this->Leaveregisters->query()
->update()
->set(['leaveregister_status' => $this->request->data('status')])
->where(['leaveregister_id IN ('.$this->request->data('final_ids_string').')'])
->execute();
Well this does the trick for me but i want this to be performed using cakephp 3.xs' ORM method .. so i am trying to use this instead
$table->updateAll(['field' => $newValue], ['id' => $entityId]);
But this code is for single id only which i do not want.. i also do not want to use foeach loop to perform the same action. Instead i want an ID to be passed via array or comma seperated in any case and want to perform the same action.
Is there any way i can perform the same thing using ORM method using cakephp 3.x
Thanks
usinga updateAll or a query() objects to do a bulk update is the same thing as you can read in the manual at the end of this paragraph
so you can do
$this->Leaveregisters->query()
->update()
->set(['leaveregister_status' => $this->request->data('status')])
->where(['leaveregister_id IN' => [1,2,3])
->execute();
or
$this->Leaveregisters->updateAll(
['leaveregister_status' => $this->request->data('status')]
['leaveregister_id IN' => [1,2,3]);
remember then when usin IN clause you have to pass an array. Read this part of the manual on how to create IN clause
You have to use array datatype for pass in IN CLAUSE
$in_condition= explode(",",$this->request->data('final_ids_string'));
$this->Leaveregisters->query()
->update()
->set(['leaveregister_status' => $this->request->data('status')])
->where(['leaveregister_id IN' => $in_condition])
->execute();
With updateAll() of Model
$table->updateAll(array(
// new values
),
array('id' => array(1,2,3,4,5,6))
);

Yii2 Query not quoting correctly

I run into this problem, time and time again. It would be nice to find out how to build queries properly so I can stop resorting to Yii::$app->db->createCommand() as a workaround.
My Yii2 query:
$users = UserSpatial::find()
->select('user_id, harvesine(y(coordinates), x(coordinates), :lat, :lon) as dist, astext(coordinates)')
->where('st_within(coordinates, envelope(linestring(point(:rlon1, :rlat1), point(:rlon2, :rlat2))))')
->orderBy('st_distance(point(:lon, :lat), coordinates)')
->params([
':lon' => $geo->lon,
':lat' => $geo->lat,
':rlon1' => $rlon1,
':rlat1' => $rlat1,
':rlon2' => $rlon2,
':rlat2' => $rlat2
])
->all();
The generated query ends up with backticks in all the wrong places and, oddly enough, not all parameters were backticked (sorry but you'll need to look closely for the misplaced backticks because I didn't know how best to highlight the incorrect placements):
SELECT \`user_id\`, harvesine(y(coordinates), x(coordinates), \`32.7699547\`, \`-116.9911288)\` AS \`dist\`, astext(coordinates)
FROM \`user_spatial\`
WHERE st_within(coordinates, envelope(linestring(point(-117.07730792871, 32.697490931884), point(-116.90494967129, 32.842418468116))))
ORDER BY st_distance(point(-116.9911288, \`32.7699547)\`, \`coordinates)\`
The query should look like the following as I did not wrap double-square-brackets around any of the fields or values:
SELECT \`user_id\`, harvesine(y(coordinates), x(coordinates), 32.7699547, -116.9911288) AS dist, astext(coordinates)
FROM \`user_spatial\`
WHERE st_within(coordinates, envelope(linestring(point(-117.07730792871, 32.697490931884), point(-116.90494967129, 32.842418468116))))
ORDER BY st_distance(point(-116.9911288, 32.7699547), coordinates)
I can live with Yii2 adding some backticks around field names and table names but why on earth is it backticking numerical values? (FYI: the $rlon and $rlat values don't seem to get backticked but I was assuming that was because they are a result of math calculations!?!?).
I've already tried forcing $geo->lon and $geo->lat to float values like so:
'lon' => (float)$geo->lon;
or
'lon' => (float)$geo->lon * 1;
but it didn't help.
Try to use array format for select and orderBy methods, like docs suggest:
Besides column names, you can also select DB expressions. You must use
the array format when selecting a DB expression that contains commas
to avoid incorrect automatic name quoting. For example,
$query->select(["CONCAT(first_name, ' ', last_name) AS full_name",
'email']);
In you case it will be like this:
$users = UserSpatial::find()
->select([
'user_id',
'harvesine(y(coordinates), x(coordinates), :lat, :lon) as dist',
'astext(coordinates)'
])
->where('st_within(coordinates, envelope(linestring(point(:rlon1, :rlat1), point(:rlon2, :rlat2))))')
->orderBy(['st_distance(point(:lon, :lat)', 'coordinates)'])
->params([
':lon' => $geo->lon,
':lat' => $geo->lat,
':rlon1' => $rlon1,
':rlat1' => $rlat1,
':rlon2' => $rlon2,
':rlat2' => $rlat2
])
->all();

CakePHP: How can I use a "HAVING" operation when building queries with find method?

I'm trying to use the "HAVING" clause in a SQL query using the CakePHP paginate() method.
After some searching around it looks like this can't be achieved through Cake's paginate()/find() methods.
The code I have looks something like this:
$this->paginate = array(
'fields' => $fields,
'conditions' => $conditions,
'recursive' => 1,
'limit' => 10,
'order' => $order,
'group' => 'Venue.id');
One of the $fields is an alias "distance". I want to add a query for when distance < 25 (e.g. HAVING distance < 25).
I have seen two workarounds so far, unfortunately neither suit my needs. The two I've seen are:
1) Adding the HAVING clause in the "group" option. e.g. 'group' => 'Venue.id HAVING distance < 25'. This doesn't seem to work when used in conjunction with pagination as it messes up the initial count query that is performed. (ie tries to SELECT distinct(Venue.id HAVING distance < 25) which is obviously invalid syntax.
2) Adding the HAVING clause after the WHERE condition (e.g. WHERE 1 = 1 HAVING field > 25) This doesn't work as it seems the HAVING clause must come after the group statement which Cake is placing after the WHERE condition in the query it generates.
Does anyone know of a way to do this with CakePHP's find() method? I don't want to use query() as that would involve a lot of rework and also mean I'd need to implement my own pagination logic!
Thanks in advance
You have to put it with the group conditions. like this
$this->find('all', array(
'conditions' => array(
'Post.length >=' => 100
),
'fields' => array(
'Author.id', 'COUNT(*) as Total'
),
'group' => array(
'Total HAVING Total > 10'
)
));
Hope it helps you
I used the following trick to add my own HAVING clause at the end of my WHERE clause. The "dbo->expression()" method is mentioned in the cake sub-query documentation.
function addHaving(array $existingConditions, $havingClause) {
$model = 'User';
$db = $this->$model->getDataSource();
// Two fun things at play here,
// 1 - mysql doesn't allow you to use aliases in WHERE clause
// 2 - Cake doesn't allow a HAVING clause separate from a GROUP BY
// This expression should go last in the WHERE clause (following the last AND)
$taut = count($existingConditions) > 0 ? '1 = 1' : '';
$having = $db->expression("$taut HAVING $havingClause");
$existingConditions[] = $having;
return $existingConditions;
}
As per the manual, CakePHP/2 supports having at last. It was added as find array parameter on version 2.10.0, released on 22nd July 2017.
From the 2.10 Migration Guide:
Model::find() now supports having and lock options that enable you to
add HAVING and FOR UPDATE locking clauses to your find operations.
Just had the same problem. I know, one is not supposed to modify the internal code but if you open the PaginatorComponent and you modify line 188:
$count = $object->find('count', array_merge($parameters, $extra));
to this:
$count = $object->find(
'count',
array_merge(array("fields" => $fields),$parameters, $extra)
);
Everything will be fixed. You will be able to add your HAVING clause to the 'group' and the COUNT(*) won't be a problem.
Or, make line:
$count = $object->paginateCount($conditions, $recursive, $extra);
to include the $fields:
$count = $object->paginateCount($fields,$conditions, $recursive, $extra);
After that, you can "override" the method on the Model and make sure to include the $fields in the find() and that's it!, =P
Here is another idea that doesn't solve the pagination issue, but it is clean since it just overrides the find command in AppModel. Just add a group and having element to your query and this will convert to a HAVING clause.
public function find($type = 'first', $query = array()) {
if (!empty($query['having']) && is_array($query['having']) && !empty($query['group'])) {
if ($type == 'all') {
if (!is_array($query['group'])) {
$query['group'] = array($query['group']);
}
$ds = $this->getDataSource();
$having = $ds->conditions($query['having'], true, false);
$query['group'][count($query['group']) - 1] .= " HAVING $having";
CakeLog::write('debug', 'Model->find: out query=' . print_r($query, true));
} else {
unset($query['having']);
}
}
return parent::find($type, $query);
}
Found it here
https://groups.google.com/forum/?fromgroups=#!topic/tickets-cakephp/EYFxihwb55I
Using 'having' in find did not work for me. Instead I put into one string with the group
" group => product_id, color_id having sum(quantity) > 2000 " and works like a charm.
Using CakePHP 2.9