この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
SOQLとSQLを比較した時に、SOQLでは自己結合ができないという制限があります1。
例えば、従業員テーブルが次のようにあるとします。
従業員ID | 氏名 | 年棒 |
---|---|---|
00001 | 菅 裕行 | 450 |
00001 | 菅 裕行 | -100 |
00002 | 宮沢 美紀 | 600 |
00003 | 河野 雅 | -75 |
00002 | 宮沢 美紀 | -150 |
00003 | 河野 雅 | 400 |
このテーブルのDDL例は次の通りです。
create table 従業員テーブル (
従業員ID varchar(15),
氏名 varchar(255),
年棒 decimal(10,2)
);
INSERT INTO 従業員テーブル (従業員ID, 氏名, 年棒) VALUES ('00001', '菅 裕行', 450);
INSERT INTO 従業員テーブル (従業員ID, 氏名, 年棒) VALUES ('00001', '菅 裕行', -100);
INSERT INTO 従業員テーブル (従業員ID, 氏名, 年棒) VALUES ('00002', '宮沢 美紀', 600);
INSERT INTO 従業員テーブル (従業員ID, 氏名, 年棒) VALUES ('00003', '河野 雅', -75);
INSERT INTO 従業員テーブル (従業員ID, 氏名, 年棒) VALUES ('00002', '宮沢 美紀', -150);
INSERT INTO 従業員テーブル (従業員ID, 氏名, 年棒) VALUES ('00003', '河野 雅', 400);
諸般の事情により(?)、従業員テーブルには正の値(年棒)と控除額(税など)が別レコードで記録されています。
この時に各従業員の控除後の年棒を出したい場合、SQLでは次のようにできます。
SELECT
DISTINCT A.従業員ID,
A.氏名,
A.年棒 + B.年棒 AS 年棒(控除後)
FROM
従業員テーブル A, 従業員テーブル B
WHERE
A.従業員ID = B.従業員ID
AND
A.年棒 > 0
AND
B.年棒 < 0
ORDER BY
従業員ID ASC
結果は
従業員ID | 氏名 | 年棒(控除後) |
---|---|---|
00001 | 菅 裕行 | 350 |
00002 | 宮沢 美紀 | 450 |
00003 | 河野 雅 | 325 |
となります。
これを、従業員というカスタムオブジェクト(SObject型のEmployee__c
)で行いたい場合、先述の通りSOQLで自己結合は使えませんので工夫が必要です。
具体的には、自己結合のAとBにあたるレコードセットを表現するDTOを定義し、そのDTOのequalsメソッドにて結合条件を設定します。
DTOの定義
DTOを次のように定義します。
global class EmployeeDto implements Comparable {
// 従業員ID
public String employeeId {get;set;}
// 氏名
public String Name {get;set;}
// 年棒
public Decimal annualSalary {get;set;}
global EmployeeDto(String employeeId, String Name, Decimal annualSalary) {
this.employeeId = employeeId;
this.Name = Name;
this.annualSalary = annualSalary;
}
// ソート(従業員ID↑)
global Integer compareTo(Object obj) {
EmployeeDto dto = (EmployeeDto) obj;
// ソートキーは従業員ID
if ( this.employeeId != null && dto.employeeId != null ) {
Integer ret = this.employeeId.compareTo(dto.employeeId);
if ( ret != 0 ) {
return ret;
}
}
return 0;
}
public Boolean equals(Object obj) {
if ( obj != null && obj instanceOf EmployeeDto ){
EmployeeDto dto = (EmployeeDto) obj;
if (
(
( employeeId != null && dto.employeeId != null ) &&
( employeeId == dto.employeeId )
)
) {
return true;
} else {
return false;
}
} else {
return false;
}
}
public Integer hashCode() {
Integer result = 17;
final Integer prime = 31;
result = prime * result + System.hashCode(employeeId);
return result;
}
}
compareTo
、 hashCode
メソッドに関しては説明を省略します2。
equals
メソッドを Override することでこのDTO(EmployeeDto)同士の一致条件(一意性)をカスタマイズすることができます。
public Boolean equals(Object obj) {
if ( obj != null && obj instanceOf EmployeeDto ){
EmployeeDto dto = (EmployeeDto) obj;
if (
(
( employeeId != null && dto.employeeId != null ) &&
( employeeId == dto.employeeId )
)
) {
return true;
} else {
return false;
}
} else {
return false;
}
}
if文で employeeId が等しい時に x.equals(y)
(※ x、yは共にEmployeeDtoオブジェクト)がTrueになるように実装しています。
これによって先のSQL例の A.従業員ID = B.従業員ID
を表現しています。
自己結合のエミュレート
DTOの準備ができたら、次のようなApexコードで自己結合をエミュレートできます。
// 正の年棒を持つEmployee__cを保持するList
List<Employee__c> plusAnnualSalaryEmployees = [SELECT employeeId__c, Name, annualSalary__c FROM Employee__c WHERE annualSalary__c > 0];
// 負の年棒を持つEmployee__cを保持するList
List<Employee__c> minusAnnualSalaryEmployees = [SELECT employeeId__c, Name, annualSalary__c FROM Employee__c WHERE annualSalary__c < 0];
/* DTOのListを作る */
List<EmployeeDto> plusDtos = new List<EmployeeDto>();
for ( Employee__c employee : plusAnnualSalaryEmployees ) {
EmployeeDto plusDto = new EmployeeDto(employee.employeeId__c, employee.Name, employee.annualSalary__c);
plusDtos.add(plusDto);
}
List<EmployeeDto> minusDtos = new List<EmployeeDto>();
for ( Employee__c employee : minusAnnualSalaryEmployees ) {
EmployeeDto minusDto = new EmployeeDto(employee.employeeId__c, employee.Name, employee.annualSalary__c);
minusDtos.add(minusDto);
}
/* 結果生成 */
for ( EmployeeDto plusDto : plusDtos ) {
for ( EmployeeDto minusDto : minusDtos ) {
if ( plusDto.equals(minusDto) ) {
// 一致すると判断されたら各項目を出力する
System.debug('従業員ID: ' + plusDto.employeeId);
System.debug('氏名: ' + plusDto.Name);
System.debug('年棒(控除後): ' + (plusDto.annualSalary + minusDto.annualSalary));
}
}
}
もし、自己結合の結合条件を変えたければ、EmployeeDtoのequalsメソッドを改修すればOKです。
まとめ
SObjectで自己結合したい場合に、DTOを定義しequalsメソッドをOverrideすることでエミュレートできることをみてきました。
思いのほか簡単に実現できることがお分かり頂けたかと思います。
自己結合しなくて済むようなテーブル(オブジェクト)設計ができれば一番良いのですが、様々な理由によりそうもいかない場合もあるかと思います。
SObjectで自己結合したい!という状況に直面したらこの方法を参考にしていただければと思います。
- 他にもたくさん制限があります。 ↩
- compareToは Comparable インターフェース、hashCodeは Javaを陰から支えるhashCode、その仕組みと実装方法を基礎から紹介 が参考になります。 ↩