SalesforceのSOSLを最大限に活用する:開発者向けガイド

背景と適用シナリオ

Salesforceプラットフォームでアプリケーションを開発する際、データの検索は最も基本的な要件の一つです。多くの開発者は、特定のオブジェクトから条件に合致するレコードを取得するために SOQL (Salesforce Object Query Language) を日常的に利用しています。SOQLはSQLに似た構文を持ち、構造化されたクエリを実行するのに非常に強力です。しかし、ユーザーがどのオブジェクトにデータが存在するか分からない場合や、複数のオブジェクトにまたがる広範なテキストベースの検索を必要とする場合はどうでしょうか?

このようなシナリオで真価を発揮するのが、SOSL (Salesforce Object Search Language) 、日本語では「Salesforceオブジェクト検索言語」です。SOSLは、Google検索のように、単一の検索クエリで複数のオブジェクトのテキストフィールドを横断的に検索するために設計されています。これは、特にカスタムの検索機能を実装する際に、優れたユーザーエクスペリエンスを提供するための鍵となります。

具体的な適用シナリオとしては、以下のようなものが考えられます。

  • グローバル検索バーの実装: Lightning Web ComponentやAura Componentで、アプリケーション全体を対象とした検索機能を構築する際に、ユーザーが入力したキーワード(例:会社名、担当者名、電話番号の一部など)をもとに、取引先、取引先責任者、商談、カスタムオブジェクトなどから関連レコードを一度にリストアップする。
  • 重複データの確認: 新規にリードや取引先責任者を作成する前に、入力された氏名やメールアドレスが既存のレコードに存在しないかを複数のオブジェクトをまたいで確認する。
  • 非構造化データの検索: Salesforce Knowledgeの記事、添付ファイル(Chatter FeedやFiles)、ドキュメントなど、テキストコンテンツを主とするオブジェクトから関連情報を効率的に探し出す。

SOQLが「どのオブジェクトの、どの項目の値が、何であるか」が明確な場合のデータ取得に適しているのに対し、SOSLは「このキーワードを含むレコードは、どのオブジェクトにあるか」という、より曖昧で広範な検索ニーズに応えるための言語です。この違いを理解し、適切に使い分けることが、Salesforce開発者としてパフォーマンスと機能性の高いアプリケーションを構築する上で非常に重要です。


原理説明

SOSLは、Salesforceの強力な検索エンジンと検索インデックスを利用して動作します。SOQLがデータベースに直接クエリを発行するのとは異なり、SOSLは事前に構築されたテキストインデックスに対して検索を実行するため、特に大量のテキストデータに対する検索パフォーマンスが非常に高くなります。

SOSLクエリの基本的な構文は以下の通りです。

FIND '検索キーワード' IN [検索グループ] RETURNING [オブジェクト名(取得フィールド, ...), ...]

この構文の各要素を詳しく見ていきましょう。

FIND '検索キーワード'

検索の核となる部分です。シングルクォートで囲まれた文字列が検索対象となります。このキーワードには、単一の単語だけでなく、ワイルドカード(`*`:0文字以上の任意の文字列、`?`:任意の1文字)や論理演算子(`AND`, `OR`, `NOT`)を含めることができます。例えば、`'ACME* OR Universal'` のように複雑な条件を指定することも可能です。

IN [検索グループ]

検索の範囲を指定します。これにより、検索エンジンがどのフィールドを対象にインデックスをスキャンするかを制御できます。代表的な検索グループは以下の通りです。

  • ALL FIELDS: アクセス可能な全てのテキストフィールドを対象とします。(最も広範ですが、パフォーマンスに影響する可能性があります)
  • NAME FIELDS: オブジェクトの「名前」として定義されている標準フィールド(例:AccountのName, ContactのFirstName/LastName)を対象とします。
  • EMAIL FIELDS: メール形式のフィールドを対象とします。
  • PHONE FIELDS: 電話番号形式のフィールドを対象とします。

検索対象を絞り込むことで、より関連性の高い結果を迅速に得ることができます。

RETURNING [オブジェクト名(取得フィールド, ...)]

検索結果として返却するオブジェクトとそのフィールドを指定します。カンマ区切りで複数のオブジェクトを指定できます。例えば、`RETURNING Account(Name, Phone), Contact(FirstName, LastName, Email)` と記述すると、検索キーワードにヒットした取引先オブジェクトからは`Name`と`Phone`を、取引先責任者オブジェクトからは`FirstName`、`LastName`、`Email`を取得します。

