Deep Knowledge(DeepThrive)

HubSpotで「最初か直近しか取れない」関連レコードの制約を回避する

作成者: 原田 将寛|Oct 21, 2026, 10:50:26 AM

「このコンタクト、どのキャンペーン経由なのか一覧で出せますか」

営業やマーケティングの担当者から、こんな依頼を受けたことはないでしょうか。

「代理店経由で接点を持ったコンタクトだけリストにしたい」 「複数のキャンペーンに接触している人を抽出したい」 「スプレッドシートに書き出して、キャンペーンごとの内訳を見たい」

レコードの詳細画面を開けば、紐づいているキャンペーンは一覧で表示されています。データはHubSpotの中にある。それなら難しくないはずだ、と思って作業を始めます。

ところが、ワークフローで関連レコードの値を持ってこようとすると、選択肢が「最初に関連付けたレコード」と「最も最近関連付けたレコード」しかありません。3件紐づいていても、取得できるのは1件だけです。

リストの絞り込み条件を見ても、関連レコードのプロパティは出てきません。レポートビルダーでも同じです。

結果として、こういう対応になりがちです。

  • 依頼者に「レコードを1件ずつ開いて確認してください」と伝える
  • 書き出したデータが実態の一部しか反映していないまま渡してしまう
  • 手作業で突き合わせた一覧を、定期的に作り続ける

これは設定ミスではありません。HubSpotの仕様上の制約です。ただし、回避する方法があります。

なぜ「最初」か「直近」しか取れないのか

HubSpotは関連付けを順序でしか識別していません。「1番目」「最後」という位置は指定できますが、「2番目」や「特定の条件に合うもの」を指す手段が標準では用意されていません。

そして標準のリストやレポートは、基本的に1つのレコードを1行として扱う設計になっています。1件のコンタクトに3件のキャンペーンが紐づいているという「1対多」の関係を、1行の中に展開する仕組みがありません。

つまり課題の本質は、データが無いことではなく、1対多の情報を1行で扱える形に変換する手段が無いことです。

コードを書く前に:標準機能で足りるかの見極め

先に確認しておきたいことがあります。やりたいことによっては、標準機能で解決します。 ここを飛ばしてコードを書くと、不要な保守対象を抱えることになります。

ケース1:件数や合計だけが必要 → ロールアップ計算プロパティ

「紐づく契約の件数」「関連する商談の金額合計」のように、数を数えるか合計するだけなら、計算プロパティのロールアップ機能で対応できます。コードは不要です。

ケース2:特定の1件を取りたい → 関連付けラベル

「主キャンペーンの名前だけ欲しい」「初回接触のものだけ欲しい」といった場合、関連付けにラベルを付けるのが有効です。

先に触れたとおり、HubSpotは関連付けを順序でしか識別できません。逆に言えば、ラベルという別の識別軸を用意すれば、順序に依存せず狙った1件を指定できるようになります。「主キャンペーン」「初回接触」といったラベルを運用ルールとして定義するほうが、コードよりも保守が軽く済みます。

ケース3:選択肢が固定的 → 複数チェックボックスへの追加

集約したい値の種類が限られていて、今後増えないなら、複数のチェックボックス型プロパティを使う方法があります。ワークフローの「レコードを編集」には、複数チェックボックスに対して既存の値を消さずに追加する設定があるためです。

ただし選択肢を事前に登録しておく必要があるため、外部システム連携などで値が増え続ける場合には向きません。

コードが必要になるのは

上記に当てはまらない場合、つまり

  • 紐づく全件の情報が必要
  • 値の種類が事前に確定していない(外部連携で増えるなど)
  • 関連付けの解除にも追従させたい

このときに、ワークフローのカスタムコードを使います。以降がその方法です。

集約プロパティを持たせると、扱えるようになる

考え方はシンプルです。関連レコードの情報を、親レコードのプロパティ1つにまとめて保持しておく。

コンタクトに「累積キャンペーン名」というプロパティを用意し、紐づく全キャンペーン名を区切り文字でつないで格納します。

テキストのプロパティになった時点で、HubSpotの標準機能がそのまま使えるようになります。

