Intereting Posts
remove_action Из класса PHP в членствах WooCommerce CakePHP 3.0.8 переводит поведение и проверку данных (requirePresence, notEmpty) Вызовите метод в приложении Console, которое выполняется в настоящий момент. Как передать параметры шаблону PHP, представленному с помощью 'include'? API Facebook получает идентификатор Фан-страницы внутри страницы PHP: вывод файла iCalendar как обычного текста MySQL получает строку (ы) между двумя # / несколькими парами # конвертировать utf8mb4 символов в utf8 в php Время безотказной работы сервера Сообщите Apache, чтобы использовать определенную версию PHP, установленную с использованием phpbrew Контактная форма подтверждения без обновления Регулярное выражение для выражения конца строки Значение в базе данных «участники» показывает ссылку, когда пользователь является администратором непредвиденное сообщение об ошибке в php-форме (синтаксическая ошибка SQL) пользовательский канал регистрации монолога в команде symfony2

Комплексные запросы к базе данных в yii2 с активной записью

TL; DR У меня есть запрос, который работает в RAW SQL, но у меня был небольшой успех, воссоздавая его с помощью построителя запросов или активной записи.


Я работаю над веб-приложением, основанным на шаблоне расширенного приложения yii2. Я написал запрос базы данных и внедрил его с помощью findbysql (), возвращающего правильные записи, но у меня возникли проблемы с переводом этого в активную запись.

Я изначально хотел разрешить пользователю изменять (фильтровать) результаты с помощью формы поиска (user & date), однако я понял, что реализация фильтров в gridview с активными записями будет более плавной.

У меня есть простые запросы к работе, но я не уверен, как реализовать его с помощью этого множества объединений. Во многих примерах использовались подзапросы, но мои попытки не возвращали никаких записей вообще. Я подумал, прежде чем пытаться фильтровать, мне нужно сначала расшифровать этот запрос.

videoController.php

public function actionIndex() { $sql = 'SELECT videos.idvideo, videos.filelocation, events.event_type, events.event_timestamp FROM (((ispy.videos videos INNER JOIN ispy.cameras cameras ON (videos.cameras_idcameras = cameras.idcameras)) INNER JOIN ispy.host_machines host_machines ON (cameras.host_machines_idhost_machines = host_machines.idhost_machines)) INNER JOIN ispy.events events ON (events.host_machines_idhost_machines = host_machines.idhost_machines)) INNER JOIN ispy.staff staff ON (events.staff_idreceptionist = staff.idreceptionist) WHERE (staff.idreceptionist = 182) AND (events.event_type IN (23, 24)) AND (events.event_timestamp BETWEEN videos.start_time AND videos.end_time)'; $query = Videos::findBySql($sql); $dataProvider = new ActiveDataProvider([ 'query' => $query, ]); return $this->render('index', [ 'dataProvider' => $dataProvider, ]); } 

Неудачная попытка

 public function actionIndex() { $query = Videos::find() ->innerJoin('cameras', 'videos.cameras_idcameras = cameras.idcameras') ->innerJoin('host_machines', 'cameras.host_machines_idhost_machines = host_machines.idhost_machines') ->innerJoin('events', 'events.host_machines_idhost_machines = host_machines.idhost_machines') ->innerJoin('staff', 'events.staff_idreceptionist = staff.idreceptionist') ->where('staff.idreceptionist = 182') ->andWhere(['events.event_type' => [23,24]]) ->andwhere(['between', 'events.event_timestamp', 'videos.start_time', 'videos.end_time']); $dataProvider = new ActiveDataProvider([ 'query' => $query, ]); return $this->render('index', [ 'dataProvider' => $dataProvider, ]); } 

