Yii 2 Gridview - Search Query generates ambiguous fields - mysql

I am generating related records search query for Gridview use
I get this error :
SQLSTATE[23000]: Integrity constraint violation: 1052 Column 'dbowner' in where clause is ambiguous
The SQL being executed was: SELECT COUNT(*) FROM tbl_iolcalculation LEFT JOIN tbl_iolcalculation patient ON tbl_iolcalculation.patient_id = patient.id WHERE (dbowner=1) AND (dbowner=1)
I have two related models 1) iolcalculation and patient - each iolcalculation has one patient (iolcalculation.patient_id -> patient.id)
The relevant code in my model IolCalculationSearch is :
public function search($params)
{
$query = IolCalculation::find();
$dataProvider = new ActiveDataProvider([
'query' => $query,
]);
$dataProvider->sort->attributes['patient.lastname'] = [
'asc' => ['patient.lastname' => SORT_ASC],
'desc' => ['patient.lastname' => SORT_DESC],
];
$query->joinWith(['patient'=> function($query) { $query->from(['patient'=>'tbl_iolcalculation']); } ]);
if (!($this->load($params) && $this->validate())) {
return $dataProvider;
}
$query->andFilterWhere([
'id' => $this->id,
'patient_id' => $this->patient_id,
'preop_id' => $this->preop_id,
'calculation_date' => $this->calculation_date,
'iol_calculated' => $this->iol_calculated,
The reason this error is generated is that each model has an override to the default Where clause as follows, the reason being that multiple users data needs to be segregated from other users, by the field dbowner:
public static function defaultWhere($query) {
parent::init();
$session = new Session();
$session->open();
$query->andWhere(['t.dbowner' => $session['dbowner']]);
}
this is defined in a base model extending ActiveRecord, and then all working models extend this base model
How Can I resolve this ambiguous reference in the MySQL code?
Thanks in advance

$query->andFilterWhere([
// previous filters
self::tableName() . '.structure_id' => $this->structure_id,
// next filters
]);

I think, that you are searching for table aliases.
(https://github.com/yiisoft/yii2/issues/2377)
Like this, of course you have to change the rest of your code:
$query->joinWith(['patient'=> function($query) { $query->from(['patient p2'=>'tbl_iolcalculation']); } ]);

The only way I can get this to work is to override the default scope find I had set up for most models, so that it includes the actual table name as follows - in my model definition:
public static function find() {
$session = new Session();
$session->open();
return parent::find()->where(['tbl_iolcalculation.dbowner'=> $session['dbowner']]);
}
There may be a more elegant way using aliases, so any advice would be appreciated - would be nice to add aliases to where clauses, and I saw that they are working on this....

Related

Yii2: Add 'user_id' when create a post

I have a 'post' table with attribute 'user_id' in it to know who have posted that post. I run into a problem, when create a post, the 'user_id' didn't add into database, which can't be null, so I can't continue from there. So how can I add 'user_id' of the user that is currently logging in, automatically.
I'm using Yii2 basic template.
Thanks
Or you could have a look at Blameable Behavior
BlameableBehavior automatically fills the specified attributes with the current user ID.
I use this in alot of my projects (often combined with sluggable and timeable) and its easy to use, just put the following in your Post model:
use yii\behaviors\BlameableBehavior;
public function behaviors()
{
return [
[
'class' => BlameableBehavior::className(),
'createdByAttribute' => 'user_id',
'updatedByAttribute' => false,
'attributes' => [
ActiveRecord::EVENT_BEFORE_VALIDATE => ['user_id'] // If usr_id is required
]
],
];
}
Referencing Behavior validation on validation behaviors.
If you want to do it manually like the other answers suggest, you need to change
if ($model->load(Yii::$app->request->post()) && $model->save()) {
return $this->redirect(['view', 'id' => $model->id]);
}
to
if ($model->load(Yii::$app->request->post()) && $model->validate()) {
$model->user_id = \Yii::$app->user->identity->id
$model->save()
return $this->redirect(['view', 'id' => $model->id]);
}
Remember: when you validate before inputting the user id, the user_id can't be required in your model rules!
Apart from what Bloodhound suggest, you can also use the following code to get the currently logged in user ID:
$loggedInUserId = \Yii::$app->user->getId();
you can try this code
//To get whole logged user data
$user = \Yii::$app->user->identity;
//To get id of the logged user
$userId = \Yii::$app->user->identity->id
Look at the documentation for more details: doc .

how to use relation table when using sqldataprovider

please help I dont know how to get relation table when using sqldataprovider. Anyone understand how to use relation model?
$model = new Finalresult();
$searchModel = new FinalresultSearch();
$dataProvider = $searchModel->search(Yii::$app->request->queryParams);
$dataProvider = new SqlDataProvider([
'sql' => 'SELECT finalresult.bib,
finalresult.series_id,
finalresult.category_id,
GROUP_CONCAT(DISTINCT finalresult.point ORDER BY series.serie_seri_no DESC) AS seriPoint
FROM finalresult, series GROUP BY finalresult.bib',
'key' => 'bib',
]);
I'm trying to get relation table:
'attribute'=>'category_id',
'width'=>'300px',
'value'=>function ($model, $key, $index, $widget) {
return $model->category->category_name;
},
then getting trying to non-object
You can't use relations with SqlDataProvider, because each single result will be presented as array, for example:
[
'id' => 1,
'name' => 'Some name',
'category_id' => 1,
],
For example, you can access category_id as `$model['category_id'].
SqlDataProvider is for very very complex queries, your query can easily be written as ActiveQuery and you can use ActiveDataProvider and get all advantages of that (relations, etc.).
You can find category by id, but it will be lazily loaded that means amount of queries is multiplied by number of rows.
With ActiveDataProvider and relations you can use eager loading and reduce amount of queries. Read more in official docs.
Grid Columns example in documentation
try to change "value" to
'value'=> function($data) {
return $data['category']['category_name'];
}

CakePHP 3: Retrieving deep associations

My model has
Conversations - hasMany - Messages
Conversations - hasMany - ConversationsRecipients
ConversationsRecipients - belongTo - Users or Applicants (depending on the flag set by field recipient_type. If recipient_type is A then it means Applicants)
So when I try to retrieve conversations for a particular Applicant, I use the following code
$conversationsTable = TableRegistry::get('Conversations');
$conversations = $conversationsTable->find()
->join([
'ConversationsRecipients' => [
'table' => 'conversations_recipients',
'type' => 'inner',
'conditions' => ['recipient_id' => $id, 'recipient_type' => 'A']
]
])
->contain([
'Messages.Users' => function ($q) {
return $q
->select(['Users.username'])
->contain(['UsersProfiles']);
},
'Messages.Applicants' => function($q) {
return $q
->select(['Applicants.firstname', 'Applicants.lastname']);
}
])
->all();
return $conversations;
This works fine - except for one part - but it doesn't retrieve the deeply contained model - UsersProfiles. Am I missing something?
Try this:
return $q
->select(['Users.username'])
->autoFields(true)
->contain(['UsersProfiles']);
When you include a select in your query, that's all that Cake will include, unless you include the autoFields call.

CakePHP Model Query - Complex - Multi Table - Sum - Count - Group By

Hello and happy holidays everyone.
Recently I have been tasked with transforming a beta application from pure PHP/jQuery to CakePHP/ExtJS (Which I am new to).
My issue is with the most complex query that populates the main grid.
To keep things simple there are 3 tables with correct baked relationships and models: Projects, ToDo, ExtraToDo
Projects hasMany ToDo and ExtraToDo.
ToDo and ExtraToDo have columns Title and Complete.
My goal is to get a completion percent for each project based on these three tables.
The way I have gone about this is the SUM of the Complete column divided by the COUNT of the Complete column. I am trying in a CakePHP way for readability/performance/otherstuffIdontknowyet.
Originally, in raw SQL I had done it like this:
SELECT
`idProject`,
(SELECT
ROUND((SUM(`Complete`) / COUNT(`Complete`)) * 100),
FROM
(SELECT `Complete`, `ProjectID` FROM `ToDo`
UNION ALL
SELECT `Complete`, `ProjectID` FROM `ExtraToDo`) orders
WHERE
`ProjectID` = `idProject`
) AS 'Completion'
FROM
`Projects`
I also got this to work in the Kohana PHP MVC framework fairly easily which I tried before deciding on CakePHP. I LOOOVED how their queries were created...:
private function get_completion() {
$Query = DB::select('ProjectID', array(DB::expr('ROUND((SUM(`Complete`) / COUNT(`Complete`)) * 100)'), 'Completion'))
->from(array('ToDo', 'ExtraToDo'))
->group_by('ProjectID');
return $Query;
}
public function get_all() {
$Query = DB::select()
->from('Projects')
->join(array(self::get_completion(), 'Completion'))
->on('projects.id', '=', 'Completion.ProjectID')
->execute()
->as_array();
return $Query;
}
Unfortunately I have completely struggled to get this working in CakePHP while doing it the CakePHP way.
I'm pretty sure virtualFields are the key to my answer but after reading the documents and trying x, y, AND z. I have been unable to comprehend them and how they relate.
Thank you in advance
-T6
That is a lot of nested selects. IMO you would be better off building a better query.
This should get you going.
class Project extends AppModel {
public $findMethods = array(
'completion' => true
);
// other code
protected function _findCompletion($state, $query, $results = array()) {
if ($state == 'before') {
$this->virtualFields['total'] = 'ROUND((SUM(Todo.Complete + TodoExtra.Complete) / (COUNT(Todo.Complete) + COUNT(TodoExtra.Complete))) * 100)';
$query['fields'] = array(
$this->alias . '.' . $this->primaryKey,
'total'
);
$query['joins'] = array(
array(
'table' => 'todos',
'alias' => 'Todo',
'type' => 'left',
'foreignKey' => false,
'conditions'=> array('Todo.project_id = ' , $this->alias . '.' . $this->primaryKey)
),
array(
'table' => 'todo_extras',
'alias' => 'TodoExtra',
'type' => 'left',
'foreignKey' => false,
'conditions'=> array('TodoExtra.project_id = ' . $this->alias . '.' . $this->primaryKey)
),
);
$query['group'] = array(
$this->alias . '.' . $this->primaryKey
);
return $query;
}
return $results;
}
// other code
}
Now you have a custom find method that can be used like find('first') or find('all').
From the controller:
$this->Project->find('completion');
Or in the Project model
$this->find('completion');
It should return something like this:
$results = array(
0 => array(
'Project' => array(
'id' => 1,
'total' => 50
)
),
1 => array(
'Project' => array(
'id' => 2,
'total' => 75
)
)
);
I would suggest either creating an afterFind() function to the Project model class, or simply just adding a function that you would call when you need to perform this calculation.
The function to perform the calculation would look like:
getPercentageComplete($project){
{
$total_todos = count($project['ToDo']);
$completed_todos = 0;
foreach($project['ToDo'] as $todo){
if($todo['Complete']) //assuming this is a boolean field
$completed_todos++;
}
return $completed_todos / $total_todos;
}
Then, your afterFind would look something like this:
function afterFind(&$results)
{
foreach ($results as &$project)
{
$project['Project']['percentageComplete'] = $this->Project->getPercentageComplete($project);
}
return $results;
}
You can see more about afterFind() at the CakePHP Bakery - > Callback Methods

Yii Query optimization MySQL

I am not very good with DB queries. And with Yii it's more complicated, since I am not very used to it.
I need to optimize a simple query
$userCalendar = UserCalendar::model()->findByAttributes(array('user_id'=>$user->id));
$unplannedEvents = CalendarEvent::model()->findAllByAttributes(array('calendar_id'=> $userCalendar->calendar_id,'planned'=>0));
CalendarEvent table, i.e the second table from which I need records does not have an user_id but a calendar_id from which I could get user_id from UserCalendar, i.e. the first table hence I created a UserCalendar object which is not a very good way as far as I understand.
Q1. What could I do to make it into one.
Q2. Yii does this all internally but I want to know what query it built to try it seperately in MySQL(phpMyAdmin), is there a way to do that?
Thanks.
Q1: You need to have the relation between UserCalendar and CalendarEvent defined in both of your active record models (in the method "relations").
Based on your comments, it seems like you have the Calendar model that has CalendarEvent models and UserCalendar models.
Lets assume your relations in Calendar are:
relations() {
return array(
'userCalendar' => array(self::HAS_MANY, 'UserCalendar', 'calendar_id'),
'calendarEvent' => array(self::HAS_MANY, 'CalendarEvent', 'calendar_id'),
}
In CalendarEvent:
relations() {
return array( 'calendar' => array(self::BELONGS_TO, 'Calendar', 'calendar_id'), );
}
And in UserCalendar:
relations() {
return array( 'calendar' => array(self::BELONGS_TO, 'Calendar', 'calendar_id'), );
}
So to make the link between UserCalendar and CalendarEvent you'll need to use Calendar
$criteria = new CDbCriteria;
$criteria->with = array(
"calendarEvent"=>array('condition'=>'planned = 0'),
"userCalendar"=>array('condition'=> 'user_id =' . $user->id),
);
$calendar = Calendar::model()->find($criteria);
and $calendar->calendarEvent will return an array of calendarEvent belonging to the user
Q2: you can enable web logging so all the db request (and others stuffs) will appear at the end of your page:
Logging in Yii (see CWebLogging)
In your application configuration put
'components'=>array(
......
'log'=>array(
'class'=>'CLogRouter',
'routes'=>array(
array(
'class'=>'CWebLogRoute',
),
),
),
),