【Salesforce】SQLとSOQLはここが違う(その1)
はじめに
こんにちは。CREFILでエンジニアをしている松井です。
この記事では、SQLとSOQLの違いについてご紹介します。
SQLは知っているけどSOQLにはまだ慣れていなくて、SQLではこう書いていたけどSOQLではどう書いたらいいのか、というようなことを思っている方々の参考になれば幸いです。
SOQLとは
SOQLとは、Salesforce Object Query Languageの略で、Salesforceに保存されたオブジェクトのデータをを検索することができるクエリ言語です。
SalesforceのApex開発で利用するのはもちろん、データの確認のために開発者コンソールから実行したり、DataLoaderでExport対象を指定したりする場面でも利用します。
基本的な構文は、SQLのSELECTステートメントと同じなので、SQLの知識さえあれば対応できることが多いです。
とはいえ、やはりSalesforce専用に設計されているため、SQLにはないSOQLに特有の文法や制約もあり、知識として押さえておく必要があります。
SQLとSOQLの違い
ここからは、具体例を挙げながら、SQLとSOQLでそれぞれどのような記述になるか、ご説明していきます。
例では、次のようなデータ構造のテーブル(オブジェクト)群を想定します。
1. SOQLにはJOIN句がない
SQLは、言わずと知れた、リレーショナルデータベースのデータの操作や定義を行うための言語です。
リレーショナルデータモデルにおいては、JOINによるテーブルの結合はまさに根幹をなすものなので、JOINがないなんて想像ができません。
一方、SOQLは、Object Query Languageという名前が指し示す通り、"オブジェクト"のクエリ言語となっています。
オブジェクト間の参照関係により、JOIN句なしでリレーションを辿った検索ができるようになっています。
考え方としては、Salesforce、もしくは、他の開発プラットフォームでの、オブジェクト指向データモデリングの知識があれば、きっと理解は難しくないと思います。
(ケース1.1)部署が"経理部"である取引先責任者の氏名とメールアドレス、および、取引先の社名を抽出したい
SQLであれば、複数のテーブルのデータを参照する場合は、JOIN句で結合を行います。
SQL
SELECT acct.Name, ctac.Name, ctac.Email
FROM Account acct
JOIN Contact ctac
ON acct.Id = ctac.AccountId
AND ctac.Department = '経理部'
SOQLでは、オブジェクト間の参照関係を利用します。
子から親(Contact -> Account)の参照を利用すると次のようになります。
SOQL
SELECT Account.Name, Name, Email
FROM Contact
WHERE Title = '経理部'
ここに出てくる"Account.Name"の"Account"はオブジェクト名ではなく、Contactオブジェクトが持つ参照項目に定義された名称です。
このことからわかるように、SOQLでは、結合には参照項目が必須であり、オブジェクト間の参照関係がない場合には対応していません。
一方で、SQLでは、性能を度外視すればの話ですが、外部キーが設定されていないテーブル同士も自由に結合することもできます。
Salesforceの基本方針として、マルチテナントアーキテクチャのクラウドサービスであるため、SOQLでもパフォーマンスのリスクを未然に防ぐ目的と見受けられる制約が設けられています。
(ケース1.2)部署が"経理部"である取引先責任者の氏名とメールアドレス、および、取引先の社名を抽出したい
※ただし、該当する取引先責任者が存在しない取引先も抽出したい
これは、いわゆる外部結合で対応できるケースです。
SQLであれば単純な話で、内部結合を外部結合に、つまり、"JOIN"を"LEFT JOIN"に変更するだけです。
SQL
SELECT acct.Name, ctac.Name, ctac.Email
FROM Account acct
LEFT JOIN Contact ctac
ON acct.Id = ctac.AccountId
AND ctac.Department = '経理部'
SOQLでは、先ほどの子から親(Contact -> Account)を参照する方法では、値がなければ参照を辿れないので無理です。
そのため、SQLと同様の結果を得ることはできません。
しかし、別のアプローチとして、親から子(Account -> Contact)の参照を利用する方法があります。
SOQL
SELECT Name, (
SELECT Name, Email
FROM Contacts
WHERE Department = '経理部'
)
FROM Account
ここに出てくる"Contacts"は、Contactオブジェクトの参照項目に定義した子リレーション名です。
ただし、この場合、取得結果はAccountのオブジェクトの形式となります。
ContactsはAccountの属性値で、リスト形式です。
そのため、Apex等のプログラムから利用する場合にはよいのですが、単純に表形式でデータを抽出したいという用途には適していません。
2. SOQLにはEXISTS句がない
(ケース2.1)部署が"経理部"である取引先責任者が存在する取引先を抽出したい
SQLであれば、EXISTS句の使いどころです。
SQL
SELECT acct.Id, acct.Name
FROM Account acct
WHERE EXISTS (
SELECT *
FROM Contact ctac
WHERE acct.Id = ctac.AccountId
AND ctac.Department = '経理部'
)
SOQLでは、EXISTS句がないので、IN句でIDのリストを条件指定する形での対応になります。
SOQL
SELECT Id, Name
FROM Account
WHERE Id IN (
SELECT AccountId
FROM Contact
WHERE Department = '経理部'
)
SQLでもIN句での対応は可能ですが、一般的にEXISTS句の方が性能面からよいとされているかと思います。
(一概には言えないのは重々承知で、あくまで一般論です。)
(ケース2.2)部署が"経理部"である取引先責任者が存在する取引先を抽出したい
※ただし取引先責任者に関連する名刺が未登録であること
SQLであれば、追加の条件は否定であるため"NOT EXISTS"で、EXISTS句をネストする形で記述できます。
SQL
SELECT Id, Name
FROM Account a
WHERE EXISTS (
SELECT *
FROM Contact c
WHERE a.Id = c.AccountId
AND c.Department = '経理部'
AND NOT EXISTS (
SELECT *
FROM BusinessCard b
WHERE c.Id = b.ContactId
)
)
SOQLでも、"NOT IN"は可能なので、同じようにIN句をネストで記述すればよいかと思いますが、これはできません。
SOQL(NG)
SELECT Id, Name
FROM Account
WHERE Id IN (
SELECT AccountId
FROM Contact
WHERE Department = '経理部'
AND Id NOT IN (
SELECT ContactId
FROM BusinessCard
)
)
このようなサブクエリのネストはサポートされていないため、エラーとなってしまいます。
そのため、SOQLでの対応としては、1回のクエリでは実現できないので、クエリを分割する他ありません。
条件に合致するIDを取得するクエリと、IDを指定して取得するクエリにわけて、間をつなぐプログラムを作成する必要があります。
3. SOQLでは項目間の比較ができない
(ケース3.1)請求先郵便番号と納入先郵便番号が一致する取引先を抽出したい
SQLでは、素直に項目間の比較を条件として記述します。
SQL
SELECT Id, Name
FROM Account
WHERE BillingPostalCode = ShippingPostalCode
SOQLでは、項目間の比較による条件指定ができず、同様の記述をするとエラーとなります。
どうやら条件式の右辺に項目を指定することができないようです。
対応としては、条件の数式をカスタム項目で追加し、その数式項目をWHERE句で指定します。
ここでは、Accoutオブジェクトに、次のような数式項目を追加することで対応できます。
API参照名:IsSamePostalCode__c
数式:"BillingPostalCode = ShippingPostalCode"
SOQL
SELECT Id, Name, BillingPostalCode
FROM Account
WHERE IsSamePostalCode__c = true
残念ながら、カスタム項目を簡単に追加できないような環境の場合もあるかと思います。
そういった場合は、とりあえずすべてのデータを出力して、Excelでフィルタするなど、SOQL以外の方法で対応する必要があります。
4. SOQLでは条件に関数を(ほとんど)利用できない
(ケース4.1)社名が50文字以上の取引先を抽出したい
SQLでは、文字列の文字数を取得する関数CHAR_LENGTHを利用できます。
SQL
SELECT Id, Name
FROM Account
WHERE CHAR_LENGTH(Name) >= 50
SOQLでは、使用できる関数は少なく、基本的な集計関数や型変換用の関数など一部に限られています。
文字列の文字数を取得する関数も利用できません。
対応としては、項目間の比較のときと同様に、条件の数式をカスタム項目で追加し、その数式項目をWHERE句で指定します。
ここでは、Accoutオブジェクトに、次のような数式項目を追加することで対応できます。
API参照名:NameLength__c
数式:"LEN(Name)"
SOQL
SELECT Id, Name
FROM Account
WHERE NameLength >= 50
おわりに
いかがでしたでしょうか。
ちょっと長くなってきたので、今回はここまでにして、続きは次回(その2)でご紹介できればと思います。
続きの「【Salesforce】SQLとSOQLはここが違う(その2)」
もしよろしければ是非こちらの方もご覧いただけると、とても嬉しいです。
ここまでご覧いただきありがとうございました。