Multiple source data rows

Last updated on
30 April 2025

The Migration class is built on the fundamental assumption that there is a one-to-one correspondence between source rows coming in and destination objects going out. What if on the source side you have multiple values spread among multiple database rows for one or more of your fields? For example, if your source were a Drupal site, your first stab at a source query to migrate nodes and related terms might look like

$query = db_select('node', 'n')
         ->fields('n', array('nid', 'title')
         ->fields('tn', array('tid'));
$query->leftJoin('term_node', 'tn', 'n.vid=tn.vid');

The problem is, if you have multiple terms for a node, you'll end up with multiple rows for the same node feeding into the migration:

1 First node  23
1 First node  89
2 Second node 23
...

That won't work - you'll create 'First node' with term 23, then the second row will be skipped because nid 1 has already been migrated. What you want is a way to deal with one node at a time, and pull in all related terms with that node. Here are a couple of options:

GROUP_CONCAT

If your source data is in a MySQL database, you can use GROUP_CONCAT to pull the multiple values into a single row:

$query = db_select('node', 'n')
         ->fields('n', array('nid', 'vid', 'title'));
$query->leftJoin('term_node', 'tn', 'n.vid=tn.vid');
$query->addExpression('GROUP_CONCAT(DISTINCT tn.tid)', 'tag_list');
$query->groupBy('n.nid');

The results of your query then look like:

1 First node  23,89
2 Second node 23

Make sure you tell the field mapping to expect multiple comma-separated values (and, since we're migrating tids rather than term names, to expect tids):

$this->addFieldMapping('field_tags', 'tag_list')
     ->separator(',');
$this->addFieldMapping('field_tags:source_type')
     ->defaultValue('tid');

Query in prepareRow

An alternative (and the only option if your source data isn't in MySQL) is to pull the data in after the initial query.

$this->addFieldMapping('field_tags', 'tag_list')
     ->description('Populated in prepare()');
$this->addFieldMapping('field_tags:source_type')
     ->defaultValue('tid');
...
public function prepareRow($row) {
  $row->tag_list = db_select('term_node', 'tn')
                   ->fields('tn', array('tid'))
                   ->condition('vid', $row->vid)
                   ->execute()
                   ->fetchCol();
}

$row->tag_list will be array(23, 89) for node 1, array(23) for node 2, etc.

Help improve this page

Page status: Not set

You can: