0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Salesforceの項目は「ある」と「使える」が別だった。外から自動化して踏んだ6つ

0
Last updated at Posted at 2026-08-17

この記事は Zenn に投稿したものの再掲です。
https://zenn.dev/reona777/articles/salesforce-fields-exist-vs-usable

教育系の事業所で現場の運営をしています。エンジニアではありません。2026年の春から Claude Code で社内ツールを作り始めて、5か月で6本を本番稼働させました。

前の記事では、自動化が無言で失敗する話を書きました。

ここまでの6本は Claude Code そのものの話でした。今回は道具ではなく、触った相手の話です。作ったツールのうち4本が Salesforce を読み書きしていて、そのたびに同じ種類の躓き方をしました。

先に前提を書きます。私はその組織の管理者ではありません。業種別の管理パッケージが載った既存の環境に、一利用者として毎日ログインしていて、そこへ外から API で読み書きしに行きました。画面は毎日見ています。つまり**「何がどこにあるか知っているつもり」の状態から始めた**わけで、今回の6つはほぼ全部、そのつもりが外れた話です。

結論から書きます。項目が「ある」ことと「使える」ことは、まったく別でした。 そして6つとも、スキーマを読んだだけでは分からず、数えて初めて分かりました。

以下、オブジェクト名は伏せて Lesson__c(予定)、Session__c(予定にぶら下がる対象者の記録)、Contact(人)と呼びます。実際は業種別パッケージなので接頭辞が付きますが、話の中身は変わりません。

1. 表示名が同じ項目が2つあって、選択肢が違った

最初に踏んだのがこれです。Contact にある「文理」という項目を更新したくて、describe で項目一覧を取ったら、「文理」が2つ出てきました。

humanities_sciences_cd_t__c   ラベル「文理(担当者)」  選択肢: 文系 / 理系
Humanities_Science_Code__c    ラベル「文理」          選択肢: 1 / 2 / 3

画面で編集していたのは前者です。後者は画面に出ておらず、値の体系がそもそも違いました。API名から中身は推測できません。 Humanities_Science_Code__c のほうがそれらしい名前をしているぶん、むしろ危ない。

Code のほうへ「文系」と書き込もうとすれば選択肢違いで弾かれるので気づけますが、もし両方が同じ選択肢を持っていたら、間違ったほうを黙って更新し続けていたはずです。

やったこと: 更新対象を決めるときは、ラベルではなく API名と選択肢の実際の値まで describe で確認してから書く。似た名前が複数出たら、片方は使わないと決めてコメントに理由を残す。

2. 項目はあるのに、7,000件あまり全部が空だった

次がこれです。Session__cStudent__c という Contact への参照項目があり、当然これで人に辿れると思いました。設計上もそこを入口にするつもりでした。

念のため数えたら、7,000件あまり、全部空でした。

SELECT COUNT(Id) FROM Session__c WHERE Student__c != null
-- 0

参照項目は定義されていて、画面にも項目として存在します。ただ誰も入れていない。運用がその項目を使っていないだけで、スキーマとしては何も間違っていません。

同じことが他にもありました。人のレコードにある拠点の項目も全件空で、絞り込みに使えません。

ここが今回いちばん効いた教訓です。 スキーマは「入れられるもの」を定義しているだけで、「入っているもの」は一切表していません。設計の前に数える。 これをやらないと、動くけれど何も返さないクエリを本番に置くことになります。前の記事に書いた「0件で正常終了」が、いちばん出やすい形で待っています。

3. 親から本体に辿り着けなかった

予定から対象者を引きたい、という要求は自然です。ところが Lesson__c にも、その親のテンプレートにあたるオブジェクトにも、人への参照がありません。

辿れるのは子の Session__c だけで、そこに名前が文字列で入っています。参照ではなく文字列です。

しかも、その子が全件には付いていませんでした。

対象期間の Lesson__c        5,344件
うち Session__c があるもの   5,217件
差の127件の内訳             下書き100 / 公開26 / 完了1

