快速保存单个字段值


19

我的网站上大约有70k个指定类型的节点。我需要对它们进行更新。进行某些操作并将一个字段设置为所需值。node_save真的很慢,并且会导致崩溃(调用堆栈可能太长)。有没有一种更快的方法可以在此特定字段上写信息?

field_attach_update在一篇文章中提到过,但是速度并不快。

编辑:在此节点类型上有一个非常复杂的视图,但不适用于我要更新的字段。

Answers:


30

我一定会去的field_attach_update

这个想法很简单。只需加载节点并使用field_attach_update保存它。

例如:

$node = node_load($nid);
$node->field_name[LANGUAGE_NONE][0]['value'] = 'New value';
field_attach_presave('node', $node);
field_attach_update('node', $node);
  // Clear the static loading cache.
entity_get_controller('node')->resetCache(array($node->nid));

这不会更改任何时间戳或node_save通常调用的任何其他挂钩。加载节点还会调用一些钩子,因此可能效率不高。

如果您有这个想法,并且节点结构简直是简单的话,您也可以这样做:

 $node = new stdClass();
 $node->nid = $nid; // Enter the nid taken. Make sure it exists. 
 $node->type = 'article';
 $node->field_name[LANGUAGE_NONE][0]['value'] = 'New value';
 field_attach_presave('node', $node);
 field_attach_update('node', $node);
  // Clear the static loading cache.
 entity_get_controller('node')->resetCache(array($node->nid));

无论如何,如果您尝试更新除字段以外的任何内容,那么它将无效(评论状态,发布状态等)。另外,如果您使用的是node_save,则将使用不同的方法自动清除特定节点的缓存,我们需要使用“ entity_get_controller”将其清除。

更新: 看来您还应该调用field_attach_presave()让其他模块正确处理字段输入。例如,文件模块使用此钩子将其设置为永久文件状态。我已经更新了上面的2个示例。


我确实有nid,但是节点的结构不是那么简单,但是我要更新的字段却非常简单。从仅为现有节点设置一个字段(由nid标识但未加载)然后调用,我会有什么期望field_attach_update
Eloar

4
事实证明,使用实体查询正在减慢速度。因此,从一个到另一个从一个切换node_save到另一个非常有意义。从3小时更新到40分钟。field_attach_updateEntityFieldQuerydb_query_range
Eloar 2013年

2

如果您不希望在不引起标准事件和动作发生的情况下保存字段数据,则可以使用drupal_write_record

这是一个将Hello World插入id为1的article类型的节点的body字段的示例。

$values = array(
  'entity_type' => 'node',
  'bundle' => 'article',
  'entity_id' => 1,
  'revision_id' => 1,
  'language' => 'und',
  'delta' => 0,
  'body_value' => 'HELLO WORLD',
  'body_summary' => '',
  'body_format' => 'filtered_html',
);
drupal_write_record('field_data_body', $values);
drupal_write_record('field_revision_body', $values);

如果您的网站多变,那么您将要使用“ en”或内容的语言而不是“ und”。

如果要进行修订,则必须小心插入正确的修订ID,否则,您可以简单地插入与entity_id相同的值。

请注意如何将此数据插入到两个表field_data_ *和field_revision_ *中。您应该同时插入两者,以确保该站点能够按需工作。

运行此命令后,您将需要清除缓存以使字段显示出来,具体取决于缓存的设置方式。


2

对于像这样的简单更新,其中需要更新许多节点,我始终使用MySQL更新语句。是的,需要考虑缓存,但是您可以在完成后刷新缓存,这一切都很好。当然,您需要熟悉数据结构,但是在Drupal 6中它相对简单。(尽管在Drupal 7中太可怕了)


专为Drupal 7创建了项目。完成模块的所有部分后,可以直接在Drupal DB结构上进行操作以读取值和进行搜索,因为sql查询比EntityFieldQueries快得多。
Eloar 2014年

2

我也建议field_attach_update不要使用直接的SQL查询,因为sql不会更新节点缓存对象,而在下一个中node_load,您将不会加载更新的字段值,而是会加载旧值

field_attach_update 比直接SQL查询好得多。


Ayesh K建议在stdClass不加载的情况下将节点创建为对象。您知道如果尝试不设置所有字段就以这种方式更新节点会发生什么情况吗?会被null或默认值覆盖吗?也许这些会在更新过程中被忽略吗?
Eloar 2013年

