6

My store has roughly 1000 products and 600 bundles, with approximately 8 store views. This equates to about 12,800 products or so.

We are going to be introducing configurables and one of the key attributes we want to reuse is a multi-select form field. Every product only has one type selected, so the EAV backend data-type which was configured during the set up of the original store was chosen incorrectly. At this point we'd like to migrate this to the dropdown data type.

Is there any relatively painless method for getting this set up outside of creating an entirely new attribute, porting the data, and deleting the old attr? Anyone with any experiences doing this that can lend me some helpful advice?

4 Answers 4

7

You not only have to change the backend_type in the eav_attribute table, but also copy the rows from the source to target table. Following code will help you accomplishing this:

$connection         = _getConnection('core_write');
$attributeIds       = 'yourattributeid';
$entityTypeId       = 'oldentitytypeid;
$sql                = 'SELECT * FROM catalog_product_entity_varchar WHERE attribute_id IN ('.$attributeIds.') AND entity_type_id = '.$entityTypeId;
$rows               = $connection->fetchAll($sql);
foreach ($rows as $row){
    $price = $row['value'];
    if (!is_null($price)) {
        $sql = 'INSERT INTO catalog_product_entity_decimal (`entity_type_id`,`attribute_id`,`store_id`,`entity_id`,`value`)
            VALUES (?,?,?,?,?)';
        $connection->query($sql, array($row['entity_type_id'], $row['attribute_id'], $row['store_id'], $row['entity_id'], $price));
    }
    $sql = 'DELETE FROM catalog_product_entity_varchar WHERE value_id = ?'; // or your old storage table
    $connection->query($sql, $row['value_id']);
}
$sql = 'UPDATE eav_attribute SET backend_type="newbackendtype" WHERE attribute_id IN ('.$attributeIds.')';
$connection->query($sql);
0
2

This is loosely based on the answer by Tobias Zander, but is a bit more robust and cautious about the update so that it's safer to run on a production environment. It is also built explicitly to move a multiselect to a select, (dropdown.)

<?php

$installer = $this;
$installer->startSetup();
$setup = new Mage_Eav_Model_Entity_Setup('core_setup');

$attributeCode = 'my_atribute';

/** @var Mage_Catalog_Model_Resource_Eav_Attribute $attribute */
$attribute = Mage::getModel('catalog/resource_eav_attribute') ->loadByCode(Mage_Catalog_Model_Product::ENTITY, $attributeCode);
$attributeId   = $attribute->getId();
$entityTypeId  = $setup->getEntityTypeId('catalog_product');

$connection    = $setup->getConnection('core_write');
$ea            = $setup->getTable('eav_attribute');
$cea            = $setup->getTable('catalog_eav_attribute');
$cpei          = $setup->getTable('catalog_product_entity_int');
$cpev          = $attribute->getBackendTable(); //$setup->getTable('catalog_product_entity_varchar');
$ip            = $setup->getTable('index_process');

/*
 * Sanity check on the existing attribute
 */
if ($attribute->getFrontendInput() != 'multiselect' || $attribute->getBackendType() != 'varchar') {
    Mage::throwException("Cannot migrate `{$attributeCode}`` because it is not the expected backend or frontend type.");
    return;
}

$connection->query("LOCK TABLES {$cpev} WRITE");

/*
 * Check that none of the entities in the database have multiple selected values
 */
