Дублирование результатов запроса после leftJoin

Уважаемое сообщество, здравствуйте!
В ходе работы над сниппетом, возвращающим список всех заказов пользователя, возникла проблема:
в результирующем массиве информация о заказе продублирована столько раз, сколько товаров включено в заказ.
Пожалуйста, подскажите, каким образом можно вернуть результат присоединения таблицы товаров к заказу в рамках одного элемента массива?
Ниже код моего костыля сниппета (код, относящийся к оформлению результатов выборки вырезан, т.к. к вопросу не относится):
<?php
//
if(!isset($user) || ($user === 0)){
    return '<p>Ошибка! Идентификатор пользователя не определён.</p>';
}

//Задаём значения чанков оформления по-умолчанию
if(!isset($tpl)){ $tpl = 'tpl.profile.orders_list.row'; }
if(!isset($tplWrapper) || ($tplWrapper === '')){ $tplWrapper = 'tpl.profile.orders_list.wrapper'; }
if(!isset($idx)){ $idx = 1; }

$q = $modx->newQuery('msOrder');
$q->select(
    array
    (
        'msOrder.id',
        'msOrder.num',
        'msOrder.createdon',
        'msOrder.cost',
        'msOrder.status',
        'op.product_id',
        'op.name'
    )
);
$q->where(array('msOrder.user_id' => $user));
$q->sortby('msOrder.id','DESC');
$q->leftJoin('msOrderProduct','op', 'msOrder.id = op.order_id');
$query = $q->prepare();
$query->execute();
$orders = $query->fetchAll(PDO::FETCH_ASSOC);

