Mysql
 sql >> Base de données >  >> RDS >> Mysql

Requêtes de base de données complexes dans yii2 avec Active Record

je suppose, sur la base de la question que vous avez posée ici vous avez aimé dans les commentaires que vous avez fourni l'intégralité de la requête (pas d'autres champs, que vous avez retirés juste pour montrer un exemple de code)

donc, si vous n'avez besoin que des champs spécifiés dans SELECT déclaration, vous pouvez optimiser un peu votre requête :

tout d'abord, vous vous joignez à host_machines uniquement pour lier les cameras et events , mais ont la même clé host_machines_idhost_machines sur les deux, donc ce n'est pas nécessaire, vous pouvez directement :

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

deuxièmement, la jointure avec ispy.staff , le seul champ utilisé est idreceptionist dans WHERE clause, ce champ existe dans events ainsi que nous pouvons le supprimer complètement

la requête finale ici :

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)

devrait afficher les mêmes enregistrements que celui de votre question, sans aucune ligne identique
certains doublons vidéo existeront toujours en raison d'une relation un à plusieurs entre cameras et events

maintenant du côté yii des choses,
vous devez définir quelques relations sur les Vidéos modèle

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

le contrôleur vidéo et la fonction de recherche elle-même

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 traitera chaque vidéo comme un seul enregistrement (basé sur pk), ce qui signifie que tous les doublons vidéo sont supprimés. vous aurez des vidéos uniques, chacune avec plusieurs événements, vous ne pourrez donc pas utiliser 'event_type' et 'event_timestamp' dans la vue, mais vous pouvez déclarer des getters dans Vidéo modèle pour afficher cette information :

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

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

et la vue utilise :

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

modifier :
si vous souhaitez conserver les doublons vidéo, déclarez les deux colonnes de events à l'intérieur de la Vidéo modèle

public $event_type, $event_timestamp;

conserver le GridView d'origine setup, et ajoutez un select et indexBy cela à la requête à l'intérieur de 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++);
    });

mettre à jour

un staff direct relation avec Video est actuellement quelque peu problématique car il y a plus d'une table loin d'elle.

cependant, vous ajoutez le staff tableau en le liant à l'événement modèle,

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

qui vous permettra d'interroger comme ceci :

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

Filtrage nécessitera quelques petites mises à jour sur le contrôleur, la vue et un SarchModel
voici une implémentation minimale :

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

contrôleur :

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

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

et la vue

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