web-dev-qa-db-ja.com

MySQLを介して複数のテキストフィールド値を変更する必要がありますか、それとも他に望ましい方法はありますか?

サイトにはコンテンツタイプがあり、このコンテンツタイプには2つのフィールドがあります。

  • テキストフィールド
  • 用語参照フィールド

このコンテンツタイプには何千ものノードがあり、いくつかの値を変更したい(検索/置換)。

たとえば、すべてのノードのテキストフィールドのすべての「ABCD」文字列を「WXYZ」文字列に変更したいとします。

MySQL経由でこの変更を行うには良い方法ですか、それとも他に望ましい方法がありますか?(テキストフィールドの変更と分類用語の置換の両方)。

検索と置換スキャナー モジュールを見ましたが、いくつかの エラー が表示されました理由がわかります。また、大量のノードでタイムアウトエラーが発生します。

4
herci

Drupalの方法は、対象のノードを反復処理してフィールドを変更することです。記事のコンテンツタイプの[本文]フィールドを変更する方法を示す簡単な例を次に示します:

  $nodes = node_load_multiple(array(), array('type' => 'article'));
  foreach($nodes as $node) {
    // Only modify nodes that have something in the body field
    if (isset($node->body[$node->language][0]['value'])) {

      $text = $node->body[$node->language][0]['value'];
      $find = 'ABCD';
      $replace = 'WXYZ';
      $text = str_replace($find, $replace, $text);
      $node->body[$node->language][0]['value'] = $text;
      node_save($node);
    } 
  }

この方法でこれを行うことについて1つ注意する点は、すべてのノードの最終変更時刻が更新されることです。これが必要な場合と必要でない場合があります。タイムスタンプを変更せずにノードを更新する場合は、代わりにこれを使用します。

  $nodes = node_load_multiple(array(), array('type' => 'article'));
  foreach($nodes as $node) {
    // Only modify nodes that have something in the body field
    if (isset($node->body[$node->language][0]['value'])) {

      $text = $node->body[$node->language][0]['value'];
      $find = 'ABCD';
      $replace = 'WXYZ';
      $text = str_replace($find, $replace, $text);
      $node->body[$node->language][0]['value'] = $text;
      unset($node->created);
      unset($node->updated);
      field_attach_update('node', $node);
    } 
  }

編集:分類用語を変更する方法

同じ方法を使用して、分類用語を変更します。つまり、用語をロードし、反復して、それぞれに対して検索/置換を行います。

  $vocab = taxonomy_vocabulary_machine_name_load('my_vocabulary');
  $terms = entity_load('taxonomy_term', FALSE, array('vid' => $vocab->vid));

  foreach ($terms as $term) {
    $text = $term->name;
    $find = 'ABCD';
    $replace = 'WXYZ';
    $text = str_replace($find, $replace, $text);
    $term->name = $text;
    taxonomy_term_save($term);
  }

MySQLのエキスパートによっては、データベースフィールドを直接更新してみて成功するかもしれませんが、特にフィールドにリビジョンがある場合はエラーが発生しやすくなります。

4
Frank H.

実際のDrupalこれを行う方法は、変更するテキストを含むすべてのノードを見つける hook_update_N() を実装する更新スクリプトを作成することです次に、すべてのノードをバッチで更新します(これにより、タイムアウトが防止されます)。

更新スクリプトの大まかなレイアウトは次のようになります。コード内の私のコメントを読んでください。

_/**
 * Replaces all "ABCD" strings with "WXYZ" strings on text fields in all nodes.
 */