$res = print_r($orders,1);
echo '<div class="ord-lst-wrap"><pre>';
echo $res;
echo '</pre></div>';
Ниже приведен результат работы сниппета, первые два пункта демонстрируют проблему (в заказе два товара):
Array
(
    [0] => Array
        (
            [id] => 9
            [num] => 1511/6
            [createdon] => 2015-11-17 09:43:21
            [cost] => 17849.00
            [status] => 1
            [product_id] => 5553
            [name] => Primavera (гостиная) Комод 1 /Глазово
        )

    [1] => Array
        (
            [id] => 9
            [num] => 1511/6
            [createdon] => 2015-11-17 09:43:21
            [cost] => 17849.00
            [status] => 1
            [product_id] => 5205
            [name] => Berlin 1 (гостиная) Шкаф МНЦ КОРПУС (01, Ясень шимо свентлый) /Глазово
        )

    [2] => Array
        (
            [id] => 8
            [num] => 1511/5
            [createdon] => 2015-11-15 09:00:35
            [cost] => 7599.00
            [status] => 1
            [product_id] => 2034
            [name] => (закрыт) Стенка Франк МА-190 Дуб кремона/Дуб феррара /Столлайн
        )

    [3] => Array
        (
            [id] => 7
            [num] => 1511/4
            [createdon] => 2015-11-10 03:09:59
            [cost] => 26323.00
            [status] => 1
            [product_id] => 5788
            [name] => Акцент орхидея Шкаф-купе 2300х1800х600/ясень шимо темный/шампань/Бумеранг
        )
P.S.: самое простое решение — вовсе избежать leftJoin'а таблицы товаров заказа к запросу, а в оформленном выводе возвращать ссылку с id-заказа, которая вела бы на страничку со сниппетом, принимающим на вход id заказа и возвращающего оформленный список товаров этого заказа. Но, может быть, это возможно сделать в одном сниппете (без обработки результирующего массива на php — объединения элементов с одинаковым id и формированием массива товаров)? Куда копать?
Спасибо!
Денис Райх
18 ноября 2015, 01:15
modx.pro
2
1 941
0

Комментарии: 3

Василий Наумкин
18 ноября 2015, 06:14
+1
Дублирование результатов запроса после leftJoin
Чтобы такого не было, нужно сгруппировать запрос:
$q->groupby('msOrder.id');

Если же нужно выбрать заказ + все его товары, то лучше не использовать leftJoin и добирать эти товары отдельным запросом во время оформления вывода заказа.

Ну а если нужно будет еще и сортировать по товарам, то тогда всё же оставляешь leftJoin и убираешь дубликаты уже во время оформления.
    Денис Райх
    18 ноября 2015, 07:07
    0
    Большое спасибо за советы, Василий!
    Так и поступаю: обрабатываю результаты выборки перед оформлением, сливая дубликаты в один элемент с массивом товаров.
    Денис Райх
    18 ноября 2015, 11:50
    1
    0
    Вдруг моя поделка кому-нибудь будет полезна.
    Ниже приведен код сниппета и его чанков оформления.
    Сниппет возвращает список заказов конкретного пользователя вместе с товарами заказа и оформляет вывод в чанки.
    На вход сниппет принимает параметры:
    'user' => Пользователь, чьи заказы выводим;
    'tpl' => Чанк оформления элемента списка заказов;
    'tplWrapper' => Чанк оформления оболочки списка заказов;
    'tplProducts' => Чанк оформления элемента списка продуктов в заказе;
    'tplEmpty' => Чанк оформления пустого результата;
    Код сниппета «getUserOrders»:
    <?php
    if(!isset($user) || ($user === 0))
    {
        return '<p>Ошибка! Идентификатор пользователя не определён.</p>';
    }
    
    //Задаём значения чанков оформления по-умолчанию
    if(!isset($tpl)){ $tpl = 'tpl.profile.orders_list.row'; }
    if(!isset($tplWrapper) || ($tplWrapper === '')){ $tplWrapper = 'tpl.profile.orders_list.wrapper'; }
    if(!isset($tplProducts) || ($tplProducts === '')){ $tplProducts = 'tpl.profile.orders_list.products.row'; }
    if(!isset($tplEmpty) || ($tplEmpty === '')){ $tplEmpty = 'tpl.profile.orders_list.empty'; }
    if(!isset($idx)){ $idx = 1; }
    
    //Формируем запрос к БД
    $q = $modx->newQuery('msOrder');
    $q->select(
        array
        (
            'msOrder.id',
            'msOrder.num',
            'msOrder.createdon',
            'msOrder.cost',
            'msOrder.status',
            'op.product_id',
            'op.name',
            'op.price',
            'op.count'
        )
    );
    $q->where(array('msOrder.user_id' => $user));
    $q->sortby('msOrder.id','DESC');
    $q->leftJoin('msOrderProduct','op', 'msOrder.id = op.order_id');
    $query = $q->prepare();
    $query->execute();
    $orders = $query->fetchAll(PDO::FETCH_ASSOC);
    
    if (count($orders) === 0)
    {
        $result = $modx->getChunk('tpl.profile.orders_list.empty');
        return $result;
    }
    
    //Инициализируем переменные для обработки результата выборки
    $orders_unique = array();
    $count = 0;
    
    foreach ($orders as $o)
    {
        //Т.к. в результате выборки дублируется информация по заказам с несколькими товарами - 
        //перед выводом избавляемся от дубликатов
        
        //Если ID последнего элемента в обработанном списке заказов не совпадает с ID обрабатываемого заказа, либо это первый шаг, 
        //то формируем новый элемент обработанного массива, запихивая туда данные выборки
        if (($o['id'] != $orders_unique[$count - 1]['order_id']) || ($count === 0))
        {
            $orders_unique[$count] = array
                (
                    'order_id'         => $o['id'],
                    'order_num'        => $o['num'],
                    'order_createdon'  => $o['createdon'],
                    'order_status'     => $o['status'],
                    'order_total_cost' => $o['cost']
                );
            //Т.к. товаров в заказе может быть несколько - инициализируем поле order_products, как массив
            $orders_unique[$count]['order_products'][] = array 
                (
                    'product_id'    => $o['product_id'],
                    'product_name'  => $o['name'],
                    'product_price' => $o['price'],
                    'product_count' => $o['count'],
                    'product_cost'  => $o['price'] * $o['count']
                );
    
            $count++;
        }
        //В случае, если мы наткнулись на дубликат заказа, то просто дополняем массив 'order_products' последнего элемента обработанного списка
        else
        {
            $orders_unique[$count - 1]['order_products'][] = array 
                (
                    'product_id'    => $o['product_id'],
                    'product_name'  => $o['name'],
                    'product_price' => $o['price'],
                    'product_count' => $o['count'],
                    'product_cost'  => $o['price'] * $o['count']
                );
        }
    }
    //Вывод отладочной информации по формируемому массиву, если потребуется
    /*$res = print_r($orders_unique, 1);
    echo '<div class="ord-lst-wrap"><pre>';
    echo $res;
    echo '</pre></div>';*/
    
    //Оформляем массив с отфильтрованными дубликатами
    foreach ($orders_unique as $o_u)
    {
        $products_list = '';
        
        //Обрабатываем каждый элемент массива товаров в заказе
        foreach($o_u['order_products'] as $o_p)
        {
            $product_data = array 
            (
                'product_id'    => $o_p['product_id'],
                'product_name'  => $o_p['product_name'],
                'product_price' => $o_p['product_price'],
                'product_count' => $o_p['product_count'],
                'product_cost'  => $o_p['product_cost']
            );
            if ($tplProducts !== '')
            {
                $products_list .= $modx->getChunk($tplProducts, $product_data);
            }
            //В случае, если шаблон оформления товара не задан - выводим массив доступных плейсхолдеров вместе с содержимым
            else
            {
                $products_list .= '<pre>'.print_r($product_data, 1).'</pre>';
            }
        }
        
        $order_data = array
        (
            'order_num'        => $o_u['order_num'],
            'order_createdon'  => $o_u['order_createdon'],
            'order_total_cost' => $o_u['order_total_cost'],
            'order_status'     => $o_u['order_status'],
            'order_products'   => $products_list,
            'idx'              => $idx
        );
        if ($tpl !== '')
        {
            $output .= $modx->getChunk($tpl, $order_data);
        }
        //В случае, если шаблон оформления заказа не задан - выводим массив доступных плейсхолдеров вместе с содержимым
        else
        {
            $output .= '<pre>'.print_r($order_data, 1).'</pre>';
        }
        
        $idx++;
    }
    
    $result = $modx->getChunk($tplWrapper, 
        array
        (
            'output' => $output,
            'orders_total'  => count($orders_unique)
        )
    );
    
    return $result;
    Содержимое чанка 'tpl.profile.orders_list.wrapper':
    <!-- tpl.profile.orders_list.wrapper-->
    <div class="pure-g ord-lst-wrap">
        <div class="pure-u-1">
            <div class="pure-g ord-lst-body">
                [[+output]]
            </div>
        </div>
        <div class="pure-u-1">
            <p>Всего заказов: [[+orders_total]]</p>
        </div>
    </div>
    <!-- /tpl.profile.orders_list.wrapper-->
    Содержимое чанка 'tpl.profile.orders_list.row':
    <!-- tpl.profile.orders_list.row -->
    <div class="pure-g ord-lst-item" id="order-list-item-[[+idx]]">
        <div class="pure-u-1">
            <div class="pure-g">
                <div class="pure-u-6-24"><span>Номер:[[+order_num]]</span></div>
                <div class="pure-u-6-24"><span>Дата:[[+order_createdon]]</span></div>
                <div class="pure-u-6-24"><span class="center">Стомость:[[+order_total_cost]] руб.</span></div>
                <div class="pure-u-6-24"><span class="center">Статус:[[+order_status]]</span></div>
            </div>
        </div>
        <div class="pure-u-1">
            <div class="pure-g ord-prod-wrap">
                <div class="pure-u-1">
                    <div class="pure-u-2-24"><span>ID товара</span></div>
                    <div class="pure-u-12-24"><span>Наименование</span></div>
                    <div class="pure-u-3-24"><span>Цена</span></div>
                    <div class="pure-u-3-24"><span>Количество</span></div>
                    <div class="pure-u-4-24"><span>Итого</span></div>
                </div>
                <div class="pure-u-1 ord-prod-lst">
                    [[+order_products]]
                </div>
            </div>
        </div>
    </div>
    <!-- /tpl.profile.orders_list.row -->
    Содержимое чанка 'tpl.profile.orders_list.products.row':
    <!-- tpl.profile.orders_list.products.row -->
    <div class="pure-g order-products-item">
        <div class="pure-u-2-24"><span>[[+product_id]]</span></div>
        <div class="pure-u-12-24"><span>[[+product_name]]</span></div>
        <div class="pure-u-3-24"><span>[[+product_price]] руб.</span></div>
        <div class="pure-u-3-24"><span>[[+product_count]]</span></div>
        <div class="pure-u-4-24"><span>[[+product_cost]] руб.</span></div>
    </div>
    <!-- /tpl.profile.orders_list.products.row -->
    Содержимое чанка 'tpl.profile.orders_list.empty':
    <!-- tpl.profile.orders_list.empty -->
    <div class="pure-g ord-lst-empty-wrap">
        <div class="pure-u-1">
            <p>Вы не сделали ни одного заказа.</p>
        </div>
    </div>
    <!-- /tpl.profile.orders_list.empty -->
    Вызов сниппета на странице:
    {$_modx->runSnippet('!getUserOrders',[
        'user'       => $_modx->user.id,
    ])}
      Авторизуйтесь или зарегистрируйтесь, чтобы оставлять комментарии.
      3