Filtrowanie w SOQL: WHERE, daty i puste wartości
Precyzyjne warunki WHERE: operatory, LIKE, IN, puste wartości, listy wyboru, daty i literały dat oraz sortowanie i stronicowanie wyników.
Większość pracy z SOQL to dobre filtrowanie. Dokument w PDF Butlerze ma pokazać tylko otwarte zgłoszenia klienta, eksport z Salesforce Inspectora tylko kontakty z tego roku. W tej lekcji poznasz wszystko, czego potrzebujesz w klauzuli WHERE.
Przykładowe dane
Wszystkie przykłady w tej lekcji działają na tych samych, małych danych z obiektów zbudowanych w module 2 kursu Podstawy Salesforce: 5 kontaktów, 6 nieruchomości i 8 ofert. Tych samych danych używamy w lekcjach o relacjach i agregacjach, więc wystarczy przygotować je raz.
Obiekt Contact (kontakty):
| FirstName | LastName | |
|---|---|---|
| Anna | Nowak | anna.nowak@example.com |
| Piotr | Kowalski | piotr.kowalski@example.com |
| Ewa | Wiśniewska | ewa.wisniewska@example.com |
| Ania | Nowak | anna.nowak@example.com |
| Jan | Zieliński | (puste) |
Ania Nowak to ta sama osoba co Anna Nowak, wprowadzona drugi raz przy imporcie. Takie duplikaty zdarzają się w każdym orgu, a w lekcji o agregacjach nauczysz się je wyszukiwać.
Obiekt Nieruchomość (Property__c):
| Name | Price__c | Area_m2__c | Status__c | Sprzedający (Seller__c) |
|---|---|---|---|---|
| Lipowa 5/12, Gdańsk | 450 000 | 50 | Dostępna | Anna Nowak |
| Kwiatowa 3, Sopot | 1 250 000 | 140 | Zarezerwowana | Anna Nowak |
| Słoneczna 8/4, Gdańsk | 380 000 | 36 | Sprzedana | Piotr Kowalski |
| Ogrodowa 15, Gdynia | 890 000 | 120 | Dostępna | (puste) |
| Morska 21/3, Gdynia | 640 000 | 62 | Nowa | Piotr Kowalski |
| Polna 2/7, Gdańsk | 520 000 | 55 | Dostępna | Ewa Wiśniewska |
Pozostałe pola tych samych nieruchomości:
| Name | Amenities__c | Is_Furnished__c | Available_From__c |
|---|---|---|---|
| Lipowa 5/12, Gdańsk | Balkon;Winda | true | 2026-11-01 |
| Kwiatowa 3, Sopot | Miejsce parkingowe;Ogród;Piwnica | false | (puste) |
| Słoneczna 8/4, Gdańsk | Balkon | true | 2026-03-01 |
| Ogrodowa 15, Gdynia | Miejsce parkingowe;Ogród | false | 2026-10-01 |
| Morska 21/3, Gdynia | Winda;Miejsce parkingowe | true | 2026-12-15 |
| Polna 2/7, Gdańsk | Piwnica | false | 2026-10-20 |
Wartości listy wielokrotnego wyboru Salesforce zwraca jako jeden tekst rozdzielony średnikami, dlatego w tabeli widzisz np. Balkon;Winda.
Obiekt Oferta (Offer__c), powiązany z nieruchomością relacją Master-Detail:
| Name | Amount__c | Nieruchomość (Property__c) |
|---|---|---|
| OF-00001 | 395 000 | Lipowa 5/12, Gdańsk |
| OF-00002 | 430 000 | Lipowa 5/12, Gdańsk |
| OF-00003 | 1 200 000 | Kwiatowa 3, Sopot |
| OF-00004 | 360 000 | Słoneczna 8/4, Gdańsk |
| OF-00005 | 375 000 | Słoneczna 8/4, Gdańsk |
| OF-00006 | 380 000 | Słoneczna 8/4, Gdańsk |
| OF-00007 | 850 000 | Ogrodowa 15, Gdynia |
| OF-00008 | 505 000 | Polna 2/7, Gdańsk |
Uwagi do tabel z wynikami. Kwoty są sformatowane dla czytelności, a Salesforce zwraca je jako zwykłe liczby, np.
450000. BezORDER BYSalesforce nie gwarantuje kolejności rekordów, więc w takich wynikach pokazujemy je w kolejności z tabel powyżej. Przykłady z datami zakładają, że dziś jest czwartek, 15 października 2026.
Chcesz sprawdzić przykłady u siebie? Dodaj te rekordy w swoim orgu do nauki: ręcznie albo importem z pliku CSV, jak w lekcji Praca z rekordami i widokami list. Numery ofert nadaje Salesforce, więc u Ciebie mogą być inne. Pole Dostępna od ma wartość domyślną TODAY(), więc wpisz daty z tabeli ręcznie, a w Kwiatowej 3 wyczyść to pole. Zapytania z literałami dat zwrócą u Ciebie inne rekordy, bo liczą się od Twojej dzisiejszej daty.
Operatory porównania
| Operator | Znaczenie | Przykład |
|---|---|---|
= / != | równe / różne | Industry = 'Technology' |
< <= > >= | mniejsze / większe | Amount >= 10000 |
LIKE | pasuje do wzorca | Name LIKE 'Acme%' |
IN / NOT IN | jest / nie jest na liście | StageName IN ('Prospecting', 'Qualification') |
INCLUDES / EXCLUDES | wartości listy wielokrotnego wyboru | Amenities__c INCLUDES ('Balkon') |
Przykład 1. Zakres liczb
SELECT Name, Area_m2__c
FROM Property__c
WHERE Area_m2__c >= 50 AND Area_m2__c < 100
Co zwróci: nieruchomości o powierzchni co najmniej 50 i poniżej 100 m². Lipowa 5/12 ma dokładnie 50 m², a >= obejmuje wartość graniczną, więc jest w wyniku. Przy > by odpadła.
Wynik (3 rekordy):
| Name | Area_m2__c |
|---|---|
| Lipowa 5/12, Gdańsk | 50 |
| Morska 21/3, Gdynia | 62 |
| Polna 2/7, Gdańsk | 55 |
Przykład 2. Lista wartości: IN
SELECT Name, Status__c
FROM Property__c
WHERE Status__c IN ('Nowa', 'Zarezerwowana')
Co zwróci: nieruchomości, których status jest jedną z wartości na liście. To krótszy zapis warunku Status__c = 'Nowa' OR Status__c = 'Zarezerwowana'.
Wynik (2 rekordy):
| Name | Status__c |
|---|---|
| Kwiatowa 3, Sopot | Zarezerwowana |
| Morska 21/3, Gdynia | Nowa |
Tekst: apostrofy, wielkość liter i wzorce
- Tekst zawsze zapisujesz w apostrofach:
'Warszawa'. Cudzysłów"Warszawa"spowoduje błąd. - Apostrof wewnątrz tekstu poprzedzasz ukośnikiem:
'O\'Brien'. - Porównania tekstu w SOQL nie rozróżniają wielkości liter:
'warszawa'znajdzie teżWarszawa. To inaczej niż we Flow, gdzie liczy się każdy znak. LIKEużywa dwóch symboli wieloznacznych:%to dowolny ciąg znaków,_to dokładnie jeden znak.
SELECT Name, Email
FROM Contact
WHERE Email LIKE '%@example.com'
Przykład 3. LIKE i wielkość liter
SELECT Name, Price__c
FROM Property__c
WHERE Name LIKE '%gdańsk'
Co zwróci: nieruchomości, których nazwa kończy się na „gdańsk”. % na początku wzorca zastępuje wszystko, co stoi przed nazwą miasta, a mała litera nie przeszkadza, bo SOQL nie rozróżnia wielkości liter.
Wynik (3 rekordy):
| Name | Price__c |
|---|---|
| Lipowa 5/12, Gdańsk | 450 000 |
| Słoneczna 8/4, Gdańsk | 380 000 |
| Polna 2/7, Gdańsk | 520 000 |
Puste wartości
Puste pole sprawdzasz słowem null, bez apostrofów:
SELECT Name, Phone
FROM Contact
WHERE Email = null
Salesforce zapisuje puste pole tekstowe jako null, a nie jako pusty tekst, więc warunek Email = '' niczego nie znajdzie.
Wskazówka: przy eksporcie danych do poprawy (np. kontaktów bez e-maila) zacznij właśnie od warunku
= null. Dostaniesz gotową listę braków do uzupełnienia.
Przykład 4. Puste pole relacji
SELECT Name, Status__c
FROM Property__c
WHERE Seller__c = null
Co zwróci: nieruchomości bez przypisanego sprzedającego. W danych jest tylko jedna taka: Ogrodowa 15. Odwrotny warunek Seller__c != null zwróci pozostałe pięć.
Wynik (1 rekord):
| Name | Status__c |
|---|---|
| Ogrodowa 15, Gdynia | Dostępna |
Listy wyboru i pola wyboru
Picklist filtrujesz po nazwie API wartości, a nie po etykiecie widocznej na ekranie. W orgu przetłumaczonym na polski etykieta może brzmieć „Zamknięta wygrana”, a wartość w zapytaniu to nadal 'Closed Won'. Nazwy API wartości znajdziesz w Object Manager, w ustawieniach pola.
Lista wielokrotnego wyboru ma własne operatory. Wartości rozdzielone przecinkami oznaczają „lub”. To zapytanie znajdzie nieruchomości z balkonem albo windą:
SELECT Name FROM Property__c WHERE Amenities__c INCLUDES ('Balkon', 'Winda')
Wartości połączone średnikiem w jednym apostrofie oznaczają „i”. To zapytanie znajdzie tylko nieruchomości, które mają balkon i windę jednocześnie:
SELECT Name FROM Property__c WHERE Amenities__c INCLUDES ('Balkon;Winda')
Checkbox porównujesz z true albo false, bez apostrofów: WHERE Is_Furnished__c = true.
Przykład 5. INCLUDES z przecinkiem: „lub”
SELECT Name, Amenities__c
FROM Property__c
WHERE Amenities__c INCLUDES ('Balkon', 'Winda')
Co zwróci: nieruchomości, które mają balkon, windę albo jedno i drugie. Morska 21/3 nie ma balkonu, ale wystarczy jej winda.
Wynik (3 rekordy):
| Name | Amenities__c |
|---|---|
| Lipowa 5/12, Gdańsk | Balkon;Winda |
| Słoneczna 8/4, Gdańsk | Balkon |
| Morska 21/3, Gdynia | Winda;Miejsce parkingowe |
Przykład 6. INCLUDES ze średnikiem: „i”
SELECT Name, Amenities__c
FROM Property__c
WHERE Amenities__c INCLUDES ('Balkon;Winda')
Co zwróci: tylko nieruchomości, które mają jednocześnie balkon i windę. Z trzech rekordów z poprzedniego przykładu zostaje jeden. Obie formy możesz łączyć: INCLUDES ('Balkon;Winda', 'Ogród') oznacza „balkon i winda albo ogród”.
Wynik (1 rekord):
| Name | Amenities__c |
|---|---|
| Lipowa 5/12, Gdańsk | Balkon;Winda |
Przykład 7. EXCLUDES
SELECT Name, Amenities__c
FROM Property__c
WHERE Amenities__c EXCLUDES ('Ogród')
Co zwróci: nieruchomości bez ogrodu. Kwiatowa 3 i Ogrodowa 15 mają ogród wśród kilku udogodnień i to wystarczy, żeby odpadły.
Wynik (4 rekordy):
| Name | Amenities__c |
|---|---|
| Lipowa 5/12, Gdańsk | Balkon;Winda |
| Słoneczna 8/4, Gdańsk | Balkon |
| Morska 21/3, Gdynia | Winda;Miejsce parkingowe |
| Polna 2/7, Gdańsk | Piwnica |
Łączenie warunków
Warunki łączysz słowami AND, OR i NOT. Gdy w jednym zapytaniu mieszasz AND i OR, SOQL wymaga nawiasów, które jednoznacznie pokazują kolejność:
SELECT Name, StageName, Amount
FROM Opportunity
WHERE IsClosed = false
AND (Amount > 50000 OR Probability >= 70)
Przykład 8. Nawiasy wokół OR
SELECT Name, Status__c, Price__c, Is_Furnished__c
FROM Property__c
WHERE Status__c = 'Dostępna'
AND (Price__c < 600000 OR Is_Furnished__c = true)
Co zwróci: dostępne nieruchomości, które są tańsze niż 600 000 albo umeblowane. Ogrodowa 15 jest dostępna, ale droższa i nieumeblowana, więc odpada.
Wynik (2 rekordy):
| Name | Status__c | Price__c | Is_Furnished__c |
|---|---|---|---|
| Lipowa 5/12, Gdańsk | Dostępna | 450 000 | true |
| Polna 2/7, Gdańsk | Dostępna | 520 000 | false |
Przykład 9. Te same warunki, inne nawiasy
SELECT Name, Status__c, Price__c, Is_Furnished__c
FROM Property__c
WHERE (Status__c = 'Dostępna' AND Price__c < 600000)
OR Is_Furnished__c = true
Co zwróci: dostępne nieruchomości tańsze niż 600 000 oraz wszystkie umeblowane, bez względu na status. Dlatego do wyniku dochodzą sprzedana Słoneczna 8/4 i nowa Morska 21/3. Warunki są te same co w przykładzie 8, a wynik zmieniło tylko przesunięcie nawiasów.
Wynik (4 rekordy):
| Name | Status__c | Price__c | Is_Furnished__c |
|---|---|---|---|
| Lipowa 5/12, Gdańsk | Dostępna | 450 000 | true |
| Słoneczna 8/4, Gdańsk | Sprzedana | 380 000 | true |
| Morska 21/3, Gdynia | Nowa | 640 000 | true |
| Polna 2/7, Gdańsk | Dostępna | 520 000 | false |
Daty i godziny
Salesforce ma dwa typy pól czasowych i każdy zapisuje się inaczej:
| Typ pola | Przykład pola | Format w zapytaniu |
|---|---|---|
| Date | CloseDate, Birthdate | 2026-10-01 |
| Date/Time | CreatedDate, LastModifiedDate | 2026-10-01T00:00:00Z |
Daty piszesz bez apostrofów. Litera Z na końcu oznacza czas UTC. Porównanie pola Date/Time z samą datą (CreatedDate > 2026-10-01) zakończy się błędem.
Wygodniej jest używać literałów dat, które Salesforce przelicza sam:
| Literał | Znaczenie |
|---|---|
TODAY, YESTERDAY, TOMORROW | dziś, wczoraj, jutro |
THIS_WEEK, LAST_WEEK, NEXT_WEEK | bieżący, poprzedni, następny tydzień |
THIS_MONTH, LAST_MONTH, NEXT_MONTH | bieżący, poprzedni, następny miesiąc |
THIS_QUARTER, THIS_YEAR, LAST_YEAR | bieżący kwartał, bieżący i poprzedni rok |
LAST_N_DAYS:30, NEXT_N_DAYS:7 | ostatnie 30 dni, najbliższe 7 dni |
THIS_FISCAL_YEAR | bieżący rok obrotowy (wg ustawień firmy) |
SELECT Subject, Status, CreatedDate
FROM Case
WHERE CreatedDate = LAST_N_DAYS:30
AND IsClosed = false
Literały działają tak samo dla pól Date i Date/Time, więc oszczędzają kłopotu z formatem.
Jeśli potrzebujesz konkretnego roku lub miesiąca, użyj funkcji dat:
SELECT Name, CloseDate
FROM Opportunity
WHERE CALENDAR_YEAR(CloseDate) = 2026
AND CALENDAR_MONTH(CloseDate) = 10
Kolejne trzy przykłady zakładają, że dziś jest czwartek, 15 października 2026. W Twoim orgu te same zapytania zwrócą inne rekordy, bo literały liczą się od prawdziwej, dzisiejszej daty.
Przykład 10. Bieżący miesiąc: THIS_MONTH
SELECT Name, Available_From__c
FROM Property__c
WHERE Available_From__c = THIS_MONTH
Co zwróci: nieruchomości dostępne od dowolnego dnia października 2026, także od dnia, który już minął. THIS_MONTH obejmuje cały miesiąc, od 1 do 31 października.
Wynik (2 rekordy):
| Name | Available_From__c |
|---|---|
| Ogrodowa 15, Gdynia | 2026-10-01 |
| Polna 2/7, Gdańsk | 2026-10-20 |
Przykład 11. Najbliższe dni: NEXT_N_DAYS
SELECT Name, Available_From__c
FROM Property__c
WHERE Available_From__c = NEXT_N_DAYS:30
Co zwróci: nieruchomości, które staną się dostępne w ciągu 30 dni od jutra, czyli od 16 października do 14 listopada. Literał przekracza granicę miesiąca, więc łapie też Lipową 5/12 z 1 listopada. NEXT_N_DAYS nie obejmuje dzisiejszego dnia.
Wynik (2 rekordy):
| Name | Available_From__c |
|---|---|
| Lipowa 5/12, Gdańsk | 2026-11-01 |
| Polna 2/7, Gdańsk | 2026-10-20 |
Przykład 12. Porównanie z TODAY i puste daty
SELECT Name, Available_From__c
FROM Property__c
WHERE Available_From__c <= TODAY
Co zwróci: nieruchomości dostępne już dziś albo wcześniej. Kwiatowa 3 ma puste pole daty, a puste pole nie spełnia porównania <=, więc nie trafi do wyniku. Jeśli puste daty też Cię interesują, dopisz OR Available_From__c = null.
Wynik (2 rekordy):
| Name | Available_From__c |
|---|---|
| Słoneczna 8/4, Gdańsk | 2026-03-01 |
| Ogrodowa 15, Gdynia | 2026-10-01 |
Sortowanie i stronicowanie
ORDER BY Pole ASC(rosnąco, domyślnie) alboDESC(malejąco). Możesz sortować po kilku polach:ORDER BY StageName, Amount DESC.NULLS FIRST/NULLS LASTdecyduje, gdzie trafią rekordy z pustym polem.LIMIT 100ogranicza liczbę wyników. Dodawaj go przy pierwszych testach, żeby nie pobierać tysięcy rekordów.OFFSET 100pomija pierwsze wyniki. Maksymalna wartość to 2000, więc do dużych eksportów OFFSET się nie nadaje.
SELECT Name, Price__c, Available_From__c
FROM Property__c
WHERE Status__c = 'Dostępna'
ORDER BY Price__c DESC NULLS LAST
LIMIT 10
Przykład 13. Gdzie trafiają puste wartości
SELECT Name, Available_From__c
FROM Property__c
ORDER BY Available_From__c
Co zwróci: wszystkie nieruchomości od najwcześniejszej daty dostępności. Kwiatowa 3 z pustą datą jest na samej górze, bo domyślnie Salesforce stawia puste wartości na początku. Po dopisaniu NULLS LAST trafi na koniec listy.
Wynik (6 rekordów):
| Name | Available_From__c |
|---|---|
| Kwiatowa 3, Sopot | (puste) |
| Słoneczna 8/4, Gdańsk | 2026-03-01 |
| Ogrodowa 15, Gdynia | 2026-10-01 |
| Polna 2/7, Gdańsk | 2026-10-20 |
| Lipowa 5/12, Gdańsk | 2026-11-01 |
| Morska 21/3, Gdynia | 2026-12-15 |
Przykład 14. Druga strona wyników: LIMIT i OFFSET
SELECT Name, Price__c
FROM Property__c
ORDER BY Price__c DESC
LIMIT 2 OFFSET 2
Co zwróci: drugą stronę listy, gdy na stronie mieszczą się dwie nieruchomości. OFFSET 2 pomija dwie najdroższe (Kwiatową 3 i Ogrodową 15), a LIMIT 2 bierze dwie kolejne. Bez ORDER BY taka „strona” zawierałaby przypadkowe rekordy.
Wynik (2 rekordy):
| Name | Price__c |
|---|---|
| Morska 21/3, Gdynia | 640 000 |
| Polna 2/7, Gdańsk | 520 000 |
Ćwiczenie
Na obiektach z modułu 2 kursu podstaw i obiektach standardowych napisz zapytania, które zwrócą:
- Nieruchomości o statusie Dostępna lub Zarezerwowana, z ceną od 300 000 do 800 000, posortowane od najtańszej.
- Nieruchomości bez uzupełnionego pola Sprzedający.
- Nieruchomości umeblowane, które mają jednocześnie balkon i miejsce parkingowe.
- Kontakty utworzone w tym miesiącu, których adres e-mail kończy się na
@example.com. - Szanse sprzedaży zamknięte w poprzednim kwartale, posortowane malejąco po kwocie.
Podpowiedź do zadania 5: literał poprzedniego kwartału to
LAST_QUARTER.
Sprawdź się
1. Dlaczego zapytanie WHERE Email = '' nie znajduje kontaktów bez e-maila?
Pokaż odpowiedź
Bo Salesforce zapisuje puste pola tekstowe jako null, a nie jako pusty tekst. Poprawny warunek to WHERE Email = null.
2. Org jest przetłumaczony na polski, a warunek StageName = 'Zamknięta wygrana' nic nie zwraca. Co jest nie tak?
Pokaż odpowiedź
Picklisty filtruje się po nazwie API wartości, a nie po przetłumaczonej etykiecie. Trzeba użyć wartości 'Closed Won'.
3. Czym różni się INCLUDES ('Balkon', 'Winda') od INCLUDES ('Balkon;Winda')?
Pokaż odpowiedź
Pierwszy warunek znajdzie rekordy z balkonem lub windą (wystarczy jedna z wartości). Drugi znajdzie tylko rekordy, które mają obie wartości jednocześnie.
Następny krok: nauczysz się pobierać w jednym zapytaniu dane z rekordów powiązanych, np. nieruchomość razem z jej ofertami.