function mymodule_update_7100(&$sandbox) {
  // Set our $sandbox, which will stay persistent in multiple batch processes.
  if (!isset($sandbox['total'])) {
    // Find all nodes having 'ABCD' in a text field
    // Use your favorite method here, for the sake of this example I use EFQ.
    $query = new EntityFieldQuery();
    $query->entityCondition('entity_type', 'node');
    $query->propertyCondition('bundle', 'YOUR_CONTENT_TYPE');
    $query->fieldCondition('my_text_field', '%ABCD%', 'like');
    $result = $query->execute();

    // Put the node ids into the $sandbox
    $sandbox['nids'] = array_keys($result['node']);
    $sandbox['total'] = count($sandbox['nids']);
    // Set batch size to a number of nodes your server can process in a single
    // request. If you still get timeouts, try to reduce this number.
    $sandbox['batch_size'] = 10;
    $sandbox['current'] = 0;
  }
  else {
    // The sandbox is set, so we can process the nodes in batches. We load only
    // that much of nodes set in the $sandbox['batch_size']
    $offset = $sandbox['current'] * $sandbox['batch_size'];
    $current_nids = array_slice($sandbox['nids'], $offset, $sandbox['batch_size']);
    $nodes = node_load_multiple($current_nids);

    foreach ($nodes as $node) {
      // Change the text value here and save the node.
      // Again use your favorite method here. For the sake of this example, I
      // change the value directly on the $node object and save the $node.
      $text = $node->my_text_field['und'][0]['value'];
      $text = str_replace('ABCD', 'WXYZ', $text);
      $node->my_text_field['und'][0]['value'] = $text;
      node_save($node);
    }
  }

  // Check the progress and set $sandbox['#finished'] to 1 once all nodes are 
  // processed.
  $progress = $sandbox['current'] * $sandbox['batch_size'] / $sandbox['total'];
  $sandbox['#finished'] = $progress > 1 ? 1 : $progress;
}
_

上記のスクリプトをカスタムモジュールの_.install_-ファイルに挿入します。関数の名前を調整する必要があります。更新スクリプトに慣れていない場合は、hook_update_N()のドキュメントを参照してください。

次に、_update.php_を実行するか、drushコマンド_drush updb_を使用します。この更新スクリプトは、ifステートメントの最初の部分に設定されているすべてのノードが処理されるたびに呼び出されます。最初の呼び出しでは、ノードを検索し、ノードIDを_$sandbox_に配置します。その後の呼び出しでは、ノードを10のバッチでロードして処理します。

2
Елин Й.

ステップ1:ルールコンポーネントを作成する

Rules Componentのこの例を見てください( Rules エクスポート形式):

{ "rules_search_and_replace_values_in_a_text_field" : {
    "LABEL" : "Search and replace values in a text field",
    "PLUGIN" : "rule",
    "OWNER" : "rules",
    "REQUIRES" : [ "rules" ],
    "USES VARIABLES" : {
      "node" : { "label" : "Node", "type" : "node" },
      "old_value" : { "label" : "Old Value", "type" : "text" },
      "new_value" : { "label" : "New Value", "type" : "text" }
    },
    "IF" : [
      { "entity_has_field" : { "entity" : [ "node" ], "field" : "field_free_format_text" } },
      { "data_is" : {
          "data" : [ "node:field-free-format-text" ],
          "value" : "[old-value:value]"
        }
      }
    ],
    "DO" : [
      { "data_set" : {
          "data" : [ "node:field-free-format-text" ],
          "value" : "[new-value:value]"
        }
      },
      { "drupal_message" : { "message" : "String \u0022[old-value:value]\u0022 was replaced to string \u0022[new-value:value]\u0022 in field \u0022field_free_format_text\u0022 for node with id \u0022[node:nid]\u0022 and title \u0022[node:title]\u0022." } }
    ]
  }
}

上記の例をさらに説明するための詳細:

  • このルールコンポーネントは、後続のステップで使用されます。
  • 私の場合、マシン名field_free_format_textのテキストフィールドがあります。

ルール条件Entity has fieldを追加して、このフィールドをこのルールコンポーネントでさらに処理できるようにしたことにも注意してください。これは、「データ比較」条件を追加するときにルールUIで言及されていることでもあります。「」と表示されている場所で、データセレクターを使用して、ルールで使用できるデータにドリルダウンできます。エンティティフィールドを作成するにはデータセレクタに表示される場合は、「エンティティがフィールドを持つ」(または「コンテンツのタイプ」)」という条件を使用する必要がある場合があります。このEntity has field条件の重要性を説明するビデオチュートリアルについては、 データタイプとデータ選択 を参照してください。特に、そのビデオの13:30から17:30頃に示されているものを参照してください。