できること 具体例
リストで絞り込む 「累積キャンペーン名に『代理店』を含む」でコンタクトを抽出レポートの軸にする 
レポートの軸にする 接触キャンペーンの組み合わせパターン別に件数を集計
書き出す スプレッドシートやCSVに、紐づく全件を含めて出力
一覧で確認する ビューの列に追加し、レコードを開かずに把握

管理者側の変化としては、「その依頼はHubSpotの仕様上できません」と答える場面が減るのが実際のところだと思います。一度作れば、以降の類似依頼は標準機能の範囲で対応できます。

実装の前に決める、3つの設計方針

手順に入る前にここを押さえておくと、後で作り直しになりません。運用してみると効いてくる部分です。

方針1:「追記」ではなく「作り直し」にする

いちばん自然に見えて、いちばん壊れやすいのが追記型です。関連付けが増えたときに、既存の値へ名前を足していく方式です。2つの理由で必ず実態とズレます。

  • 関連付けを解除しても値が残る。 しかも間違っていることに気づけません
  • 並び順が追加順になる。 同じ組み合わせでも文字列が一致せず、突合や比較に使えません

そこで、処理が走るたびに関連レコードを全件読み直し、文字列をゼロから作り直す方式にします。何回実行しても同じ結果になるため、ズレが原理的に発生しません。追加にも解除にも自動で追従します。

処理量は増えますが、後述のとおり実務上のコストはわずかです。正しさが自動で保たれる設計を選ぶほうが、長期的には安く済みます。

方針2:区切り文字にカンマを使わない

CSVやスプレッドシートに書き出したときに列が割れます。値そのものにカンマが含まれている場合、元の区切りが復元できなくなります。|(縦棒)や ;(セミコロン)を使ってください。

方針3:値が取れなかったときは書き込まない

APIの一時的なエラーなどで取得件数がゼロになったとき、そのまま書き込むと蓄積していた値を空で上書きしてしまいます。「取得ゼロなら何もしない」という分岐を1つ挟んでおいてください。

設定手順

前提条件

  • Data Hub Professional以上(旧Operations Hub)が必要です。ワークフローの「カスタムコード」アクションを使います
  • コードは40行程度です。ゼロから書く必要はありませんが、冒頭の設定値が何を指しているかは理解しておく必要があります

Step 1. 集約先のプロパティを作る

親オブジェクト(例:コンタクト)に、値を格納するプロパティを作成します。

  • フィールドタイプ:複数行テキスト
  • 名前の例:累積キャンペーン名

作成後、内部名を控えてください。設定 > プロパティ から該当プロパティを開くと表示されています。

HubSpotのプロパティには、画面表示用のラベルとシステムが使う内部名の2つがあります。コードが受け付けるのは内部名だけです。日本語ラベルで作成すると内部名がローマ字になることが多く、推測は当たりません。必ず実物を確認してください。

Step 2. 必要なIDと内部名をそろえる

コードに埋め込む値を先に集めます。

必要なもの 具体例
関連オブジェクトのID(2- で始まる数字)  設定 > オブジェクト、または設定画面のURL
集約したいプロパティの内部名  設定 > プロパティ で該当オブジェクトを選択

プロパティ一覧画面の「エクスポート」を使うと、ラベルと内部名の対応表がまとめて取得できます。内部名は今後も必要になるので、1回落としておくと以降が楽です。

Step 3. Private Appでアクセストークンを用意する

設定 > 連携 > Private Apps から新規作成し、次のスコープ(権限)を付与します。

  • 親オブジェクトの読み取り(例:crm.objects.contacts.read)
  • 関連オブジェクトの読み取り(カスタムオブジェクトなら crm.objects.custom.read)

片方だけでは動きません。 関連レコードを取得する処理は、親と子の両方の読み取り権限を参照しています。ここは実際につまずきやすい箇所です。

書き込み権限は不要です。プロパティへの反映は、後続の「レコードを編集」アクションが行うためです。必要最小限の権限に絞るという観点でも、読み取りのみに留めてください。

作成後に表示されるアクセストークンを控えます。なお、後からスコープを変更するとトークンが変わる場合があります。その際は次のStepで登録した値も更新してください。

Step 4. ワークフローを作る

親オブジェクト(コンタクト)ベースのワークフローを作成します。