$num = (int)$connection->fetchOne("
    SELECT COUNT(*) as num
    FROM {$cpev}
    WHERE ". $connection->prepareSqlCondition('attribute_id', $attributeId) ."
    AND ". $connection->prepareSqlCondition('entity_type_id', $entityTypeId) ."
    AND value LIKE '%,%'
");

if ($num !== 0) {
    $connection->query("UNLOCK TABLES");
    Mage::throwException("Cannot migrate `{$attributeCode}`` because it contains {$num} entities with multiple selected values.");
    return;
}

$connection->beginTransaction();

$connection->query("
    INSERT INTO {$cpei}
        (`entity_type_id`, `attribute_id`, `store_id`, `entity_id`, `value`)
    SELECT `entity_type_id`, `attribute_id`, `store_id`, `entity_id`, CONVERT(`value`,UNSIGNED INTEGER) as `value`
    FROM {$cpev}
    WHERE ". $connection->prepareSqlCondition('attribute_id', $attributeId) ."
    AND ". $connection->prepareSqlCondition('entity_type_id', $entityTypeId) ."
    AND value IS NOT NULL
    AND value != ''
");

$connection->query("
    UPDATE {$ea}
    SET
        `frontend_input`    = 'select',                             # from 'multiselect'
        `backend_type`      = 'int',                                # from 'varchar'
        `backend_model`     = NULL,                                 # from 'eav/entity_attribute_backend_array'
        `source_model`      = 'eav/entity_attribute_source_table'   # from NULL
    WHERE ". $connection->prepareSqlCondition('attribute_id', $attributeId) ."
");

/*
 * OPTIONAL: Migrate the new attribute to be a configurable attribute
 */
$connection->query("
    UPDATE {$cea}
    SET `is_configurable` = '1'
    WHERE ". $connection->prepareSqlCondition('attribute_id', $attributeId) ."
");

$connection->query("UNLOCK TABLES");

$connection->query("
    DELETE
    FROM {$cpev}
    WHERE ". $connection->prepareSqlCondition('attribute_id', $attributeId) ."
    AND ". $connection->prepareSqlCondition('entity_type_id', $entityTypeId) ."
");

$connection->query("
    UPDATE {$ip}
    SET `status` = 'require_reindex'
    WHERE `indexer_code` IN ('catalog_product_attribute', 'catalog_product_flat')
");

$connection->commit();

$installer->endSetup();
1

I have updated Tyler V.'s code to be used directly in the database and for Magento 2.2.7. The biggest change is that entity_type_id is not used in the current database tables.

I also created the new super attribute manually through the Magento admin dashboard. I named it the same as the old one but appended "new" to the end. I will delete the old multiselect attribute at the end when I have finished and rename the new dropdown one to the old one's name.

To create the new super attribute, login to your Admin Dashboard and click Stores > Attributes > Products. Add new attribute. Make sure Scope is Global and the Catolog Input Type is Dropdown. Then go to Stores > Attributes > Attribute Set and drag the new attribute over to your attribute set.

My old multiselect attribute_id is 144. My new dropdown attribute_id is 278. You will need to change these to your appropriate id numbers.

  1. Check if the old multiselect values that have more than one value (has to be one-to-one for the dropdown). Either change these to have a new delimiter other than a comma so they can stay as their own new values or split them into individual values.

SELECT * FROM catalog_product_entity_varchar WHERE attribute_id = 144 AND value LIKE '%,%'

  1. Copy varchar values to int table, ignoring empty values and multiple values.

INSERT INTO catalog_product_entity_int (attribute_id, store_id, entity_id, value) SELECT '278' as attribute_id, store_id, entity_id, CONVERT(value,UNSIGNED INTEGER) as value FROM catalog_product_entity_varchar WHERE attribute_id = 144 AND value IS NOT NULL AND value != '' AND value NOT LIKE '%,%'

  1. Change attribute_id from old to new in eav_attribute_option table.

UPDATE eav_attribute_option SET attribute_id = 278 WHERE attribute_id = 144

  1. Reindex (from CLI: php bin/magento indexer:reindex) and Clear Cache (from CLI: php bin/magento cache:clean). Then go to Stores > Attributes > Products and click on your new attribute. Make sure the old values are now showing up in the new attribute.

  2. Delete the old multiselect attribute and rename the new one to the old one's name.

1
  • Very helpful! Thanks a lot.
    – omerts
    Mar 10, 2021 at 12:02
0

And here is a script tested on M1 1.9.4.3 for changing the attribute type the other way around, from select to multi-select:

// Get the attribute codes that we want to update
$attributeCodes = $this->getAttributeCodesToChange();

$this->log('The following attributes will be changed from select to multiselect: %s %s', "\n\n", print_r($attributeCodes, true));

if (count($attributeCodes)) {

    // Prepare the DB connection
    $setup = new Mage_Eav_Model_Entity_Setup('core_setup');
    $connection = $setup->getConnection('core_write');
    $eavAttributeTable = $setup->getTable('eav_attribute');
    $catalogProductEntityIntTable = $setup->getTable('catalog_product_entity_int');
    $catalogProductEntityVarcharTable = $setup->getTable('catalog_product_entity_varchar');
    $entityTypeId = $entityTypeId  = $setup->getEntityTypeId('catalog_product');

    // Loop through the attribute codes, update each attribute
    foreach ($attributeCodes as $attributeId => $attributeCode) {

        // Change the type of the attribute itself
        $connection->query("
            UPDATE {$eavAttributeTable}
            SET
                `frontend_input` = 'multiselect',
                `frontend_model` = NULL,
                `frontend_class` = NULL,
                `backend_type` = 'varchar',
                `backend_model` = 'eav/entity_attribute_backend_array',
                `backend_table` = NULL,
                `attribute_model` = NULL,
                `source_model` = NULL
            WHERE ". $connection->prepareSqlCondition('attribute_id', $attributeId)
        );

        // Insert the current values of the attribute into the table where the multiselect attribute requires them
        $connection->query("
            INSERT INTO {$catalogProductEntityVarcharTable} (`entity_type_id`, `attribute_id`, `store_id`, `entity_id`, `value`)
            SELECT `entity_type_id`, `attribute_id`, `store_id`, `entity_id`, `value`
            FROM {$catalogProductEntityIntTable}
            WHERE ". $connection->prepareSqlCondition('attribute_id', $attributeId) ."
            AND ". $connection->prepareSqlCondition('entity_type_id', $entityTypeId) ."
            AND value IS NOT NULL
            AND value != ''
        ");

        // Remove the values from the table where the select attribute stored them
        $connection->query("
            DELETE FROM {$catalogProductEntityIntTable}
            WHERE " . $connection->prepareSqlCondition('entity_type_id', $entityTypeId) . "
            AND " . $connection->prepareSqlCondition('attribute_id', $attributeId)
        );

        $this->log('Successfully changed attribute type for attribute: ' . $attributeCode);
    }
}

$this->log('Finished!');

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.