ここでは、選択したコンテンツタイプに関するルール条件を追加していないことに注意してください。これは、追加したい追加条件になる可能性があります。ただし、選択リストフィールドが必要なコンテンツタイプに対してのみ使用されている場合は、Entity has field条件で十分です。

必要に応じて、上記のエクスポートされたルールを取得(コピー)し、そのフィールドのマシン名のすべての出現箇所をフィールドのマシン名と一致するように変更してから、このルールコンポーネントを独自の環境にインポートするだけです。

これを行った後、自分のサイトのadmin/config/workflow/rules/componentsに移動し、ハイパーリンク「実行」を使用して、自分のサイトでそれをqa-testできます。そのリンクを使用した後、というボタンを使用しますSwitch to direct input mode。次に、いくつかのノード識別子を入力して、Executeモード。選択したノードIDに応じて(そのフィールドがあり、ルール条件が満たされている場合)、ノードが更新されます。

ルールがノードのすべてのバリエーションに最適であると確信したら、次のステップに進みます。

これは、ID = 48のノードに対してこのルールコンポーネントを実行したときに取得した(2)Drupalメッセージの例です。ここで、文字列「Pierre」を「Herci」に変更しました。

  • 文字列「Pierre」は、ID「48」、タイトル「Demo field_free_format_text replacement」のノードのフィールド「field_free_format_text」で文字列「Herci」に置き換えられました。

  • コンポーネントルール:テキストフィールドの値の検索と置換が実行されました。

そのメッセージの表示のバリエーションは、ルールを使用して(類似した)ログレコードを作成することである可能性があります...

注意:あなたは常に2番目のメッセージを受け取ります。しかし、最初のものは、実際に一致する場合にのみ表示されます...したがって、更新も実行されます。

ステップ2:作業の大部分をVBOに任せる

この手順は非常に簡単です。処理するすべてのノードの適切なビューを作成し、それを Views Bulkに変換します。操作 ビュー。 VBOが操作として実行することの手掛かりは...予想通りです:ステップ1のルールコンポーネントを実行します

方法がよくわからない場合は、 VBOでのルールコンポーネントの使用 "に関するビデオをご覧ください。さらに知りたい場合は、次の" 複数のパラメーターを持つルールコンポーネント "ビデオ。

もちろん、コンポーネントタイプによる処理、「a」特定の値のみへのビュー出力の絞り込みなど、上記のアプローチには複数のバリエーションがあります。しかし、それは簡単で、あなた自身の想像力にならなければなりません。

注意:一度に多くのノードを処理することは非常に困難です(メモリ要件など)。そのため、 " VBO設定の詳細 "についてのビデオも参照することをお勧めします... VBOを使用して一度にすべてを処理するように誘惑される前に...

ステップ3:簡単なバリエーション

この質問には数十(数百?)の単純なバリエーションがあり、上記の [〜#〜] vbo [〜#〜]Rules の組み合わせを使用して解決することもできます。 =。それらの1つだけを言及するには、「 選択したコンテンツ/ノードを一括削除する方法 ...への私の回答を見てください。

このアプローチで得られる柔軟性は、直接MySqlアプローチと比較した場合のこのアプローチの利点も示しています。

ハッピー [〜#〜] vbo [〜#〜] -ing while Rules -ing!

2
Pierre.Vriens

IMOは、これを解決するためにDrupal AP​​I関数(別の関数を呼び出すことができる)を使用する必要がないため、データベースへの直接SQLクエリを使用するのが最も速い方法です。

ノードの一部の値を変更するため、サイトのインデックスを再作成する必要があることに注意してください。

しかし、リビジョンも更新したい場合、これはそれを行う方法ではありません。

@ kiamlalunoコメントの後に編集

SQLクエリで変更を行った場合、およびいくつかのルール/トリガー/検証/その他がある場合、これらの変更は検出できないことに注意してください。

1