トリガーは「関連レコード数の変更」にします。

親オブジェクトに、関連レコードの件数を数えるロールアップ計算プロパティ(例:関連キャンペーン数)を作り、「この値が変更されたとき」を登録条件に設定します。

関連付けの追加でも解除でも件数が変化するため、どちらのケースでも自動的に発火します。方針1の「解除に追従する」を成立させているのは、この仕組みです。

再登録(同じレコードが条件を再度満たしたときに再度登録する)はオンにしてください。

Step 5. カスタムコードアクションを設定する

シークレットの登録

アクション内の「シークレット」で、Step 3のアクセストークンを登録します。ここで付けた名前が、そのままコード内で参照する名前になります。 HS_TOKEN として登録したなら、コードでは process.env.HS_TOKEN と書きます。名前が一致していないと認証エラーになります。

コードに含めるプロパティ

空のままで構いません。このコードは親レコードのIDだけを使い、必要な情報はAPI経由で取得します。

コード

冒頭の設定部分だけ、環境に合わせて書き換えてください。

処理 役割 省略できるか
ページング(while (after)) 関連が500件を超える場合に続きを取得 件数が少なくても残しておく
100件ずつの分割 値の一括取得は1回100件が上限  省略不可。101件で停止します
if (!body.results) エラー応答を検知して原因をログに出す 残す。切り分けの手間が変わります
重複除去と並び替え 同じ文字列が安定して生成されるようにする 突合に使うなら残す
文字数の上限処理 プロパティの上限を超えないようにする 残す


出力フィールドの定義

アクション下部の「データ出力」で、ruisekikyanpenmeiを文字列型として追加します。

コード側で値を返していても、このフォームで宣言しないと後続アクションから参照できません。「出力フィールドで定義されていません」という注意が表示されていたら、この設定が漏れています。

Step 6. 書き込みアクションを追加する

カスタムコードの後に、2つのアクションをこの順番で置きます。


  1. レコードを編集:累積キャンペーン名 にruisekikyanpenmeiを設定する

順番が重要です。分岐を先に置くことで、方針3で触れた「空で上書きしてしまう」事故を防げます。

Step 7. テストする

関連レコードが2〜3件紐づいているレコード1件で実行し、実行履歴のログを確認します。

確認ポイント1:3つの件数がそろっているか

  • 関連=3件 / 値取得=3件 / ユニーク=3件 
  • 関連=3件 / 値取得=0件 → プロパティの内部名が違う、またはスコープが足りません
  • 関連=3件 / 値取得=1件 → 一部のレコードでプロパティが空欄になっています(データ側の課題)
  • 関連=3件 / ユニーク=2件 → 同じ値のレコードが重複しています(想定どおりか確認)

確認ポイント2:解除したときに値が減るか

関連付けを1件解除して再実行し、値が3件から2件に減ることを確認してください。

これが「作り直し方式」になっているかを確かめる唯一のテストです。 ここで減らない場合、追記型になっているか、トリガーが解除を拾えていません。追加のテストだけで済ませると、この不具合は本番稼働から数か月後に「数字が合わない」という形で発覚します。

運用と管理で押さえておくこと

管理者・情報システム部門の立場から、稼働後に関わってくる点をまとめます。

反映には数分のラグがある

ワークフローは即時実行ではありません。加えてトリガーに使うロールアップ計算プロパティの更新にも時間がかかります。関連付けの操作から反映まで、数分程度の遅れを見込んでください。

「今すぐ画面で確認したい」という用途には向きません。リストやレポート、定期的な書き出しといった用途が適しています。

API利用量への影響

1回の実行で消費するAPIコールは、関連レコード100件あたり1回+関連取得の1回です。関連が3件のコンタクトなら2回で済みます。

Private AppのAPIコール数には1日あたりの上限があります。通常の運用で問題になることはまずありませんが、初回に既存データへ一括適用する場合は件数を確認してください。数万件のコンタクトを対象にする場合、日を分けて実行するなどの配慮が必要です。

なお、カスタムコードには実行時間の上限があります。1レコードに数千件が紐づくケースがある場合は、事前にテストしてください。

トークンの管理

