Apex Academy
Módulo 3·03·30 min

Consultas de relación (padre e hijo)

Subir al padre con un punto, bajar a los hijos con una subconsulta: una sola consulta trae la cuenta, su propietario y todos sus contactos.

En Admin esUna fórmula entre objetos hacia arriba, una related list hacia abajo

✓ En esta sub-lección

  1. 01Leer campos del registro padre con la notación de punto (Account.Name, Owner.Email).
  2. 02Traer los registros hijos con una subconsulta y recorrerlos con un bucle anidado.
  3. 03Distinguir el campo __c del nombre de relación __r en objetos personalizados.
  4. 04Filtrar por los hijos con IN (SELECT ...), como el filtro cruzado de un informe.

La nutria: ¿Te acuerdas? · Repaso de la lección 2

¿En qué orden van estas cláusulas en una consulta?

Los datos de Salesforce no viven sueltos: un contacto pertenece a una cuenta, una oportunidad tiene propietario, una factura tiene líneas. Como Admin ya diseñaste esas relaciones con campos de búsqueda y maestro-detalle. Esta lección enseña a recorrerlas desde una consulta, en las dos direcciones.

CONTACTO
  1. c.LastName→ 'Torres'el propio contacto
  2. c.Account.Name→ 'Northwind Trading'un punto: su cuenta
  3. c.Account.Industry→ 'Retail'otro campo de la misma cuenta
  4. c.Account.Owner.Name→ 'Marco Vidal'dos puntos: el propietario de su cuenta

Cada punto sube un nivel por un campo de búsqueda, igual que en una fórmula entre objetos. Hacia arriba puedes encadenar hasta cinco.

Hacia el padre, un punto. Hacia los hijos, una consulta dentro de la consulta.

Hacia arriba: del hijo al padre

La nutria: El paralelo de Admin

Cuando yo creaba una fórmula en Contacto que mostraba el sector de su cuenta, escribía Account.Industry: con un punto subía por el campo de búsqueda hasta el padre. En SOQL es exactamente el mismo punto, y funciona igual: puedes subir hasta cinco niveles, como en Account.Owner.Manager.Name.
Apex
List<Contact> contacts = [
    SELECT Id, LastName, Email,
           Account.Name, Account.Industry,
           Owner.Name
    FROM Contact
    WHERE Account.Industry = 'Retail'
];

for (Contact c : contacts) {
    System.debug(c.LastName + ' trabaja en ' + c.Account.Name);
}
Puedes pedir campos del padre en el SELECT y también filtrar por ellos en el WHERE, como un filtro de informe sobre un campo de la cuenta.

__c o __r: el campo o el camino

En objetos estándar el nombre de la relación coincide con el del campo sin «Id»: AccountId guarda el Id, Account es el camino. En objetos personalizados la regla es la misma con otro sufijo: el campo de búsqueda Customer__c guarda el Id de la cuenta, y Customer__r es el camino para subir a ella.

Quieres…Estándar (Contact → Account)Personalizado (Invoice__c → Account)
el Id del padreAccountIdCustomer__c
un campo del padreAccount.NameCustomer__r.Name

Hacia abajo: del padre a los hijos

La nutria: El paralelo de Admin

Yo la entendí pensando en la página de una cuenta: la related list de Contactos te enseña todos sus hijos debajo del registro. Una subconsulta hace lo mismo: dentro del SELECT de la cuenta, entre paréntesis, pides los contactos, y cada cuenta llega con su propia lista de contactos colgando.
Apex
List<Account> accounts = [
    SELECT Id, Name,
           (SELECT Id, LastName, Email FROM Contacts ORDER BY LastName)
    FROM Account
    WHERE Industry = 'Retail'
];

for (Account a : accounts) {
    System.debug(a.Name + ' tiene ' + a.Contacts.size() + ' contactos');
    for (Contact c : a.Contacts) {
        System.debug('   · ' + c.LastName);
    }
}
a.Contacts es una List<Contact>. Recorrer padres y, dentro, sus hijos es el bucle anidado del Módulo 2, pero aquí sí está justificado: cada interior solo recorre los hijos de esa cuenta.
  • En el FROM de la no va el nombre del objeto (Contact) sino el nombre de la relación hija, en plural: Contacts, Opportunities, Cases.
  • En objetos personalizados es el «Child Relationship Name» que pusiste al crear la búsqueda, con __r: (SELECT Id FROM Invoices__r).
  • La subconsulta admite su propio WHERE, ORDER BY y LIMIT, como el filtro y el orden de una related list.
  • Una cuenta sin contactos llega con a.Contacts vacía, no null: puedes recorrerla sin miedo.

Filtrar por los hijos: el filtro cruzado

En los informes existe el filtro cruzado: «Cuentas con Oportunidades» o «Cuentas sin Casos». En SOQL se escribe con IN o NOT IN y una consulta que devuelve Ids. Es distinto de la subconsulta del SELECT: aquella trae los hijos; esta solo los usa para decidir qué padres entran.

Apex
// Cuentas CON al menos una oportunidad ganada
[SELECT Id, Name FROM Account
 WHERE Id IN (SELECT AccountId FROM Opportunity WHERE IsWon = true)]

// Cuentas SIN ningún caso
[SELECT Id, Name FROM Account
 WHERE Id NOT IN (SELECT AccountId FROM Case)]

La nutria: ¿Y por qué no un Flow? Porque Get Records no empareja padres e hijos

Para la ronda de visitas de Retail lo probé primero en Flow. «Cada cuenta con sus contactos» eran dos Get Records (las cuentas, y los contactos con AccountId In) y dos Loops anidados para emparejarlos a mano; o un Get Records por cuenta dentro del Loop, que revienta con más de 100 cuentas. Y el filtro de Get Records solo ofrece campos del propio objeto: «contactos cuya cuenta es de Retail» no se puede escribir. Desde Summer '25 hay una opción beta para traer relacionados en el mismo Get Records, solo en flows autolanzados. En SOQL, una consulta te lo da ya emparejado.

Las tres formas, una al lado de otra

Bajar a los hijos, subir al padre y filtrar por los hijos se escriben parecido y devuelven cosas MUY distintas. Cambia de pestaña en el diagrama y fíjate en dos columnas: cuántas filas vuelven y de qué objeto son. Es el error más común al empezar: pedir contactos y recibir cuentas, o al revés.

[SELECT Name, (SELECT LastName FROM Contacts) FROM Account]
Account.NameContacts
Acme CorpRivera, Soto
Northwind TradingTorres
GlobexMéndez, Ruiz, Pardo
Initech(lista vacía)
Umbrella FoodsDíaz

5 filas — Subconsulta en el SELECT: cada cuenta llega con SU lista de contactos colgando, como la related list de la página del registro. Devuelve las 5 cuentas, tengan contactos o no — Initech viene con la lista vacía, que no es null.

Los mismos cinco registros de la org, consultados de tres maneras. Fíjate en el recuento de filas de cada pestaña.