Variables de enlace y SOQL dinámico
Meter valores de Apex dentro de la consulta con dos puntos, filtrar por una colección entera con IN y construir consultas en tiempo de ejecución sin abrirle la puerta a nadie.
✓ En esta sub-lección
- 01Usar una variable de Apex dentro de una consulta con :variable.
- 02Filtrar por muchos Ids a la vez con IN :conjunto.
- 03Saber cuándo hace falta SOQL dinámico y cómo evitar la inyección de SOQL.
La nutria: ¿Te acuerdas? · Repaso de la lección 4
La subconsulta (SELECT Id FROM Opportunities WHERE IsClosed = false) en el SELECT de cuentas, ¿quita las cuentas sin oportunidades abiertas?
Una consulta con 'Acme' escrito dentro solo sirve para Acme. En código real, el valor del filtro llega de fuera: el registro que se acaba de guardar, lo que un usuario escribió en un buscador, la lista de cuentas que procesas. Esta lección es sobre cómo meter esos valores en la consulta, de la forma segura y de la forma peligrosa.
La nutria: El paralelo de Admin
Los dos puntos
Id accId = '0015g00000Xyz12AAB';
String stage = 'Prospecting';
Date since = Date.today().addDays(-30);
List<Opportunity> opps = [
SELECT Id, Name
FROM Opportunity
WHERE AccountId = :accId
AND StageName = :stage
AND CreatedDate >= :since
];- Detrás de los dos puntos puede ir una variable (:accId), el campo de un objeto (:opp.AccountId) o una expresión (:Date.today().addDays(-30)).
- El tipo tiene que encajar con el campo: no puedes comparar Amount con un String. Es el tipado estático del Módulo 1 funcionando también dentro de la consulta.
IN :colección: muchos valores de una vez
Aquí está la pieza que más usarás a partir del Módulo 6. Si tienes 200 oportunidades y quieres las cuentas de todas, no haces 200 consultas: juntas los AccountId en un Set<Id> y haces una sola consulta con IN :accountIds. Es el operador «In» del Get Records de Flow, apuntando a una variable de colección.
// 1. Juntar los Ids (el Set descarta repetidos)
Set<Id> accountIds = new Set<Id>();
for (Opportunity o : opps) {
accountIds.add(o.AccountId);
}
// 2. Una sola consulta para todas
Map<Id, Account> accountsById = new Map<Id, Account>(
[SELECT Id, Name, Industry FROM Account WHERE Id IN :accountIds]
);
// 3. Buscar sin recorrer
for (Opportunity o : opps) {
Account parent = accountsById.get(o.AccountId);
}SOQL dinámico: cuando la consulta se decide al ejecutar
La consulta entre corchetes es estática: su forma (qué objeto, qué campos, qué condiciones) está fija al guardar la clase, y por eso el compilador la revisa. A veces la forma no se conoce hasta que el código corre: un buscador donde el usuario elige por qué campo ordenar, o una utilidad que sirve para cualquier objeto. Para eso existe Database.query, que recibe la consulta como texto.
String sortField = 'Name'; // viene de una opción del usuario
String soql = 'SELECT Id, Name FROM Account ORDER BY ' + sortField + ' LIMIT 50';
List<Account> accs = Database.query(soql);La nutria: El paralelo de Admin
La inyección de SOQL
Si pegas con + lo que escribió el usuario dentro del texto de la consulta, el usuario ya no escribe un valor: escribe código. Imagina un buscador de casos por asunto que hace '... WHERE IsClosed = false AND Subject LIKE \'%' + keyword + '%\''. Alguien escribe en el buscador «%' OR Subject LIKE '%» y la consulta resultante ya no filtra casos cerrados: ve todos. Eso es la .
CONCATENANDO CON +
WHERE IsClosed = false AND Subject LIKE '%Impresora%'- · Impresora no imprime
CON :pattern
WHERE IsClosed = false AND Subject LIKE :pattern- · Impresora no imprime
Con un texto normal las dos versiones devuelven lo mismo. Por eso el problema pasa desapercibido en las pruebas.
- Primera opción: no uses SOQL dinámico. Si solo cambia el valor del filtro, la consulta estática con :variable basta y es inmune a la inyección.
- Si necesitas dinámico, pasa los valores como enlaces: Database.queryWithBinds(soql, new Map<String, Object>{ 'kw' => pattern }, AccessLevel.USER_MODE), con :kw dentro del texto.
- Lo que no puede ir como enlace (un nombre de campo para ORDER BY) compruébalo contra una lista de valores permitidos. String.escapeSingleQuotes() existe, pero es la última línea de defensa, no la primera.