Mysql
 sql >> Datenbank >  >> RDS >> Mysql

Komplexe Datenbankabfragen in yii2 mit Active Record

Ich nehme an, basierend auf der Frage, die Sie hier gestellt haben Ihnen in Kommentaren gefallen hat, dass Sie die gesamte Suchanfrage gestellt haben (keine anderen Felder, die Sie herausgenommen haben, nur um Beispielcode zu zeigen)

wenn Sie also nur die in SELECT angegebenen Felder benötigen -Anweisung können Sie Ihre Abfrage ziemlich optimieren:

Zunächst einmal treten Sie mit host_machines bei nur um cameras zu verknüpfen und events , haben aber denselben Schlüssel host_machines_idhost_machines auf beiden, so dass das nicht benötigt wird, können Sie direkt:

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

zweitens der Join mit ispy.staff , das einzige verwendete Feld ist idreceptionist in WHERE -Klausel existiert dieses Feld in events auch, damit wir es komplett fallen lassen können

die letzte Abfrage hier:

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)

sollte die gleichen Aufzeichnungen wie die in Ihrer Frage ausgeben, ohne identische Zeilen
einige Videoduplikate werden aufgrund einer Eins-zu-viele-Beziehung zwischen cameras immer noch vorhanden sein und events

Nun zur Yii-Seite der Dinge,
Sie müssen einige Beziehungen zu den Videos definieren Modell

// 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');
}

der VideoController und die Suchfunktion selbst

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 behandelt jedes Video als einen einzigen Datensatz (basierend auf pk), das bedeutet, dass alle Videoduplikate entfernt werden. Sie haben einzelne Videos mit jeweils mehreren Ereignissen, sodass Sie 'event_type' nicht verwenden können und 'event_timestamp' in der Ansicht, aber Sie können einige Getter innerhalb von Video deklarieren Modell, um diese Informationen anzuzeigen:

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

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

und die Ansicht verwenden:

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

bearbeiten :
Wenn Sie die Videoduplikate behalten möchten, deklarieren Sie die beiden Spalten von events im Video Modell

public $event_type, $event_timestamp;

behalten Sie die ursprüngliche GridView setup, und fügen Sie ein select hinzu und indexBy dies an die Abfrage innerhalb von 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++);
    });

aktualisieren

ein direkter staff Beziehung zu Video ist derzeit etwas problematisch, da das mehr als eine Tabelle davon entfernt ist. Es gibt ein Problem darüber hier

Sie fügen jedoch den staff hinzu Tabelle, indem Sie sie mit dem Ereignis verknüpfen Modell,

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

damit können Sie wie folgt abfragen:

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

Filtern erfordert einige kleine Updates auf dem Controller, der Ansicht und einem SarchModel
Hier ist eine minimale Implementierung:

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;
    }
}

Controller:

public function actionIndex() {
    $searchModel = new VideoSearch();
    $dataProvider = $searchModel->search(Yii::$app->request->queryParams);

    return $this->render('test', [
        'searchModel'  => $searchModel,
        'dataProvider' => $dataProvider,
    ]);
}

und die Aussicht

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