быстрое сохранение значения одного поля

19

У меня на сайте около 70 тыс. Узлов указанного типа. Мне нужно запустить обновление на них. Некоторые операции и установка одного поля на желаемое значение. node_saveдействительно медленный и вызывает сбои (возможно, слишком длинный callstack). Есть ли более быстрый способ написать информацию об этом конкретном поле?

Там было field_attach_updateупомянуто в одном посте, но это не намного быстрее.

РЕДАКТИРОВАТЬ: Существует довольно сложное представление, построенное на этом типе узла, но не работает в этом поле, которое я хочу обновить.

Eloar
источник

Ответы:

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. Загрузка узла также вызовет некоторые хуки, поэтому, вероятно, это не так эффективно.

Если у вас есть nid и если структура узла очень проста, вы также можете сделать это следующим образом:

 $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 моих примера выше.

AyeshK
источник
У меня есть nid, но структура не так проста для узла, но поле, которое я хочу обновить, очень просто. Что я мог ожидать от установки только одного поля для существующего узла (идентифицируемого по nid, но не загруженного) и затем вызова field_attach_update?
Элоар
4
По мере того, как это происходило с использованием сущностного запроса, все замедлялось. Так переход от node_saveк field_attach_updateи от EntityFieldQueryк db_query_rangeбыло весьма полезным. С 3ч обновление до 40 минут.
Eloar
2

Если вы не хотите сохранять полевые данные, не вызывая стандартных событий и действий, вы можете использовать drupal_write_record .

Вот пример для вставки Hello World в поле body для узла типа article с идентификатором 1.

$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».

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

Обратите внимание, как эти данные вставляются в две таблицы field_data_ * и field_revision_ *. Вы должны вставить в оба, чтобы убедиться, что сайт работает как нужно.

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

Thomas4019
источник
2

Для такого простого обновления, как это, где нужно обновить множество узлов, я всегда использую оператор обновления MySQL. Да, кеширование необходимо принимать во внимание, но вы можете просто очистить кеш после того, как закончите, и это все хорошо. Вы, конечно, должны быть знакомы со структурой данных, но в Drupal 6 это относительно просто (хотя в Drupal 7 это ужасно)

Трой Фрех
источник
Проект был создан для Drupal 7. В конце концов, некоторые части моего модуля были созданы для непосредственного управления структурой БД Drupal для чтения значений и поиска, поскольку запросы sql выполнялись намного быстрее, чем EntityFieldQueries.
Элоар
2

Я field_attach_updateтоже предлагаю , а не прямой SQL-запрос, потому что sql не обновляет объект кэша узла, и в следующий раз node_loadвы не загрузите обновленное значение поля, вы загрузите старое значение

field_attach_update это намного лучше, чем прямой запрос SQL.

pico34
источник
Айеш К предложил создать узел как stdClassобъект без загрузки. Знаете ли вы, что может произойти, если я попытаюсь обновить узел таким образом, не устанавливая все поля? Будут ли они перезаписаны нулями или значениями по умолчанию? Может, они будут проигнорированы при обновлении?
Eloar
1
Можно сохранить узел напрямую методом Ayeshs (новый stdClass и т. Д.), Только пользовательский интерфейс проверяет необходимые поля
pico34
Я попытаюсь обновить узел этим методом, не устанавливая все поля, и проверю, что произойдет. Это может быть самый быстрый способ обновления узлов в процедуре обновления модуля (пакетной).
Eloar
2

Перепробовав все подходы, упомянутые в других ответах, я получил очень медленное время обновления (около 7 дней для 700 000 узлов типа узла с 20+ полями), пока не нашел эту статью: http://www.drupalonwindows.com/en/ blog / only-update-changes-fields-or-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'];
dasj19
источник
1

У меня даже было такое же требование обновления поля для всех узлов определенного типа контента. Я использовал node_load_multiple и field_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? Это, наверное, самый простой и быстрый способ достичь того, чего вы хотите.

Вот простой пример. Вы можете выполнить такую ​​команду на вкладке «SQL» в phpMyAdmin. Представьте, что у вас есть тип контента, который называется Профиль участника. В нем есть поле с именем «Тип участника» (например, компания, частное лицо, организация). Допустим, вы хотите обновить все вхождения для «КОМПАНИЯ» на «компания». Следующая команда сделает это.

ОБНОВЛЕНИЕ content_type_member_profile SET field_type_of_member_value= 'company' WHERE field_type_of_member_value= 'COMPANY';

Кроме того, оформить заказ Начало работы с MySQL

Bisonbleu
источник
Это один из вариантов. Я оставляю это как последний, чтобы проверить и осуществить. Я не хочу сильно портить структуру и недостатки друпала. Итак, сначала я ищу любые решения непосредственно через Drupal API. Если бы вы могли привести несколько примеров, это было бы очень ценно.
Элоар
Гуг точка. Я добавил простой пример к своему первоначальному ответу.
Bisonbleu
да, я знаю SQL достаточно хорошо, чтобы обновлять любые записи в любых таблицах. Это не проблема. Проблема в том, что я недостаточно знаком с тем, как drupal хранит данные (особенно метаданные). В таких системах всегда много кеширования, сохранения и т. Д., Поэтому его обновление на самом низком уровне может (не всегда) испортить некоторые недостатки. Так что, если до этого дойдет, я постараюсь сделать это на самом низком уровне, и я буду готов к некоторому настоящему беспорядку.
Элоар