Разработка на 1С-Битрикс Михаил Базаров
Блог-note

Получить заказы с корзинами, свойствами, доставками и оплатами через getList-ы

Просмотров: 637
Задача: получить заказы со всеми дополнительными данными: корзинами, свойствами, доставками и оплатами- через
getLis-ы, для контроля получаемых данных.
На самом деле хотел написать заметку про составление оптимизированных запросов при получении больших массивов информации из БД.

Но решил не делать пример в вакууме, поэтому взял наиболее частую задачу- работу с заказами.
В любом случае, в примере ниже вы получите то, за чем пришли :)- всю информацию о заказах.

Почему именно через getList-ы? Потому что это низкоуровневый инструмент с полным контролем над запросом: можно явно указать нужные поля через select, задать условия через filter, отсортировать через order. Именно эта гибкость позволяет писать эффективные запросы и избежать распространённой ловушки.

У заметки есть аналог:
Заказы через ORM

Проблема N+1 в запросах

Есть соблазн получать данные порциями: в цикле по заказам дополнительно подгружать свойства, доставки, оплаты и т.д., дозаполняя массив с заказами прямо на месте.

При таком подходе можно не столкнуться с проблемой в процессе разработки на небольшом количестве заказов, но на продакшене, когда потребуется обработать сотни или тысячи заказов, это обернётся серьёзной потерей производительности.

Посмотрим на конкретный пример. Допустим, в базе 100 заказов. Код ниже выполнит:

  • 1 запрос = список заказов (Order::getList)
  • 100 запросов = название статуса для каждого заказа (CSaleStatus::GetByID)
  • 100 запросов = товары корзины для каждого заказа (Basket::getList)
  • 100 запросов = свойства для каждого заказа (OrderPropsValueTable::getList)
  • 100 запросов = доставки для каждого заказа (ShipmentTable::getList)
  • 100 запросов = оплаты для каждого заказа (PaymentTable::getList)

Итого: 1 + 100 × 5 = 501 запрос вместо 6. Это и есть проблема N+1: вместо одного запроса на каждый тип данных выполняется по N запросов - по одному на каждую запись из первого запроса.

use Bitrix\Main\Loader,
    Bitrix\Sale\Order,
    Bitrix\Sale\Internals\OrderPropsValueTable,
    Bitrix\Sale\Internals\ShipmentTable,
    Bitrix\Sale\Internals\PaymentTable,
    Bitrix\Sale\Basket;
Loader::includeModule('sale');
$dbRes = Order::getList([
    'select' => [
        "*"
    ],
    'order' => [
        'ID' => 'DESC'
    ]
]);
while ($order = $dbRes->fetch()) {
    $arResult['ORDERS'][$order['ID']] = $order;
    // Название статуса
    $arResult['ORDERS'][$order['ID']]['STATUS_NAME'] = CSaleStatus::GetByID($order['STATUS_ID']);
    // Получаем товары корзины заказа
    $arResult['ORDERS'][$order['ID']]['BASKET_ITEMS'] = Basket::getList(
        [
            'select' => ['*'],
            'filter' => [
                '=ORDER_ID' => $order['ID']
            ]
        ]
    )->FetchAll();
    // Получаем свойства заказа
    $arResult['ORDERS'][$order['ID']]['ORDER_PROPS'] = OrderPropsValueTable::getList(
        [
            'select' => ['*'],
            'filter' => [
                '=ORDER_ID' => $order['ID']
            ]
        ]
    )->fetchAll();
    // Получаем информацию о доставках
    $arResult['ORDERS'][$order['ID']]['DELIVERY'] = ShipmentTable::getList([
        'select' => ['*'],
        'filter' => [
            '=ORDER_ID' => $order['ID'],
            '=SYSTEM' => 'N'   // исключаем системную «виртуальную» отгрузку
        ]
    ])->fetchAll();
    // Получаем информацию об оплатах
    $arResult['ORDERS'][$order['ID']]['PAYMENTS'] = PaymentTable::getList([
        'select' => ['*'],
        'filter' => [
            '=ORDER_ID' => $order['ID']
        ]
    ])->fetchAll();
}
// Все о заказе
var_dump($arResult['ORDERS']);

Чем больше заказов- тем хуже ситуация. При 1000 заказах это уже 5001 запрос вместо 6. Каждый запрос- это отдельное обращение к базе данных: установка соединения, передача запроса, ожидание ответа. Всё это многократно увеличивает время ответа страницы и нагрузку на сервер БД.

Правильное решение- выполнить по одному запросу на каждый тип данных, собрав сразу все нужные ID заказов, а затем "склеить" результаты в PHP.

Оптимизированный пример

Идея проста: сначала одним запросом получаем все заказы и собираем массив их ID, а затем делаем по одному запросу на каждый тип связанных данных, передавая сразу весь массив ID в фильтр. Результаты разбрасываем по нужным заказам уже в PHP- без единого лишнего обращения к базе.