ApexでSOSLクエリを実行すると、その結果は `List>` という特殊な形式で返されます。これはリストのリストであり、外側のリストの各要素が、`RETURNING`句で指定したオブジェクトに対応するレコードのリスト(内側のリスト)となります。例えば、上記の `RETURNING Account(...), Contact(...)` の場合、結果の `searchList[0]` には取引先のリストが、`searchList[1]` には取引先責任者のリストが格納されます。この順序は `RETURNING`句で指定した順序と一致します。


示例代码

ここでは、Salesforceの公式ドキュメントに基づいた、ApexでSOSLクエリを実行する具体的なコード例をいくつか紹介します。

基本的なSOSLクエリ

まず、'Wingo'というキーワードを全てのテキストフィールドから検索し、合致した取引先と取引先責任者のレコードを取得するシンプルな例です。

// 'Wingo' という単語を全てのテキスト項目から検索します。
// 検索結果として、取引先(Account)のNameとPhone項目、
// および取引先責任者(Contact)のFirstName, LastName, Email項目を返します。
List> searchList = [FIND 'Wingo' IN ALL FIELDS RETURNING Account(Name, Phone), Contact(FirstName, LastName, Email)];

// 検索結果は List> 型で返されます。
// RETURNING句で指定した順序で、各オブジェクトのレコードリストが格納されます。
// この例では、searchList.get(0) にはAccountのリストが、
// searchList.get(1) にはContactのリストが入ります。

// 最初の結果リスト(Account)を取得します。
List searchAccounts = (List)searchList[0];

// 2番目の結果リスト(Contact)を取得します。
List searchContacts = (List)searchList[1];

// デバッグログに結果を出力します。
System.debug('Found ' + searchAccounts.size() + ' accounts.');
System.debug('Found ' + searchContacts.size() + ' contacts.');

for (Account acc : searchAccounts) {
    System.debug('Account Name: ' + acc.Name);
}

for (Contact con : searchContacts) {
    System.debug('Contact Name: ' + con.FirstName + ' ' + con.LastName);
}

このコードは、SOSLクエリを実行し、返された `List>` をオブジェクトごとにキャストして処理する基本的な流れを示しています。

動的な検索キーワードの利用と結果の処理

次に、ユーザー入力など動的な値を検索キーワードとして使用し、複数のオブジェクトの結果をまとめて処理する、より実践的な例です。

public class SoslExample {
    public static void searchForTerm(String searchTerm) {
        // SOSLインジェクションを防ぐために、動的な値をサニタイズします。
        String sanitizedSearchTerm = String.escapeSingleQuotes(searchTerm);
        
        // 動的なSOSLクエリ文字列を構築します。
        // FIND句のキーワードはコロンでバインドする必要があります。
        String soslQuery = 'FIND \'' + sanitizedSearchTerm + '*\' IN ALL FIELDS ' +
                           'RETURNING Account(Name), Contact(FirstName, LastName), Lead(Name)';
        
        try {
            // Search.queryメソッドを使用して動的SOSLを実行します。
            List> searchList = Search.query(soslQuery);

            if (searchList != null && !searchList.isEmpty()) {
                Account[] searchAccounts = (Account[])searchList[0];
                Contact[] searchContacts = (Contact[])searchList[1];
                Lead[] searchLeads = (Lead[])searchList[2];

                System.debug('--- Search Results for: ' + searchTerm + ' ---');
                System.debug('Found ' + searchAccounts.size() + ' accounts.');
                System.debug('Found ' + searchContacts.size() + ' contacts.');
                System.debug('Found ' + searchLeads.size() + ' leads.');
            } else {
                System.debug('No records found for search term: ' + searchTerm);
            }
        } catch (QueryException e) {
            System.debug('SOSL query failed: ' + e.getMessage());
        }
    }
}

// 実行例:
// SoslExample.searchForTerm('GenePoint');

この例では、静的なSOSL(角括弧 `[]` を使う方法)ではなく、`Search.query()` メソッドを用いた動的SOSLを使用しています。これにより、実行時に検索キーワードを組み立てることができます。重要なのは、`String.escapeSingleQuotes()` を使ってユーザー入力をエスケープ処理し、SOSLインジェクションと呼ばれるセキュリティ脆弱性を防ぐことです。