Private Appのアクセストークンは、発行者と保管場所を決めておいてください。担当者の異動時に「誰が発行したか分からない」状態になると、スコープの変更や再発行ができなくなります。

読み取り権限のみに絞っておけば、万一の漏洩時の影響範囲も限定されます。Step 3で書き込み権限を付けない理由はここにもあります。

設定値を記録して属人化を避ける

この仕組みは、コード冒頭の設定値さえ分かれば誰でも保守できます。逆にそれが不明だと、コードを読み解く作業が発生します。次の表をドキュメントとして残しておくことを勧めます。

記録項目 例
ワークフロー名 【コンタクト】累積キャンペーン名の更新PARENT_OBJ 
PARENT_OBJ contacts
CHILD_OBJ 2-227687479(キャンペーン)
NAME_PROP campaign_name(キャンペーン名)
格納先プロパティ ruisekikyanpenmei(累積キャンペーン名)
シークレット名 HS_TOKEN
Private App名 / 発行者 累積プロパティ更新用 / 情報システム部
トリガー用プロパティ 関連キャンペーン数(ロールアップ計算プロパティ)

同じ仕組みを他のオブジェクトへ展開するときも、この表を複製して値を差し替えるだけで済みます。

つまずきやすいポイントと切り分け

実行履歴のログに出る内容から、原因を特定できます。

ログに出る内容 原因 対処
MISSING_SCOPES / 403 権限不足 Private Appに親・子両方の読み取り権限を追加
Authentication credentials not found / 401 トークンが無効 トークンを再取得し、シークレットの値を更新
OBJECT_NOT_FOUND / 404  IDが違う オブジェクトIDとレコードIDを確認
Cannot read properties of undefined エラー応答を受け取っている オブジェクトIDとレコードIDを確認
値取得=0件 プロパティの内部名が違う 設定 > プロパティ で内部名を再確認
出力が後続で選べない 出力フィールドの宣言漏れ 「データ出力」フォームに追加

コード内の if (!body.results) によるエラー確認とログ出力は、稼働後も消さずに残してください。何か起きたときに、実行履歴を見るだけで原因が絞れます。設定変更やHubSpot側の仕様変更があったときに、ここが効いてきます。

応用できる場面

同じ考え方は、オブジェクトとプロパティを差し替えるだけで転用できます。

  • 取引先に、紐づく全契約の契約名をまとめる — 拠点や事業部ごとの契約状況を、会社レベルの一覧で把握する
  • 商談に、紐づく全製品名をまとめる — 提案内容の組み合わせパターンを分析する
  • コンタクトに、参加した全イベント名をまとめる — 接点の履歴をリストの条件に使う

また、values.join() の部分を変えれば、集約の内容自体を変更できます。

  • 条件付きの件数:特定のステージのものだけ数える
  • 条件付きの合計:特定の区分に該当する金額だけ足す
  • 最新の1件:日付で並べ替えて先頭を取る

ロールアップ計算プロパティでは条件を細かく指定しづらい場面があります。「集計はしたいが、条件が標準機能の枠に収まらない」というときに、この形が使えます。

まとめ

HubSpotの標準機能は「1レコードに1つの関連先」を前提に設計されています。複数紐づく情報を扱いたい場面では、集約した結果をプロパティとして保持しておくことで、リストもレポートも書き出しも、いつもの操作の範囲で扱えるようになります。

進める順番は次のとおりです。

  1. 標準機能で足りないかを先に確認する。 件数や合計だけならロールアップ計算プロパティ、特定の1件なら関連付けラベルで済みます
  2. コードを使う場合は、追記ではなく作り直しにする(解除に追従し、並び順が安定する)
  3. 区切り文字にカンマを使わない(書き出したときに壊れない)
  4. 取得ゼロなら書き込まない(蓄積した値を守る)
  5. 解除して値が減るかをテストする(設計の合否を決める唯一の確認)
  6. 設定値を表にして残す(保守を属人化させない)

実装そのものは1つのワークフローで完結します。関連レコードの情報が扱えないことで手作業や確認作業が発生しているなら、検討する価値のある方法です。

自社の環境で適用できるか判断がつかない場合や、どの方法が適しているかを整理したい場合は、お気軽にご相談ください。