1
仅在用户界面对必填字段进行验证时,才可以通过Ayeshs方法(新的stdClass等...)直接保存节点
pico34 2013年

我将尝试使用此方法更新节点而不设置所有字段,然后检查会发生什么。它可能是在模块更新过程(批量)中更新节点的最快方法。
Eloar 2013年

2

尝试了其他答案中提到的所有方法后,我的更新时间非常慢(对于具有20多个字段的节点类型的700.000个节点,更新时间约为7天),直到找到本文为止:http : //www.drupalonwindows.com/en/ Blog / only-update-changed-fields-或-properties-entity-drupal

在hook_update中实现以下代码之后,我将更新时间减少到2小时,我认为这是可以管理的。

if (!isset($sandbox['storage']['nids'])) {
    $sandbox['storage']['nids'] = [];
    $query = 'SELECT {nid} FROM node WHERE type = \'article\';';
    $result = db_query($query)->fetchCol();
    if ($result) {
      $sandbox['storage']['nids'] = $result;
      $sandbox['storage']['total'] = count($sandbox['storage']['nids']);
      $sandbox['storage']['last_run_time'] = time();
      $sandbox['progress'] = 0;
    }
  }

  $amount = 300;
  $nids = array_slice($sandbox['storage']['nids'], 0, $amount);

  if (!empty($nids)) {
    $nodes = node_load_multiple($nids, [], TRUE);
    foreach ($nodes as $node) {
      // Lets manipualte the entity.
      $article_wrapper = UtilsEntity::entity_metadata_wrapper('node', $node);
      // Eventual logic here.

        // Field to update
        $article_wrapper->my_field = 'my_value';
        $article_wrapper->save();

    $sandbox['progress']++;
    }
    $sandbox['message'] = 'Runs left: ' . (($sandbox['storage']['total'] - $sandbox['progress'])/$amount) . ' Progress: ' . (($sandbox['progress'] * $amount)/$sandbox['storage']['total']) . '%';
    $sandbox['storage']['last_run_time'] = time();
    unset($nids);
  }
  $sandbox['storage']['nids'] = array_slice($sandbox['storage']['nids'], 100, count($sandbox['storage']['nids']));
  if (!empty($sandbox['storage']['total'])) {
    $sandbox['#finished'] = ($sandbox['storage']['total'] - count($sandbox['storage']['nids'])) / $sandbox['storage']['total'];
  }
  return $sandbox['message'];

1

我什至有相同的要求,即为特定内容类型的所有节点更新字段。我使用了node_load_multiplefield_attach_update

$nodes = node_load_multiple(array(), array('type' => 'content_type_name'));
foreach ($nodes as $node) {
  $node->field_name['und'][0]['value'] = 'field value';
  field_attach_update('node', $node);
}

我通过匆忙跑了,很快。


0

您是否考虑过使用mySQL直接在数据库中进行这些更新?这可能是实现所需目标的最简单,最快的方法。

这是一个简单的例子。您可以从phpMyAdmin中的“ SQL”选项卡执行这样的命令。想象一下,您有一个名为“会员资料”的内容类型。其中有一个名为“成员类型”的字段(例如,公司,个人,组织)。假设您想将所有出现的“ COMPANY”更新为“ company”。下面的命令将执行此操作。

更新content_type_member_profile SET field_type_of_member_value='company'WHERE field_type_of_member_value='COMPANY';

另外,请检出MySQL入门


这是选项之一。我把它留作最后一个检查和实施。我不想过多地破坏Drupal的结构和缺陷。所以首先我直接通过Drupal API寻找任何解决方案。如果您可以提供一些示例,将不胜感激。
Eloar

Goog点。我在最初的回答中添加了一个简单的示例。
Bisonbleu

嗯,我确实很了解SQL,可以更新任何表中的任何记录。这不是问题。问题是,我对drupal存储数据(尤其是元数据)的方式还不够熟悉。在这样的系统中,总是有很多缓存,保留等内容,因此在最低级别进行更新可能会(并非总是)造成一些缺陷。因此,如果涉及到这一点,我将尝试在最低级别上做到这一点,并且我将为真正的混乱做好准备。
Eloar
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.