注意事項

SOSLを効果的かつ安全に利用するためには、いくつかの重要な点に注意する必要があります。

権限 (Permissions)

SOSLは、実行ユーザーの権限と共有ルールを完全に尊重します。ユーザーがアクセス権を持たないオブジェクト、レコード、またはフィールドは、検索結果に含まれません。これにより、データセキュリティが担保されます。開発者は、ユーザーのプロファイルや権限セットによって検索結果が異なることを常に意識する必要があります。

API制限 (Governor Limits)

Salesforceプラットフォームの他の機能と同様に、SOSLにもガバナ制限が存在します。これらは、システムの安定性を保つために不可欠です。

  • 1トランザクションあたりのSOSLクエリ数: 1つのApexトランザクション内で実行できるSOSLクエリは20回までです。
  • 返却されるレコード数: 1回のSOSLクエリで返されるレコードの最大数は2,000件です。この上限を超えた場合、全ての合致するレコードが返されるわけではないことに注意が必要です。

ループ内でSOSLクエリを実行することは、ガバナ制限に抵触する典型的なアンチパターンですので、絶対に避けるべきです。

エラー処理 (Error Handling)

SOSLクエリ、特に動的クエリは、構文エラーや不正な入力によって `QueryException` をスローする可能性があります。そのため、`Search.query()` を使用する場合は必ず `try-catch` ブロックで囲み、例外を適切に処理することが推奨されます。また、検索結果が0件の場合も考慮し、`searchList` が `null` でないこと、および空でないことを確認してから処理を進めるべきです。

インデックス作成 (Indexing)

SOSLのパフォーマンスは検索インデックスに依存します。標準オブジェクトの主要なテキストフィールド(名前、メール、電話など)は自動的にインデックスが作成されます。しかし、カスタムオブジェクトのカスタムフィールドをSOSLの検索対象に含めたい場合、そのフィールドが検索可能である必要があります。通常、テキスト、テキストエリア、ロングテキストエリア、リッチテキストエリア、メール、電話の各データ型のフィールドは自動的にインデックスが作成されます。パフォーマンスを向上させたい特定のカスタムフィールドがある場合は、「外部ID」としてマークすることで、検索インデックスの対象に含めることができます。


まとめとベストプラクティス

SOSLは、Salesforceプラットフォーム上で強力かつ柔軟な検索機能を実装するための不可欠なツールです。複数のオブジェクトを横断し、非構造化データを含む広範なテキスト検索を高速に実行できる能力は、SOQLだけでは実現できない優れたユーザーエクスペリエンスを提供します。

Salesforce開発者としてSOSLを最大限に活用するためのベストプラクティスを以下にまとめます。

  1. SOQLとの使い分けを意識する:
    • SOSL: 検索対象のオブジェクトが不明、または複数のオブジェクトを横断してテキスト検索を行いたい場合。
    • SOQL: 検索対象の単一オブジェクトと条件が明確で、特定のレコードを精密に取得したい場合。
  2. 検索範囲を限定する: パフォーマンス向上のため、可能な限り `IN ALL FIELDS` ではなく、`IN NAME FIELDS` や `IN PHONE FIELDS` など、より具体的な検索グループを指定します。
  3. 動的クエリのセキュリティを確保する: ユーザーからの入力をSOSLクエリに含める場合は、必ず `String.escapeSingleQuotes()` を使用してサニタイズし、SOSLインジェクションを防止します。
  4. 結果の順序を理解する: `List>` の結果は、`RETURNING` 句で指定したオブジェクトの順序と一致することを前提にコードを記述します。これにより、予期せぬキャストエラーを防ぎます。
  5. ガバナ制限を考慮した設計を行う: 1トランザクション内でSOSLを複数回発行する場合は、発行回数を意識し、ループ内での実行を避けるなど、バルク処理を前提とした設計を心がけます。
  6. 空の結果を適切に処理する: 検索結果が常に存在るとは限りません。クエリ結果のリストが空でないことを確認するチェック処理を必ず実装します。

これらの原則を守ることで、あなたはSOSLの能力を最大限に引き出し、堅牢で高パフォーマンス、かつセキュアなSalesforceアプリケーションを構築することができるでしょう。

コメント