Часть обзора

 <?= GridView::widget([ 'dataProvider' => $dataProvider, 'columns' => [ ['class' => 'yii\grid\SerialColumn'], 'idvideo', 'event_type', 'event_timestamp', 'filelocation', //['class' => 'yii\grid\ActionColumn'], ], ]); ?> 

Пожалуйста, дайте мне знать, если мне нужно быть более конкретным или включать любую дополнительную информацию.


Вперед

я буду предполагать, исходя из вопроса, который вы задали здесь, вам понравилось в комментариях, что вы предоставили весь запрос (никаких других полей, которые вы достали, чтобы показать пример кода)

поэтому, если вам нужны только поля, указанные в SELECT , вы можете немного оптимизировать свой запрос:

во-первых, вы присоединяетесь к host_machines только для связи cameras и events , но для них есть один и тот же ключ host_machines_idhost_machines , так что это не нужно, вы можете напрямую:

  INNER JOIN events events ON (events.host_machines_idhost_machines = cameras.host_machines_idhost_machines)) 

во-вторых, соединение с ispy.staff , единственным используемым полем является idreceptionist в WHERE , это поле существует и в events поэтому мы можем полностью его удалить

окончательный запрос здесь:

 SELECT videos.idvideo, videos.filelocation, events.event_type, events.event_timestamp FROM videos videos INNER JOIN cameras cameras ON videos.cameras_idcameras = cameras.idcameras INNER JOIN events events ON events.host_machines_idhost_machines = cameras.host_machines_idhost_machines WHERE (events.staff_idreceptionist = 182) AND (events.event_type IN (23, 24)) AND (events.event_timestamp BETWEEN videos.start_time AND videos.end_time) 

должен выводить те же записи, что и в вашем вопросе, без каких-либо идентификационных строк
некоторые дубликаты видео по-прежнему будут существовать из-за одного отношения между cameras и events


теперь к стороне yii вещей,
вы должны определить некоторые отношения на модели видео

 // this is pretty straight forward, `videos`.`cameras_idcameras` links to a // single camera (one-to-one) public function getCamera(){ return $this->hasOne(Camera::className(), ['idcameras' => 'cameras_idcameras']); } // link the events table using `cameras` as a pivot table (one-to-many) public function getEvents(){ return $this->hasMany(Event::className(), [ // host machine of event => host machine of camera (from via call) 'host_machines_idhost_machines' => 'host_machines_idhost_machines' ])->via('camera'); } 

VideoController и сама функция поиска

 public function actionIndex() { // this will be the query used to create the ActiveDataProvider $query =Video::find() ->joinWith(['camera', 'events'], true, 'INNER JOIN') ->where(['event_type' => [23, 24], 'staff_idreceptionist' => 182]) ->andWhere('event_timestamp BETWEEN videos.start_time AND videos.end_time'); $dataProvider = new ActiveDataProvider([ 'query' => $query, ]); return $this->render('index', [ 'dataProvider' => $dataProvider, ]); } 

yii будет обрабатывать каждое видео как отдельную запись (на основе pk), что означает, что все видео дубликаты удалены. у вас будет одно видео, каждое из которых имеет несколько событий, поэтому вы не сможете использовать 'event_type' и 'event_timestamp' в представлении, но вы можете объявить некоторые геттеры внутри модели видео, чтобы показать эту информацию:

 public function getEventTypes(){ return implode(', ', ArrayHelper::getColumn($this->events, 'event_type')); } public function getEventTimestamps(){ return implode(', ', ArrayHelper::getColumn($this->events, 'event_timestamp')); } 

и использование вида:

 <?= GridView::widget([ 'dataProvider' => $dataProvider, 'columns' => [ ['class' => 'yii\grid\SerialColumn'], 'idvideo', 'eventTypes', 'eventTimestamps', 'filelocation', //['class' => 'yii\grid\ActionColumn'], ], ]); ?> 

Редактировать :
если вы хотите сохранить дубликаты видео, объявите два столбца из events внутри видео модели

 public $event_type, $event_timestamp; 

сохраните первоначальную настройку GridView и добавьте select и indexBy к запросу внутри VideoController :

 $q = Video::find() // spcify fields ->addSelect(['videos.idvideo', 'videos.filelocation', 'events.event_type', 'events.event_timestamp']) ->joinWith(['camera', 'events'], true, 'INNER JOIN') ->where(['event_type' => [23, 24], 'staff_idreceptionist' => 182]) ->andWhere('event_timestamp BETWEEN videos.start_time AND videos.end_time') // force yii to treat each row as distinct ->indexBy(function () { static $count; return ($count++); }); 

Обновить

прямое отношение staff к Video в настоящее время несколько проблематично, так как это более чем одна таблица от него. здесь есть проблема.

однако вы добавляете таблицу сотрудников, привязывая ее к модели Event ,

 public function getStaff() { return $this->hasOne(Staff::className(), ['idreceptionist' => 'staff_idreceptionist']); } 

что позволит вам запросить следующее:

 ->joinWith(['camera', 'events', 'events.staff'], true, 'INNER JOIN') 

Фильтрация потребует небольших обновлений на контроллере, представлении и SarchModel
вот минимальная реализация:

 class VideoSearch extends Video { public $eventType; public $eventTimestamp; public $username; public function rules() { return array_merge(parent::rules(), [ [['eventType', 'eventTimestamp', 'username'], 'safe'] ]); } public function search($params) { // add/adjust only conditions that ALWAYS apply here: $q = parent::find() ->joinWith(['camera', 'events', 'events.staff'], true, 'INNER JOIN') ->where([ 'event_type' => [23, 24], // 'staff_idreceptionist' => 182 // im guessing this would be the username we want to filter by ]) ->andWhere('event_timestamp BETWEEN videos.start_time AND videos.end_time'); $dataProvider = new ActiveDataProvider(['query' => $q]); if (!$this->validate()) return $dataProvider; $this->load($params); $q->andFilterWhere([ 'idvideo' => $this->idvideo, 'events.event_type' => $this->eventType, 'events.event_timestamp' => $this->eventTimestamp, 'staff.username' => $this->username, ]); return $dataProvider; } } 

контроллер:

 public function actionIndex() { $searchModel = new VideoSearch(); $dataProvider = $searchModel->search(Yii::$app->request->queryParams); return $this->render('test', [ 'searchModel' => $searchModel, 'dataProvider' => $dataProvider, ]); } 

и вид

 use yii\grid\GridView; use yii\helpers\ArrayHelper; echo GridView::widget([ 'dataProvider' => $dataProvider, 'filterModel' => $searchModel, 'columns' => [ ['class' => 'yii\grid\SerialColumn'], 'idvideo', 'filelocation', [ 'attribute' => 'eventType', // from VideoSearch::$eventType (this is the one you filter by) 'value' => 'eventTypes' // from Video::getEventTypes() that i suggested yesterday // in hindsight, this could have been named better, like Video::formatEventTypes or smth ], [ 'attribute' => 'eventTimestamp', 'value' => 'eventTimestamps' ], [ 'attribute' => 'username', 'value' => function($video){ return implode(', ', ArrayHelper::map($video->events, 'idevent', 'staff.username')); } ], //['class' => 'yii\grid\ActionColumn'], ], ]); 

Моя рекомендация состояла бы в том, чтобы иметь 2 запроса. Первый, чтобы получить идентификаторы видео, которые соответствуют вашему поиску, второй запрос theone, который использует эти идентификаторы и передает ваш $dataProvider .

 use yii\helpers\ArrayHelper; ... public function actionIndex() { // This is basically the same query you had before $searchResults = Videos::find() // change 'id' for the name of your primary key ->select('id') // we don't really need ActiveRecord instances, better use array ->asArray() ->innerJoin('cameras', 'videos.cameras_idcameras = cameras.idcameras') ->innerJoin('host_machines', 'cameras.host_machines_idhost_machines = host_machines.idhost_machines') ->innerJoin('events', 'events.host_machines_idhost_machines = host_machines.idhost_machines') ->innerJoin('staff', 'events.staff_idreceptionist = staff.idreceptionist') ->where('staff.idreceptionist = 182') ->andWhere(['events.event_type' => [23,24]]) ->andwhere(['between', 'events.event_timestamp', 'videos.start_time', 'videos.end_time']) // query the results ->all(); // this will be the query used to create the ActiveDataProvider $query = Videos::find() // and we use the results of the previous query to filter this one ->where(['id' => ArrayHelper::getColumn($searchResults, 'id')]); $dataProvider = new ActiveDataProvider([ 'query' => $query, ]); return $this->render('index', [ 'dataProvider' => $dataProvider, ]); }