SOQL Salesforce: składnia zapytań, relacje i limity
- 30 kwietnia 2026
SOQL (Salesforce Object Query Language) to język zapytań Salesforce – oparty na składni SQL, ale zaprojektowany wyłącznie dla danych obiektowych w Salesforce i dostępny w Apex jako element języka, nie biblioteka zewnętrzna (Salesforce Developer Documentation). W odróżnieniu od SQL SOQL nie obsługuje JOIN – zamiast tego nawigujesz relacje między obiektami przez specjalną składnię podzapytań i notację kropkową. Dla każdego, kto pracuje z danymi Salesforce – czy to jako developer piszący Apex, admin używający Workbench, czy analityk w Developer Console – SOQL to pierwsze narzędzie sięgania po dane. Poniżej pełna mapa języka z limityami i pułapkami.
Minimalna forma zapytania SOQL:
SELECT Id, Name, Industry FROM Account WHERE AnnualRevenue > 1000000 ORDER BY Name LIMIT 10
Każde zapytanie musi zawierać SELECT i FROM. Reszta jest opcjonalna. Kluczowe klauzule:
WHERE filtruje rekordy z operatorami porównania (=, !=, <, >, <=, >=), LIKE (z wildcardami % i _), IN i NOT IN (do sprawdzania wartości w kolekcji), operatory logiczne AND, OR, NOT. Przykład: WHERE Industry IN ('Technology', 'Finance') AND AnnualRevenue > 500000.
ORDER BY sortuje wyniki, domyślnie ASC. Null values domyślnie na końcu (NULLS LAST), ale możesz to kontrolować: ORDER BY LastName ASC NULLS FIRST.
LIMIT i OFFSET: LIMIT ogranicza liczbę zwróconych rekordów. OFFSET pozwala paginować wyniki – LIMIT 10 OFFSET 20 zwraca rekordy 21-30. Uwaga: OFFSET ma własny limit: maksymalnie 2000.
WITH SECURITY_ENFORCED lub WITH USER_MODE wymuszają respektowanie uprawnień FLS (Field-Level Security) i Object-Level Security zalogowanego użytkownika. Domyślnie SOQL w Apex działa w system context i ignoruje FLS – deweloper musi aktywnie zdecydować o egzekwowaniu uprawnień. W nowym kodzie WITH USER_MODE jest podejściem preferowanym przez Salesforce.
Maksymalna długość zapytania: 100 000 znaków (Salesforce Ben). Maksymalna długość stringa w klauzuli WHERE: 4 000 znaków.
SOQL nie ma JOIN – zamiast tego masz dwa typy relacyjnych zapytań.
Child-to-parent (w górę hierarchii): dostęp do pól obiektu-rodzica przez notację kropkową. Dla standardowych relacji: SELECT Name, Account.Name, Account.Industry FROM Contact – każdy Contact ma lookup do Account, więc Account.Name zwraca nazwę powiązanego konta. Dla relacji custom: używasz relationship name pola lookup (kończy się na __r): SELECT Name, MyObject__r.CustomField__c FROM AnotherObject__c. Limit: maksymalnie 55 powiązań child-to-parent w jednym zapytaniu (cross-reference z Salesforce Ben i twopirconsulting).
Parent-to-child (subquery, w dół hierarchii): pobierasz rekordy-dzieci wewnątrz zapytania na obiekcie-rodzicu. Syntaktycznie to zagnieżdżony SELECT:
SELECT Name, (SELECT LastName, Email FROM Contacts) FROM Account WHERE Industry = 'Technology'
Dla standardowych obiektów używasz nazwy relacji (Contacts, Opportunities, Cases). Dla customowych relacji: nazwa z __r bez pola (np. jeśli obiekt dziecko ma field Parent__c, relacja w subquery to Parent__r z perspektywy rodzica – sprawdź nazwę relacji w Schema Builder lub Object Manager). Limit: maksymalnie 5 poziomów chain parent-to-child.
Zapytania agregacyjne zamiast zwracać poszczególne rekordy, zwracają dane statystyczne według zgrupowanych pól. Wymagają GROUP BY i zwracają wyniki jako kolekcję AggregateResult[] (oficjalna dokumentacja Salesforce Developer).
Dostępne funkcje agregacyjne: COUNT() lub COUNT(fieldName), COUNT_DISTINCT(fieldName) (unikalne niepuste wartości), AVG(fieldName), MIN(fieldName), MAX(fieldName), SUM(fieldName). AVG, MIN, MAX, SUM działają tylko na polach numerycznych lub daty.
Przykład: liczba Contactów per Account, tylko te Account z więcej niż 5 kontaktami:
SELECT AccountId, COUNT(Id) contactCount FROM Contact GROUP BY AccountId HAVING COUNT(Id) > 5
HAVING to odpowiednik WHERE dla wyników zagregowanych – filtruje po wartościach funkcji agregacyjnych, nie po polach rekordów. Nie możesz użyć WHERE do filtrowania po COUNT(Id) – musisz użyć HAVING.
Dwie rozszerzone wersje GROUP BY: GROUP BY ROLLUP generuje sumy pośrednie dla każdego poziomu grupowania, GROUP BY CUBE generuje agregaty dla wszystkich kombinacji grup (użyteczne dla raportów cross-tabelarycznych). Ograniczenia aggregate queries: nie możesz używać funkcji agregacyjnych w subquery, jedno zapytanie może dotyczyć tylko jednego obiektu.
SOQL w Apex pisze się inline bez stringów – jest to syntaktyczny element języka:
List<Account> accounts = [SELECT Id, Name FROM Account WHERE AnnualRevenue > 1000000];
To tzw. static SOQL (statyczne zapytanie). Dynamic SOQL to budowanie zapytania jako stringa i wykonanie przez Database.query(queryString) – konieczne gdy warunki lub pola są nieznane w czasie kompilacji, ale podatne na SOQL injection jeśli bez dezynfekcji wejścia od użytkownika.
Limity SOQL w Apex (cross-reference z Salesforce Ben i twopirconsulting na podstawie oficjalnej dokumentacji): 100 zapytań per synchroniczna transakcja, 200 per asynchroniczna (Batch, Queueable, Future). Maksymalnie 50 000 rekordów zwróconych per transakcja. Aggregate queries liczą każdy zwrócony wiersz AggregateResult jako 1 wiersz, niezależnie od liczby zagregowanych rekordów.
Krytyczny wzorzec: nigdy nie umieszczaj zapytania SOQL wewnątrz pętli. Jeden SOQL wykonany N razy dla N rekordów to N zapytań. Zamiast tego: zbierz wszystkie ID przed pętlą, wykonaj jedno zapytanie do Mapy, w pętli sięgaj do Mapy. To wzorzec bulkification i jest tym samym wzorcem co w triggerach Apex. Dla piszących do egzaminu PD1, SOQL limits i query in loops to klasyczne pułapki – szczegółowy zakres egzaminu opisujemy w przewodniku po certyfikacie Platform Developer I.
SOQL to język prosty w podstawach, ale skrywający niuanse – relacje wielopoziomowe, agregacje z HAVING, dynamiczne zapytania. Im więcej czasu spędzisz na Workbench lub Developer Console pisząc i testując zapytania ręcznie, tym lepiej zrozumiesz co platforma zwraca i jak to zoptymalizować. Jakie zapytania SOQL sprawiają Ci najwięcej trudności w codziennej pracy?