Задать вопрос
atis2345
@atis2345
PHP developer

Как лучше оптимизировать агрегатные запросы к бд а Yii2?

Необходимо вывести список юзеров с количеством купленных товаров и суммой этих товаров.
Код урезан.

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

Вариант #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

Вариант #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.
// Рефакторить это дело очень плохо!

Вариант #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 самый лучший вариант ( его понимает IDE + рефакторить очень легко). вот только я не знаю пока как его лучше протестировать.

Вопрос связан с вопросами Подходит ли паттерн Decorator для моей задачи? и Как можно заюзать PDO::FETCH_CLASS при чистых запросах к бд на Yii2?
  • Вопрос задан
  • 1043 просмотра
Подписаться 2 Оценить Комментировать
Пригласить эксперта
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы