A
A
atis //2017-01-04 11:49:05
Yii
atis //, 2017-01-04 11:49:05

What is the best way to optimize aggregate queries to the database in Yii2?

It is necessary to display a list of users with the number of purchased goods and the amount of these goods.
The code has been truncated.

use yii\db\ActiveRecord;
use Yii;
use yii\db\Query;

/**
 * @property int     $id
 * @property string  $name
 * @property Order[] $orders
 * @property int     $totalOrders
 * @property double  $totalOrdersPrice
 */
class User extends ActiveRecord
{
    public function getOrders()
    {
        return $this->hasMany(Order::className(), ['user_id' => 'id']);
    }

    public function getTotalOrders()
    {
        return $this->hasOne(Order::className(), ['user_id' => 'id'])->count();
    }

    public function getTotalOrdersPrice()
    {
        return $this->hasOne(Order::className(), ['user_id' => 'id'])->sum('price');
    }
}

/**
 * @property int    $id
 * @property string $date_add
 * @property double $price
 * @property int    $user_id
 * @property User   $user
 */
class Order extends ActiveRecord
{
    public function getUser()
    {
        return $this->hasOne(User::className(), ['id' => 'user_id']);
    }
}

Option 1
$users = User::find()->with(['orders'])->all();

foreach ($users as $User) {

    echo $User->name;
    echo $User->totalOrders;
    echo $User->totalOrdersPrice;
}

// Например если я вывожу 100 юзеров на страницу, то количество запросов к БД будет = 1 + 1 + 100 + 100 = 202!!

'SELECT * FROM users';                             // x 1
'SELECT * FROM orders WHERE user_id IN ( ... )';   // x 1
'SELECT COUNT(*) FROM orders WHERE user_id = ?';   // x 100
'SELECT SUM(price) FROM orders WHERE user_id = ?'; // x 100

Option #2
$users = User::find()->with(['orders'])->all();
$ids   = array_map(function ($User) { return $User->id; }, $users);
$stat  = (new Query())
            ->select(['COUNT(*)', 'SUM(price)'])
            ->from(Order::tableName())
            ->where(['in', 'user_id', $ids])
            ->groupBy(['user_id'])
            ->all();

// Надо объединить $stat и $users.
// Рефакторить это дело очень плохо!

Option #3
/**
 * @property int    $total
 * @property double $price
 * @property int    $user_id
 * @property User   $user
 */
class Stat
{
    /**
     * @param User[] $users
     * @return Stat[]
     */
    public static function fill($users)
    {
        // ...

        'SELECT COUNT(*) as total, SUM(price) as price, user_id FROM orders WHERE user_id IN( ... ) GROUP BY user_id';

        // $pdoStatement->fetchAll(PDO::FETCH_CLASS, 'Stat');
        // Цепляем юзеров к екзеплярам Stat
        // ...
    }
}


$stats = Stat::fill(User::find()->with(['orders'])->all());

foreach ($stats as $Stat) {

    echo $Stat->user->id;
    echo $Stat->total;
}

UPD:
#3 is the best option (the IDE understands it + refactoring is very easy). I just don't know yet how best to test it.
The question is related to the questions Is the Decorator pattern suitable for my task? and How can I use PDO::FETCH_CLASS for pure database queries in Yii2?

Answer the question

In order to leave comments, you need to log in

Didn't find what you were looking for?

Ask your question

Ask a Question

731 491 924 answers to any question