TL; DR У меня есть запрос, который работает в RAW SQL, но у меня был небольшой успех, воссоздавая его с помощью построителя запросов или активной записи.
Я работаю над веб-приложением, основанным на шаблоне расширенного приложения yii2. Я написал запрос базы данных и внедрил его с помощью findbysql (), возвращающего правильные записи, но у меня возникли проблемы с переводом этого в активную запись.
Я изначально хотел разрешить пользователю изменять (фильтровать) результаты с помощью формы поиска (user & date), однако я понял, что реализация фильтров в gridview с активными записями будет более плавной.
У меня есть простые запросы к работе, но я не уверен, как реализовать его с помощью этого множества объединений. Во многих примерах использовались подзапросы, но мои попытки не возвращали никаких записей вообще. Я подумал, прежде чем пытаться фильтровать, мне нужно сначала расшифровать этот запрос.
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, ]); }