sql >> データベース >  >> RDS >> Mysql

ActiveRecordを使用したyii2の複雑なデータベースクエリ

    こちら での質問に基づいて想定します クエリ全体を提供したというコメントが気に入りました (サンプルコードを表示するためだけに取り出した他のフィールドはありません)

    したがって、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間の1対多の関係により、一部のビデオの重複がまだ存在します および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から2つの列を宣言します 動画の内部 モデル

    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との関係 現在、テーブルから1つ以上離れているため、多少問題があります。ここに問題があります。

    ただし、staffを追加します テーブルをイベントにリンクして モデル、

    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'],
        ],
    ]);
    


    1. クエリをMySqlからSqliteに変換します

    2. PostgreSQL(psql)でヌル出力の現在の設定を表示する方法

    3. Apache Sparkに火をつける–パート2

    4. 更新部分で新しい値と古い値の両方を使用できるようにUPSERTを実行する方法