差の127件のうち100件は、そもそも除外対象の種別だったので問題ありません。困るのは公開状態の26件で、これは実在の予定でした。 つまり「子から辿る」を主経路にすると、26件が黙って落ちます。

やったこと: 主経路は別に持ち、子は補完としてだけ使う。しかも補完で得た名前は「推測」として扱い、名簿と完全一致したときだけ採用する。一致しなければ処理せず通知に回します。ここは前の記事の結論と同じで、別人に届くほうが害が大きいからです。

4. 同じ名前の項目が、親では日付・子では文字列だった

Lesson__c の日付項目は date 型です。なので子でも同じつもりで絞り込んだら、こうなりました。

MALFORMED_QUERY: value of filter criterion for field 'Lesson_Date__c'
must be of type string

子側の同名項目は文字列型でした。 日付として比較できません。同じ名前で、意味も同じで、型だけ違います。パッケージ側が転記のときに文字列で持たせているようです。

回避は親経由での絞り込みでした。

SELECT Id, Student_Name__c
FROM Session__c
WHERE Lesson__r.Lesson_Date__c >= 2026-07-01

やったこと: 子オブジェクトを直接絞りたくなったら、まず親から辿れないかを見る。エラーメッセージが親切だったので気づけましたが、これが文字列比較として通ってしまう条件だったら、また無言で0件です。

5. 同じ人が、複数の表記で存在していた

これは結局いちばん深く刺さりました。

予定の名前に埋め込まれている表記と、Contact の正式表記が違う人がいます。 姓名の間の空白なら正規化で吸収できます。実際 NFKC と空白除去は入れていました。吸収できなかったのは異体字です。

片方が常用漢字で、もう片方が異体字でした。「高」と「髙」、「辺」と「邊」のような関係です。字が1文字違うだけで、読みも本人も同じです。

同じ人です。人間が見れば分かります。プログラムから見れば別人で、しかもエラーは出ません。名寄せに失敗して、その人だけ通知が飛ばない。

担当者側でも似たことがあり、同一人物が姓名の間の空白の有無で分裂していて、集計すると2人分になっていました。こちらは正規化で吸収できる側です。

やったこと: 名前を突合キーにするのをやめる方向に寄せる。どうしても名前でしか繋がらない箇所は、どちら側の表記を正とするかを決めて固定しました。今回は「予定の名前と通知先の名簿」を正にしています。Contact 側を正にすると、正しいはずの表記を使ったせいで届かなくなる人が出るからです。

正しいデータを使うと壊れるという状況は、事前には想像できませんでした。

6. 権限が、データ設計の前提そのものを変えた

「いつ誰がこの項目を変えたか」を追いたくなり、履歴を見に行きました。

Contact の履歴に残っていたのは5項目だけで、私が追いたかった項目は入っていません。一方で予定オブジェクトのほうは手厚く、担当者の付け替えが13,000件超も残っていました。オブジェクトごとに追跡設定が違うわけです。

では追跡をONにすればいい、と思って設定画面を開くと、見えるのに保存できません。 接続に使っているユーザーのプロファイルにはアプリケーションのカスタマイズ権限が無く、設定は閲覧だけ許可されていました。権限を持つ管理者への依頼が必要で、そこまでする話ではないと判断して諦めました。

代わりの手段も、期待したほど残っていませんでした。

LastModifiedDate は最後の1回しか持ちません。誰かが後から直すと、それ以前は消えます。さらに、子レコードを作っても親の LastModifiedDate は動きません。

そして1つ、明確に読み間違えたものがあります。updated() を「その期間に更新されたレコード」だと思って使いましたが、これは違います。**「現在の最終更新日時がその期間にあるレコード」**しか返しません。あとから更新されたレコードは、過去の期間を指定しても出てきません。変更履歴の代わりにはなりません。

