SOQLクエリのパフォーマンスチューニング
by Tetsuo Ajima on 9月 17, 2010 at 09:44 午後
最初に確認すべきポイント
まず最初に確認すべきポイントは、SOQLクエリの検索条件です。
SOQLクエリの検索条件にインデックス化された項目が含まれているかどうかを確認してください。
[Select Field1__c, Field2__c, Field3__c From CustomObject__c Where Field1__c = :value]
上記の場合、データベースにて Field1__c のインデックスが作成されている必要があります。
インデックスの作成
データベース内部で項目のインデックスを作成させるには、項目の編集画面にて「外部 ID」チェックボックスをONにします。
「外部ID」項目はUpsert処理のキーに利用する項目ですが、「外部ID」に指定された項目はデータベース内部でインデックスが作成されるので、パフォーマンスチューニングのためにこの設定をするという使い方が可能です。
「外部ID」項目は、1つのオブジェクトに対してデフォルトで3つまで指定できます。
ただし、インデックス化された項目を検索条件にした場合の絞り込みがデータ全体の1/10以下になるようなデータ分布でないと、インデックス作成の効果は無いことに注意してください。
(追記: データ全体の件数が100万件以上になる場合は1/20以下に絞り込めないとインデックスとしての効果がありません。)
また、Id項目やName項目、監査日付項目(例: LastModifiedDate)などはデフォルトでインデックス化されています。IdやNameを検索条件にした検索は高いパフォーマンスを期待できます。
(追記: 標準項目のインデックスは、データ全体の30%以下に絞り込めること、かつ絞り込んだ件数が33万3千件以下である必要があります。カスタム項目のインデックスは、絞り込んだ件数が100万件以下である必要があります。)
検索条件にインデックス化された項目が複数含まれる場合
検索条件にインデックス化された項目が複数含まれる場合は、Force.comプラットフォームのクエリオプティマイザが実行計画を立てる際に、効果が高いと判断された項目が優先して使用されます。
データ件数が多い場合
検索対象のオブジェクトに格納されているデータ全体の件数が多いと検索のパフォーマンスに影響します。
筆者の経験では、Apexトリガなら10万件、Visualforceコントローラやレポート・リストビューの実行であれば100万件を超えるような場合に、検索条件となる項目のインデックスが作成されているかどうかに注意すべきです。とは言え、利用が進むにつれてデータ量が増大することも考慮し、またより快適なレスポンスを得るためには検索条件となる項目を常にインデックス化することを推奨します。
Triggerでは10万件以上のデータから検索する時はインデックス化された項目が検索条件に含まれていないとエラーになる場合があります。TriggerではDMLが発行されてからコミットされるまでの間の処理を行うため、高い応答性能が求められるためです。
削除済みデータの考慮
Force.comアプリケーションのデータは次の3種類の状態があります。
1. Activeなデータ。通常の操作で参照・更新可能な状態
2. 削除され、ごみ箱に入っているデータ。ごみ箱から復元可能な状態
3. ごみ箱から削除され、内部的に物理削除されるのを待っている状態のデータ。画面上からは確認不可
上記の1.の状態のみでなく、2.および3.の状態で存在するデータの件数も検索のパフォーマンスに影響します。3.の状態のデータは一定間隔でバックグラウンドで物理削除されますが、大量のデータを削除した直後はクエリオプティマイザによるクエリ実行計画の最適化に反映されるまで1~2日程度かかる場合があります。
ちなみに、2.および3.の状態のデータは、SOQLクエリに isDeleted=TRUE および ALL ROWS を付加することによって取得・参照することができます。
[Select Field1__c, Field2__c, Field3__c From CustomObject__c Where isDeleted=TRUE ALL ROWS]
その他のチューニングの手段
Force.comプラットフォームにはDivisionという、データベースパーティションのような概念を持った機能があります。Divisionの方がパフォーマンス改善に対する効果は高いのですが、いろいろな制約があります。Divisionについてはまた別の機会に述べたいと思います。
レポートおよびリストビュー
本稿ではSOQLクエリの実行にフォーカスして述べていますが、レポートやリストビューの実行にもまったく同様のチューニングが適用可能です。レポートやリストビューの抽出条件に利用する項目がインデックス化されるようにすることで、レスポンスの改善を期待できます。
日々成長しています
ここに述べたようなチューニングを適切に行えば、数千万件のデータに対するクエリも実用上問題なく利用できます。弊社内で行ったテストでは、1億件以上のデータに対するクエリも実用的な応答性能が得られたという実績もあります。
Salesforceのデータセンターでは、24時間体制で常にサーバ負荷や応答性能の監視を行っています。インフラの増強も必要に応じて随時行っておりますので、ここで述べたようなパフォーマンスチューニングのノウハウも近い将来通用しなくなるかもしれません。サービスベンダーにより日々メンテナンスされ必要に応じた増強が行われるのもクラウドのメリットですね :-)
Force.comプラットフォームのトランザクション制御
by Tetsuo Ajima on 9月 2, 2010 at 02:45 午後
開発の現場から良く出る質問にトランザクション制御関連が多いのでまとめてみました。
トランザクションスコープ
基本的に、Apexコードの呼び出しから終了までのすべての処理は1つのトランザクションにつながって実行されます。処理開始とともにトランザクションが開始され、処理終了時にコミットされます。コード中での明示的なBegin/Commitはありません。実行時例外が投げられるとトランザクションはロールバックされます。これはJavaEEの宣言的トランザクション仕様のRequired属性に似た動作と言えます。
Visualforce Controllerの場合は、Webクライアントからのリクエストを受けてからレスポンスを返すまでが1トランザクションとなります。Apexトリガの場合はトリガの起動から終了までが1トランザクションです。トリガ内で発行されたDMLによって起動したトリガもすべて同一トランザクションに組み込まれます。
SavepointとRollback
コード中で明示的に、ある時点までのロールバック処理を実行できます。 Database.setSavepoint()メソッドを使ってSavepointを設定し、Database.rollback(Savepoint sp)メソッドを呼ぶと指定したSavepointまでロールバックされます。ガバナ制限により、Savepointは1トランザクションにつき5つまで設定でき、ロールバック処理は1トランザクションにつき20回まで行えます。
// Savepointの作成
Savepoint sp = Database.setSavepoint();
・・・Database更新処理等・・・
// Savepoint設定時の状態までロールバック
Database.rollback(sp);
SELECT FOR UPDATE
Apexコード中で実行するSOQLクエリは、"for update"キーワードを付けて行ロックを取得することができます。行ロックはトランザクションがコミットまたはロールバックされるまで持続されます。
[Select Id From Account for update]
非同期処理
@futureアノテーションを付与したメソッドは別トランザクションとして実行されます。ガバナ制限も別計算になります。同一トランザクションではガバナ制限に抵触するような処理も、このアノテーションで処理を非同期化・別トランザクション化することにより実現できる場合があります。このメソッドの呼び出し時に行われるのはキューイングのみで、実際の実行タイミングは保証されないことに注意してください。また、呼び出し元のトランザクションが失敗すると呼び出し先の処理もコミットされません。
@futureアノテーションについての詳細は下記を参照してください。
http://www.salesforce.com/us/developer/docs/apexcode/Content/apex_classes_annotation.htm
スレッドのWait/Notify/Sleep
@futureアノテーション等で処理を別スレッド化することはできますが、スレッドを停止したり処理を待ったり等のスレッド制御はできません。
また、Apexにはsynchronizedキーワードがありません。原則的に、永続化されたデータ以外にスレッド間でデータを共有するという仕組みが無いため、synchronizedキーワードも不要となっています。
トランザクションの分離レベル(Isolation level)
更新したがまだコミットされていないデータは、他のトランザクションからは読み取られません。JavaEE仕様の"Read Committed"に似た動作です。あるトランザクションが更新中のレコードを他のトランザクションが読み取ろうとしたときは、更新中トランザクションが終了するまで一定時間待ちます。一定時間を過ぎた場合は待っている方のトランザクションが失敗します。
Force.comプラットフォームでは、トランザクション分離レベルを変更することはできません。
ロングトランザクションの排他制御
Webアプリケーションにおける「ロングトランザクション」とは、複数回のリクエストにわたってレコードの一貫性の保証や排他制御を行う概念です。
一般的な設計方針では、データ取得時からレコードの更新時までレコードのロックを取得する「悲観的(Pesimistic )ロック」と、レコードの更新時のみにレコードのロックを取得する「楽観的(Optimistic)ロック」があります。
Webアプリケーションでよく用いられるのは、楽観的ロックです。悲観的ロックは、ロックステータスの管理を自前で実装したりロックのタイムアウトを実装したりが必要で、実装が複雑化してしまうという技術的な問題があります。(業務要件としても楽観的ロックの方がマッチする場合も多くみられます。)
楽観的ロックの実装では「バージョンカラム」がよく使われます。バージョンカラムとは、例えばInteger型の項目を作成しておき、レコードの取得時とコミット直前でこの値が変わっていなければ、バージョンカラム項目の値をインクリメントしてレコードをコミットするというものです。こうしておけば、他のトランザクションによりレコードが更新されているかどうかをバージョンカラムの値が変わっているかどうかをチェックすることにより検出できます。
Force.comでは、わざわざバージョンカラムを実装せずに、SystemModstamp項目を利用できます。SystemModstamp項目は、標準オブジェクト/カスタムオブジェクトのすべてにデフォルトで作成されている日付時間型の項目です。この項目はレコードに対して何らかの更新操作が行われるとシステムにより値が更新されるので、データ取得時とコミット直前でこの値が変わっていなければ他のトランザクションによる更新が行われていないという判断の根拠となります。
メタデータAPIでDBスキーマ作成
by Shinichi Tomita on 9月 5, 2007 at 06:19 午後
Summer'07より(Developer Editionのみのプレビューリリースですが)メタデータAPIが公開されました。Salesforceをデータベースになぞらえるなら、今まではDML(データ操作言語)だけであったのが、今度からはDDL(データ定義言語)相当の機能も追加されるようになったと思っていただければよいかもしれません。これによって、Salesforceのデータベーススキーマ(カスタムオブジェクト、カスタム項目)を操作する独自プログラムを作成することが可能になります。
メタデータAPIにアクセスするには、まずAPIの仕様を定義したWSDLファイルをダウンロードします。Developer Editionの管理ユーザアカウントでSalesforceにログインし、「設定>統合>Apex API」から、「Metadata WSDL のダウンロード」のリンクからダウンロードができます。なお、同時にPartner WSDL についてもダウンロードしておいてください。
WSDLを利用するため、今回はJava言語を利用してクライアントを書いてみます。事前にApache Axis 1.4をダウンロードしておいて、関連ライブラリをローカルファイルに保存しておきましょう。
まずAxisのWSDL2Javaを利用して、先ほどダウンロードしたWSDLファイルから、APIにアクセスするためのスタブソースファイルを生成します。<CLASSPATH>にはAxisのライブラリへのパスを含めて指定します。
java -classpath <CLASSPATH> org.apache.axis.wsdl.WSDL2Java -a metadata.wsdl
同様にして、Partner WSDLについてもスタブソースファイルを生成します。
java -classpath <CLASSPATH> org.apache.axis.wsdl.WSDL2Java -a partner.wsdl
生成されたクラスを利用することで、JavaのクライアントプログラムからメタデータAPIにアクセスすることができます。もちろん、メタデータAPIにアクセスするにはまずSalesforceから認証を受けなければいけません。ログイン認証は従来のAPIにアクセスする場合と同様に行います。
SforceServiceLocator sforceServiceLocator = new SforceServiceLocator();
SoapBindingStub sforce = (SoapBindingStub) sforceServiceLocator.getSoap();
LoginResult loginResult = sforce.login(username, password);
ログインに成功すると、認証済みセッションIDが取得できますので、この値をメタデータAPIのスタブにヘッダ情報としてセットします。これでメタデータAPIのメソッドを利用できるようになります。
MetadataServiceLocator metaServiceLocator = new MetadataServiceLocator();
meta = (MetadataBindingStub) metaServiceLocator.getMetadata();
SessionHeader sessionHeader = new SessionHeader();
sessionHeader.setSessionId(loginResult.getSessionId());
meta.setHeader(metaServiceLocator.getServiceName().getNamespaceURI(), "SessionHeader", sessionHeader);
Salesforceではカスタムオブジェクトがデータベースにおけるテーブル(表)に相当し、カスタム項目はカラム(列)に相当します。まず最初にカスタムオブジェクトを作成してみましょう。カスタムオブジェクトを作成するには、WSDLから生成されたクラスに含まれる CustomObject クラスをインスタンス化することで行います。カスタムオブジェクトに必要なプロパティをインスタンスにセットした後、レコード名を表示するためのカスタム項目を1つ作成し、NameFieldとして設定します。
CustomObject customObj = new CustomObject();
customObj.setFullName("MyCustomObject__c"); // 作成するオブジェクトのAPI参照名(__cを末尾に付加)を指定
customObj.setLabel("私のカスタムオブジェクト"); // 表示ラベル
customObj.setPluralLabel("私のカスタムオブジェクト"); // 複数形の表示ラベル
customObj.setDescription("このオブジェクトはメタデータAPIから作成されました"); // オブジェクトの説明
customObj.setEnableReports(true); // レポートの許可
customObj.setEnableActivities(true); // 活動の許可
customObj.setEnableHistory(true); // 項目履歴管理
customObj.setDeploymentStatus(DeploymentStatus.Deployed); //リリース状況
customObj.setSharingModel(SharingModel.ReadWrite); // デフォルトの共有設定
// レコード名の項目
CustomField nameField = new CustomField();
nameField.setFullName("MyCustomObject__c.Name"); // オブジェクトのAPI参照名 . 項目のAPI参照名
nameField.setType(FieldType.AutoNumber); // 項目のデータ型(レコード名項目の場合はTextあるいはAutoNumberのみ)
nameField.setDisplayFormat("{00000}"); // データ型がAutoNumber(自動採番)の場合は必ず指定
nameField.setLabel("レコード番号"); // 表示ラベル
nameField.setDescription("この項目はメタデータAPIから作成されました"); // 項目の説明
// カスタムオブジェクトのレコード名項目に設定
customObj.setNameField(nameField);
カスタムオブジェクトの作成要求を発行するには、MetadataBiding#create() メソッドを利用します。引数には作成するメタデータオブジェクト(カスタムオブジェクト、項目)を配列で指定します。
AsyncResult[] results = meta.create(new Metadata[] { customObj });
サーバ側で完全にデータ定義が終了するまでには時間がかかる場合があるため、APIのレスポンスはリクエストの終了を待たずに一旦非同期で返されます。このレスポンスにはリクエストの進行状況を問い合わせるためのIDが含まれています。データ定義が完了したことを保証するためには、与えられたIDを元にしてクライアントプログラムからサーバに対して定期的な問い合わせ(ポーリング)を行う必要があります。
カスタムオブジェクトの定義が完了したら、そのカスタムオブジェクトに対してカスタム項目を追加定義することができます。カスタム項目のデータ型としては、テキストや数値、日付、選択リストなども指定できますし、他オブジェクトへのリレーション(参照関係、主従関係)なども指定することができます。
// テキスト項目の作成
CustomField textField = new CustomField();
textField.setFullName("MyCustomObject__c.TextField__c"); // オブジェクトのAPI参照名 . 項目のAPI参照名
textField.setType(FieldType.Text); // データ型(テキスト)
textField.setLabel("テキスト項目"); // 表示ラベル
textField.setDescription("この項目はメタデータAPIから作成されました"); // 項目の説明
// 選択リスト項目の作成
CustomField picklistField = new CustomField();
picklistField.setFullName("MyCustomObject__c.PicklistField__c"); // オブジェクトのAPI参照名 . 項目のAPI参照名
picklistField.setType(FieldType.Picklist); // データ型(選択リスト)
picklistField.setLabel("選択リスト項目"); // 表示ラベル
picklistField.setDescription("この項目はメタデータAPIから作成されました"); // 項目の説明
Picklist picklist = new Picklist();
PicklistValue[] picklistValues = new PicklistValue[]{ new PicklistValue(), new PicklistValue() };
picklistValues[0].setLabel("男");
picklistValues[0].setValue("M");
picklistValues[1].setLabel("女");
picklistValues[1].setValue("F");
picklist.setPicklistValues(picklistValues);
picklistField.setPicklist(picklist); // 選択リストとして設定
// 参照項目(リレーション)の作成
CustomField lookupField = new CustomField();
lookupField.setFullName("MyCustomObject__c.AccountLookupField__c"); // オブジェクトのAPI参照名 . 項目のAPI参照名
lookupField.setType(FieldType.Lookup); // データ型(参照関係)
lookupField.setLabel("参照項目(取引先)"); // 表示ラベル
lookupField.setDescription("この項目はメタデータAPIから作成されました"); // 項目の説明
lookupField.setReferenceTo("Account"); // 参照するオブジェクトのAPI参照名
lookupField.setRelationshipName("MyCustomObjects"); // 子リレーションの関連名(取引先=>カスタムオブジェクト)
// カスタム項目の作成
AsyncResult[] results = meta.create(new Metadata[] { textField, picklistField, lookupField });
なお、1リクエストに指定できるメタデータオブジェクトの数はAPI仕様上は最高200個までとされていますが、少なくとも現在のところ5つほどにしておいたほうがよいようです。また、配列として同時に指定できるメタデータオブジェクトは同じ種類のものである必要があります(1リクエストの中にカスタムオブジェクトとカスタム項目を混在することはできない)。
メタデータAPIが利用できるのは現在のところDeveloper Editionのみですが、作成したメタデータオブジェクトをパッケージングしてAppExchangeにアップロードすることで他の組織に自由にコピーすることができます。そのため、実質どのSalesforce組織、Editionにおいても自由にデータベース・スキーマを作成することができます。
Apex API 10.0 Manual
http://www.salesforce.com/us/developer/docs/api/index.htm
実践SOQLで学ぶSalesforceデータベース
by Shinichi Tomita on 7月 12, 2007 at 11:53 午前
Salesforceのデータベースにアクセスするには、SOQLという独自のクエリ言語を用います。この文法自体はSQLによく似ているので、いままでデータベース開発に携わった方であればそれほど難しくないかもしれません。おそらく開発者の方が抱く一番の疑問点は「Salesforceのデータベースのどこにどんなデータが入るのか、そしてどのようにしてそのデータを引っ張ってくるのか」といったところではないかと思います。
Salesforceの標準データモデルについてはこちらのエントリでも簡単に解説しましたが、今回はもう少しユースケースを意識した実践的なデータクエリの例をいくつか挙げてみましょう。
ある取引先に所属している顧客名(取引先責任者)を一覧取得する
SELECT Id, FirstName, LastName, Account.Name
FROM Contact
WHERE Account.Name = '株式会社四川商会'
Contact(取引先責任者)オブジェクトに対しての比較的単純な問い合わせ文ですが、レコードの検索条件を親リレーション(Account)の属性値で指定してフィルタリングしているところが特徴です。
自分(ログインユーザ)が担当している商談と、その商談の更新履歴をすべて取得する
SELECT Id, Name, Account.Name, Amount,
(SELECT Id, StageName, CreatedDate
FROM OpportunityHistories
ORDER BY CreatedDate DESC)
FROM Opportunity
WHERE OwnerId = '{現在ログインしているユーザのID}'
先ほどと同様に2つのオブジェクトにまたがった問い合わせです。前回が子供=>親への参照であったのに対して、今回は親=>子供のレコードへの関連を使用しています。
ここで、副問い合わせの形(括弧で囲まれたクエリ文)で指定されている中にある「OpportunityHistories」とは、Opportunity(商談)オブジェクトからOpportunityHistory (商談履歴) オブジェクトへのリレーションになっています。商談情報のレコードが更新されると自動的にこのオブジェクトに履歴が格納されるようになっています。履歴が時系列に並ぶように子オブジェクトに対してもORDER BY句でレコードを整列させしていることに注目してください。
Opportunity(商談)の問い合わせ条件として、OwnerId(所有者ID)がログインユーザIDと一致しているものに絞り込んでいます。現在ログインしているユーザのIDは getUserInfo APIメソッドで取得することができます。なお、Salesforceにおいてはレコードの所有ユーザは「担当者」という意味でよく使われます。
30日以上活動していない商談を一括取得する
SELECT Id, Name, Account.Name, Amount, LastActivityDate
FROM Opportunity
WHERE LastActivityDate = null
OR LastActivityDate < LAST_N_DAYS:30
検索条件として、LastActivityDate(最終活動日)という項目に対して「過去n日以内」という意味を表す LAST_N_DAYS:n というリテラル記述を使用しています。これにより問い合わせを実行した時点から30日以内に活動がない商談をすべて取得します。
SOQLではこの他にも日付を表すリテラル表記を定義しています。詳細についてはマニュアルをご参照ください。
LastActivityDateは、そのレコードに関連付けられているToDoや行動といった活動情報のうち、最後に発生した活動の日時を表示する項目です。これは活動の記録をサポートしているオブジェクトに存在する標準項目です。Opportunity(商談)のほかにもAccount(取引先)やCase(ケース)オブジェクトに存在しています。
ある取引先に対して行った活動履歴、および活動予定を日時が近い順に5件ずつ取得
SELECT Id, Name,
(SELECT Id, Subject, ActivityDate
FROM ActivityHistories
ORDER BY ActivityDate DESC, LastModifiedDate DESC
LIMIT 5),
(SELECT Id, Subject, ActivityDate
FROM OpenActivities
ORDER BY ActivityDate ASC, LastModifiedDate DESC
LIMIT 5)
FROM Account
WHERE Id = '{取引先のID}';
Salesforceの標準オブジェクトの中でも「Event(行動)」および「Task(ToDo)」というオブジェクトは、少々特殊な扱いになっています。これらはまとめて「Activity(活動)」という単位で扱われ、さまざまなSalesforceオブジェクトに関連付けて用いることができます。上記のクエリでわかるように、関連付けられた活動情報は、もうすでに完了したものについては「ActivityHistories(活動履歴)」、および未だ完了していないものについては「OpenActivities」という関連をたどることで参照することができます。
なおTask(ToDo)オブジェクトに関してですが、日本語のラベル名は「ToDo」という名前になっているので、このオブジェクトが過去の活動記録の用途にも使われるというのはちょっと混乱してしまうかもしれません。実際の話Salesforceを使う場合に、ToDoを本当にToDoとして用いることはそれほど多くなく、むしろ送信したメールや電話の内容など「すでに行った活動のログ」を保存しておくのにToDoオブジェクトが用いられることのほうが多いくらいです。これは、日本語ラベルではなく英語名の「Task」という名前を考えていただければ、本来このオブジェクトが未来の活動予定のみでなく使われるのは納得いただけるでしょう。
ある部署の今月の売上げを営業マン別に集計して取得
SELECT Amount, Owner.Id, Owner.Name
FROM Opportunity
WHERE IsWon = true
AND CloseDate = THIS_MONTH
AND Owner.UserRole.Name = '営業2課'
これは、よく営業成績のレポートに用いられるタイプのデータクエリだと思います。IsWon(成立フラグ)が商談がまとまったことを示しているため、金額(Amount)を売り上げとしてみなすことができます。検索条件の中のOwnerに指定されているUserRoleという項目は、商談を所有している営業が所属している部署をあらわしています。UserRoleはUserオブジェクトに対して1つのみとなり、その意味では「役割」というよりもむしろ「組織階層」を表現するの用いられるので、ちょっと注意が必要かもしれません。
SQLと異なり、現在SOQLでは集計などの集合関数が用意されていないため、そのままではランキングのような形式で利用することができません。そのため、このような場合はクエリを送信したプログラム側で集計作業を行う必要があります。以下にJavaScriptで書いた場合の集計の例を記載します。
var soql = "SELECT Amount, Owner.Id, Owner.Name "+
" FROM Opportunity "+
" WHERE IsWon = true "+
" AND CloseDate = THIS_MONTH "+
" AND Owner.UserRole.Name = '営業2課'";
var qr = sforce.connection.query(soql);
var tmp = {}; // 集計を行うための一時Hashオブジェクト
var iter = new sforce.QueryResultIterator(qr);
while (iter.hasNext()) {
var opp = iter.next();
if (!tmp[opp.Owner.Id]) {
tmp[opp.Owner.Id] = {
Name : opp.Owner.Name,
TotalAmount : opp.Amount ? opp.getInt("Amount") : 0
};
} else {
tmp[opp.Owner.Id].TotalAmount += opp.Amount ? opp.getInt("Amount") : 0;
}
}
// 配列に変換
var ranking = [];
for (var id in tmp) ranking.push(tmp[id]);
// TotalAmountの降順でソート
ranking = ranking.sort(function(r1, r2) {
return r1.TotalAmount < r2.TotalAmount ? 1 : -1;
});
なお、これらのクエリを試していただくにしても、Salesforceにデータが入っていなくてはあまり話になりません。実際に運用しているデータがあればベストでしょうが、すべての開発者の方がそのような環境にあるわけではないと思います。そのような方には、テスト用のデータをロードするアプリケーションをこちらから配布しています。ぜひ有効活用してください。
今回はSalesforceの標準オブジェクトのみにフォーカスしてSOQL文の例を挙げました。SOQLは独自に定義したテーブル(カスタムオブジェクト)に対してももちろん使えますが、こちらについては別の機会に詳しく説明できればと思います。
翔泳社DBマガジンでSalesforceプラットフォームが紹介されます
by Shinichi Tomita on 6月 20, 2007 at 06:42 午後
データベースの技術情報誌として有名なDBマガジン2007/08月号(翔泳社)で、Salesforceプラットフォームが紹介されます(6月23日発売予定)。本記事では、Salesforceを単なるSFAやCRMアプリケーションサービスとしてだけではなく、基盤サービスの面から切り込んで紹介しており、技術的な濃度の濃い内容になっています。
具体的にはSalesforceのプラットフォームアーキテクチャについて紹介した後、AJAX Toolkitを使ったAPIの利用方法から、簡単なアプリケーションのチュートリアルまで記載しています。
同号ではAjaxとデータベースのアプリケーション開発についての特集が別途組まれていますが、SalesforceのようにWeb経由でデータベースサービスを提供できるプラットフォームが、既存のインストールベースのデータベース開発に対してどれだけインパクトを与えることができるかについて考えてみるのもよいかもしれません。
特集:JavaScriptのデバッグやJava連携も簡単になった Ajaxによる最新型DBアプリ開発
商品番号:810708
添付:CD-ROM 1点
サイズ:A4変
ページ:228
販売価格:\1,380(本体\1,314 消費税5%)
出版社:株式会社翔泳社
(追記)
なお以下は誌面のチュートリアルで利用した掲示板アプリケーションの完成版のURLです。開発用アカウントでアプリケーションをインストールして、Sコントロールのソースコードを参照してみてください。
https://www.salesforce.com/jp/appexchange/detail_overview.jsp?id=a03300000035ra1AAA
Salesforceのアプリケーション・データモデル
by Shinichi Tomita on 4月 25, 2007 at 12:54 午後
SalesforceのApexプラットフォームは汎用のデータベースサービスとしても利用できるのですが、Salesforceが標準で提供しているCRM/SFAアプリケーションと連動するアプリケーション開発を行うことによって、より強力なアプリケーションサービスを構築することも可能です。
ただ、このようなアプリケーションの開発のためには、標準で提供されるCRM/SFAアプリケーションが保有しているデータモデルについてある程度前提となる知識が必要になります。そのため、ここでは、Salesforceの営業支援アプリケーションが用意している標準オブジェクトのデータモデルについて、どのようなものがあるかについてご紹介します。
(※カッコ内はAPI参照名)
ユーザ(User)
Salesforce組織にアクセスするユーザの情報を管理しています。
一人一人にユーザ名とパスワードが与えられ、Salesforceにログインすることができます。
営業支援の用途としてSalesforceを使う場合は、自社の営業マンがこれに相当します。また管理ユーザの情報もこの中に登録されます。
SELECT Id, FirstName, LastName, Alias, Username FROM User
取引先(Account)
顧客企業/事業者の情報を格納するオブジェクトです。
取引先名、電話番号、住所(請求先/発送先)、従業員数などの企業情報を格納します。
SELECT Id, Name, Phone, NumberOfEmployees,
BillingState, BillingCity, BillingStreet
FROM Account
取引先責任者(Contact)
取引先責任者は取引先に所属している個人をあらわします。
氏名、役職、電話番号、Eメールなどの個人情報を格納します。
取引先責任者には、所属している取引先の情報が親として関連付けられます。
子(Contact)から親(Account)への参照
SELECT LastName, FirstName, Email,
Account.Name, Account.Phone
FROM Contact
親(Account)から子(Contact)への参照
SELECT Name, Phone,
(SELECT Email FROM Contacts)
FROM Account
商談(Opportunity)
商談は現在進行中の(あるいは完了した)案件情報を格納するためのオブジェクトです。
現在のフェーズや商談全体の金額などの情報を指定できます。
商談に関連している取引先の情報も参照関係という形で指定できます。
子(Opportunity)から親(Account)への参照
SELECT Id, Name, StageName,
Account.Name, Account.Phone
FROM Opportunity
親(Account)から子(Opportunity)への参照
SELECT Id, Name,
(SELECT Name, StageName FROM Opportunities)
FROM Account
行動(Event)/ ToDo(Task)
)行動(Event)は、カレンダー上のスケジュールをあらわすオブジェクトです。
またToDo(Task)は、今後遂行すべき業務内容(ToDo)の情報を格納するオブジェクトですが、そのほかにもすでに終了した活動情報を履歴として格納することがあります。
行動およびToDoといった活動情報には、その活動に関係する取引先責任者/リード(Who)の情報や、商談/取引先(What)の情報を関連づけておくことができます。
SELECT Id, Subject, ActivityDateTime, DurationInMinutes,
Who.Id, Who.Type, Who.Name,
What.Id, What.Type, What.Name
FROM Event
なお、上記すべてのオブジェクトについて、レコードの所有者(Owner)および作成者(CreatedBy)の情報としてユーザオブジェクトが自動的に関連付けられます。
SELECT Id, Name, CreatedBy.Name, Owner.Name FROM Account
その他にも、マーケティング用途としてリード(Lead)、キャンペーン(Campaign)、サービス&サポート用途としてケース(Case)およびソリューション(Solution)などのオブジェクトが定義されています。以下の図はSalesforceの標準オブジェクトの関係を示したER図です
これらのデータモデルの詳細については、Apex APIマニュアルを参照するか、あるいはdescribeSObject APIメソッドを利用することで詳細情報を確認できます。




