[Tips] 親子レコード対応版: SOQLの結果からId型のリストを抽出する

2023.03.23

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

[Tips] 子レコード対応版: SOQLの結果からId型のリストを抽出する をさらに改良して、親レコードのId項目も抽出できるようにしました。

classes/FieldSlice.cls

public class FieldSlice {

    private list<Object> olist;

    public FieldSlice(list<Object> olist) {
        this.olist = olist;
    }


    /**
     * 指定したIdフィールドのリストを返す
     * @param idFieldName Idフィールド名(親のリレーション A__r.B__c の形式も受容する)
     * @return 指定したIdフィールドのリスト
     */
    public List<Id> retrieveIds(String idFieldName) {
        return retrieveIds(idFieldName, null);
    }

    /**
     * 指定したIdフィールドのリストを返す
     * @param idFieldName Idフィールド名(親のリレーション A__r.B__c の形式も受容する)
     * @param childRelationName 子リレーションから取得する場合に指定する子リレーション名
     * @return 指定したIdフィールドのリスト
     */
    public List<Id> retrieveIds(String idFieldName, String childRelationName) {
        List<Id> ids = new List<Id>();
        for ( Object obj : olist ) {
            String s = JSON.serialize(obj);
            Map<String,Object> m = (Map<String,Object>)JSON.deserializeUntyped(s);
            if ( String.isEmpty(childRelationName) ) {
                Id id = retrieveId(m, idFieldName);
                // nullは結果から除外
                if ( id != null ) {
                    ids.add(id);
                }
            } else {
                // 子リレーションから取得
                Map<String,Object> childRelation = (Map<String, Object>)m.get(childRelationName);
                List<Object> children = (List<Object>)childRelation.get('records');
                ids.addAll(new FieldSlice(children).retrieveIds(idFieldName));
            }
        }
        return new List<Id>(new Set<Id>(ids));
    }

    /**
     * 指定したIdフィールドを返す
     * @param m 取得対象のObjectのMap
     * @param idFieldName Idフィールド名(親のリレーション A__r.B__c の形式も受容する)
     * @return 指定したIdフィールドのリスト
     */
    private Id retrieveId(Map<String,Object> m, String idFieldName) {
        List<String> chainsOfField = idFieldName.split('\\.');
        if ( chainsOfField.size() == 1 ) {
            return (Id)m.get(chainsOfField.get(0));
        }
        m = (Map<String,Object>)m.get(chainsOfField.remove(0));
        if ( m == null ) {
            return null;
        }
        return retrieveId(m, String.join(chainsOfField, '.'));
    }
}

使い方は次の通り。

List<Account> opportunities = [
    SELECT
        Id,
        Account.Id,
        Account.SomeParentObj__r.SomeAncestorObj__r.Id,
        (
            SELECT
                Id
            FROM
                OpportunityLineItems
        )
    FROM
       Opportunity
];

// ここで商談のIdを取得(従来の使い方でそのまま使える)
List<Id> opportunityIds = new FieldSlice(opportunities).retrieveIds('Id');

// 子の商談商品のIdを取得(従来の使い方でそのまま使える)
List<Id> oliIds = new FieldSlice(opportunities).retrieveIds('Id', 'OpportunityLineItems');

// 親の取引先のIdを取得 <= New!
List<Id> accountIds = new FieldSlice(opportunities).retrieveIds('Account.Id');

// 商談の親(取引先)の親(SomeParentObj__c)の親(SomeAncestorObj__c)のIdを取得 <= New!
List<Id> ancestorIds = new FieldSlice(opportunities).retrieveIds('Account.SomeParentObj__r.SomeAncestorObj__r.Id');

SOQLでは5世代までの親を遡れるので、FieldSlice.retrieveIdsでも5世代までは辿って取得できます。Apexにはガバナ制限により、SOQLの発行回数に制限があるため、一つのSOQLで親子構造を一気に取ってきておいて、FieldSliceで個別にIdのリストを取得するようにすることでSOQL発行回数を節約できるメリットもあります。