結局、証拠になったのは業務のデータ側でした。 過去に一括更新スクリプトを流したときの undo 用 JSON が残っていて、そのスクリプトは在職中の人だけを対象にする作りだったので、**「そのファイルに載っている=実行時点で在職扱いだった」**と読めます。履歴機能ではなく、自分が残した副産物のほうが証拠になりました。

設計の前に投げるようにした4本

6つとも共通して、スキーマを読んだ段階では気づけず、数えたら分かりました。 なので、SOQL を書く前に必ずこれを投げるようにしています。

その項目に値が入っているか

SELECT COUNT(Id) FROM 対象 WHERE 項目 != null

ゼロなら、その項目は存在しないものとして設計する。

参照や子レコードが全件に付いているか

SELECT COUNT(Id) FROM 
SELECT COUNT(Id) FROM 

差があるなら、差の中身の内訳まで見る。「ほぼ全部」は主経路にできません。

選択肢の実際の値

describe の picklist の値をそのまま見る。画面のラベルではなく、保存されている値です。

突合キーの重複と表記ゆれ

SELECT 名前, COUNT(Id) FROM 対象 GROUP BY 名前 HAVING COUNT(Id) > 1

ここに何か出たら、名前をキーにするのは諦めます。

AIにSOQLを書かせるときに足りないもの

シリーズで一貫して書いていることが、ここでも同じ形で出ました。

AIに SOQL を書かせると、きれいに通るクエリが出てきます。 存在する項目名を使うので構文エラーになりません。参照項目があれば当然のように辿ってくれます。

ただし、その項目が全件空であることは、メタデータのどこにも書いていません。 型もラベルも参照先も定義されていて、定義上は完璧に使えます。使えないのは運用の結果であって、スキーマの外にある情報です。

なので、渡すものをスキーマから「数えた結果」に変えました。

この項目は7,000件あって全部 null なので使えない。人に辿るには子の名前(文字列)経由しかなくて、その子は127件欠けている。欠けた中に実在のものが26件あるので、主経路にはしないで。

こう伝えると、最初から補完扱いの設計が出てきます。AIが持っていないのは構文ではなく、その組織で実際にどう運用されているかでした。 そしてそれは、自分で数えるしか手に入れる方法がありません。

最後に

Salesforce を外から触り始めたときに一番効くと思っていたのは SOQL の書き方でしたが、実際に効いたのは数える習慣のほうでした。

画面を毎日見ているぶん「知っているつもり」が強く、それが全部の躓きの入口になっています。画面に見えているのは、権限と運用でフィルタされた後の姿で、API から見えるものとは違いました。

これから Salesforce まわりの実務を増やしていくので、この手の話は続けて書きます。

ほかに作ったものは GitHub に置いています。

書いている人

エンジニアではありません。勤務先の業務ツールを Claude Code で作って運用していて、作ったものと、壊れたときに直した話を書いています。

Claude Code の実務運用については X(@KouritsuONI)でも書いています。スプレッドシート・GAS・LINE・Salesforce まわりの業務自動化について、ご相談は X のDMからどうぞ。

この記事のシリーズ

Claude Code の実務運用について、順に9本書いています。

  1. 非エンジニアがClaude Codeで社内ツールを6本 本番稼働させるまでにやったこと
  2. AIに作らせたツールが本番で壊れた5つの原因と、直し方
  3. Claude CodeにCONTEXT.mdとSPEC.mdとADRを書かせると、途中で破綻しなくなる
  4. Claude Codeのスラッシュコマンドには、手順ではなく踏んだ罠を書く
  5. Claude Codeのメモリに53本ためて分かった、書く価値のある事実とない事実
  6. 実行は成功、でも誰にも届いていない。無言で失敗する自動化に気づく仕掛け
  7. Salesforceの項目は「ある」と「使える」が別だった。外から自動化して踏んだ6つ(この記事)
  8. Salesforceのパスワードを変えたら、連携が8箇所いっせいに止まった
  9. Salesforceのログインが2027年6月に廃止される。自動化10本を調べたら、直す場所は1つではなかった
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?