use Bitrix\Main\Loader,
    Bitrix\Sale\Order,
    Bitrix\Sale\Internals\OrderPropsValueTable,
    Bitrix\Sale\Internals\ShipmentTable,
    Bitrix\Sale\Internals\PaymentTable,
    Bitrix\Sale\Basket;
Loader::includeModule('sale');
$dbRes = \Bitrix\Sale\Order::getList([
    'select' => [
        "*"
    ],
    'order' => [
        'ID' => 'DESC'
    ]
]);
// Шаг 1: собираем заказы и их ID
$orderIds = [];
while ($order = $dbRes->fetch()) {
    $arResult['ORDERS'][$order['ID']] = $order;
    $arResult['ORDERS'][$order['ID']]['STATUS_NAME'] = CSaleStatus::GetByID($order['STATUS_ID']);
    $arResult['ORDERS'][$order['ID']]['BASKET_ITEMS'] = [];
    $arResult['ORDERS'][$order['ID']]['ORDER_PROPS'] = [];
    $arResult['ORDERS'][$order['ID']]['DELIVERY'] = [];
    $orderIds[] = $order['ID'];
}
if (!empty($orderIds)) {
    // Шаг 2: корзины - 1 запрос
    $dbBasket = Basket::getList([
        'select' => ['*'],
        'filter' => ['=ORDER_ID' => $orderIds]
    ]);
    while ($item = $dbBasket->fetch()) {
        $arResult['ORDERS'][$item['ORDER_ID']]['BASKET_ITEMS'][] = $item;
    }
    // Шаг 3: свойства - 1 запрос
    $dbProps = \Bitrix\Sale\Internals\OrderPropsValueTable::getList([
        'select' => ['*'],
        'filter' => ['=ORDER_ID' => $orderIds]
    ]);
    while ($prop = $dbProps->fetch()) {
        $arResult['ORDERS'][$prop['ORDER_ID']]['ORDER_PROPS'][] = $prop;
    }
    // Шаг 4: доставки - 1 запрос
    $dbShipments = \Bitrix\Sale\Internals\ShipmentTable::getList([
        'select' => ['*'],
        'filter' => [
            '=ORDER_ID' => $orderIds,
            '=SYSTEM' => 'N'
        ]
    ]);
    while ($shipment = $dbShipments->fetch()) {
        $arResult['ORDERS'][$shipment['ORDER_ID']]['DELIVERY'][] = $shipment;
    }
    // Шаг 5: оплаты - 1 запрос
    $dbPayments = \Bitrix\Sale\Internals\PaymentTable::getList([
        'select' => ['*'],
        'filter' => ['=ORDER_ID' => $orderIds]
    ]);
    while ($payment = $dbPayments->fetch()) {
        $arResult['ORDERS'][$payment['ORDER_ID']]['PAYMENTS'][] = $payment;
    }
}
// Все о заказе
var_dump($arResult['ORDERS']);

Теперь независимо от количества заказов выполняется ровно 6 запросов: по одному на каждый тип данных. При 100 заказах — 6 запросов, при 1000 — всё те же 6.

  • 1 запрос = список заказов
  • 1 запрос = корзины всех заказов
  • 1 запрос = свойства всех заказов
  • 1 запрос = доставки всех заказов
  • 1 запрос = оплаты всех заказов
  • N запросов = названия статусов (их тоже можно вынести отдельно, получив все нужные статусы одним запросом через CSaleStatus::GetList)

Ключевое отличие от плохого варианта - фильтр принимает массив ID ('=ORDER_ID' => $orderIds), что транслируется в один SQL-запрос с конструкцией IN (...). Разбивка полученных данных по заказам происходит в PHP через простой while с присвоением по ключу.

Услуги

Стоимость разработки на 1С-Битрикс

Стоимость разработки сайта зависит от объёма и сложности проекта. Ниже приведены ориентировочные цены, как правило не выходят за обозначенные рамки. Срок разработки зависит от сложности проекта: как правило называю сроки с запасом.

Финальная стоимость и сроки разработки обговариваются на этапе обсуждения. Скачайте опросник на разработку, заполните как можно подробнее и вышлите удобным способом. После ознакомления смогу задать уточняющие вопросы и оценить проект.

Включено в стоимость разработки

В цену уже заложено всё, что нужно для легального и быстрого запуска. Доплачивать за базовые вещи не придётся.

  • Лицензия и модули

    Лицензия на «1С-Битрикс» нужной редакции, дополнительные модули и видео-инструкции по работе с готовым проектом.

  • Скорость и SEO

    Оптимизация кода и сервера под быструю загрузку, базовая SEO-оптимизация и добавление сайта в поисковые системы.

Блог-note

Заметки по 1С-Битрикс