Setting up bulk transfer of goods between 1C-Bitrix sections

Our company is engaged in the development, support and maintenance of Bitrix and Bitrix24 solutions of any complexity. From simple one-page sites to complex online stores, CRM systems with 1C and telephony integration. The experience of developers is confirmed by certificates from the vendor.
Our competencies:
Development stages
Latest works
  • image_website-b2b-advance_0.png
    B2B ADVANCE company website development
    1212
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Website development for FIXPER company
    815
  • image_bitrix-bitrix-24-1c_development_of_an_online_appointment_booking_widget_for_a_medical_center_594_0.webp
    Development based on Bitrix, Bitrix24, 1C for the company Development of an Online Appointment Booking Widget for a Medical Center
    565
  • image_bitrix-bitrix-24-1c_mirsanbel_458_0.webp
    Development based on 1C Enterprise for MIRSANBEL
    747
  • image_crm_dolbimby_434_0.webp
    Website development on CRM Bitrix24 for DOLBIMBY
    657
  • image_crm_technotorgcomplex_453_0.webp
    Development based on Bitrix24 for the company TECHNOTORGKOMPLEKS
    980

Setting Up Bulk Product Movement Between Sections in 1C-Bitrix

Catalog restructuring: the "Smartphones" section splits into "Apple Smartphones", "Samsung Smartphones", "Xiaomi Smartphones". 800 products need to be distributed to new sections. Or the supplier changed category structure, and 400 items need moving from one section to another.

Section Structure and Product Links

Iblock sections are stored in b_iblock_section. Product-to-section binding is the IBLOCK_SECTION_ID field in b_iblock_element (primary section). Additional binding to multiple sections is the b_iblock_section_element table: IBLOCK_ELEMENT_ID, IBLOCK_SECTION_ID, ADDITIONAL_PROPERTY_ID.

When moving a product, update both:

  1. IBLOCK_SECTION_ID in b_iblock_element — primary section.
  2. Record in b_iblock_section_element — for proper filter functionality.

Moving via CIBlockElement::Update

Standard update method with section change:

\CIBlockElement::Update($elementId, false, [
    'IBLOCK_SECTION_ID' => $newSectionId,
]);

After Update(), Bitrix automatically updates b_iblock_section_element. But the method is slow for bulk operations — each call goes through events, cache, and permissions.

Direct SQL update for speed:

global $DB;

$elementIds = implode(',', array_map('intval', $productIds));
$newSection  = (int)$newSectionId;

$DB->Query("
    UPDATE b_iblock_element
    SET IBLOCK_SECTION_ID = {$newSection}
    WHERE ID IN ({$elementIds})
");

$DB->Query("
    DELETE FROM b_iblock_section_element
    WHERE IBLOCK_ELEMENT_ID IN ({$elementIds})
    AND ADDITIONAL_PROPERTY_ID IS NULL
");

foreach ($productIds as $id) {
    $DB->Query("
        INSERT INTO b_iblock_section_element
            (IBLOCK_ELEMENT_ID, IBLOCK_SECTION_ID)
        VALUES
            ({$id}, {$newSection})
    ");
}

Direct SQL works 50-100 times faster than CIBlockElement::Update() for bulk operations, but requires manual cache clearing.

Multi-section Binding

If a product appears in multiple sections simultaneously (e.g., "Smartphones" and "Sales"), b_iblock_section_element contains multiple rows for one IBLOCK_ELEMENT_ID:

// Delete old section bindings
\Bitrix\Iblock\SectionElementTable::deleteByFilter([
    'IBLOCK_ELEMENT_ID' => $elementId,
    'ADDITIONAL_PROPERTY_ID' => false,
]);

// Add new ones
foreach ($sectionIds as $secId) {
    \Bitrix\Iblock\SectionElementTable::add([
        'IBLOCK_ELEMENT_ID' => $elementId,
        'IBLOCK_SECTION_ID' => $secId,
    ]);
}

The primary section (IBLOCK_SECTION_ID in b_iblock_element) remains one — the one considered "main" for breadcrumbs and URLs.

Cache Clearing After Moving

After bulk moving, component cache doesn't expire immediately. Force clearing:

\Bitrix\Main\Application::getInstance()->getTaggedCache()->clearByTag('iblock_id_' . $iblockId);

// For specific sections
foreach (array_unique(array_merge($oldSectionIds, [$newSectionId])) as $secId) {
    \Bitrix\Main\Application::getInstance()->getTaggedCache()
        ->clearByTag('iblock_section_' . $secId);
}

Conditional Moving

To automatically distribute products to sections based on properties — for example, by brand:

$brandSectionMap = [
    'Apple'   => 125,
    'Samsung' => 126,
    'Xiaomi'  => 127,
];

$res = \CIBlockElement::GetList(
    [],
    ['IBLOCK_ID' => $iblockId, 'IBLOCK_SECTION_ID' => $sourceSection],
    false,
    false,
    ['ID', 'PROPERTY_BRAND']
);

while ($item = $res->GetNext()) {
    $brand    = $item['PROPERTY_BRAND_VALUE'];
    $targetId = $brandSectionMap[$brand] ?? null;
    if ($targetId) {
        \CIBlockElement::Update($item['ID'], false, ['IBLOCK_SECTION_ID' => $targetId]);
    }
}

For large volumes, this script runs as a Bitrix agent in batches of 100-200 items with progress saved in b_option.