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.

Lekcja 3 z 714 min czytania

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):

FirstNameLastNameEmail
AnnaNowakanna.nowak@example.com
PiotrKowalskipiotr.kowalski@example.com
EwaWiśniewskaewa.wisniewska@example.com
AniaNowakanna.nowak@example.com
JanZieliń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):

NamePrice__cArea_m2__cStatus__cSprzedający (Seller__c)
Lipowa 5/12, Gdańsk450 00050DostępnaAnna Nowak
Kwiatowa 3, Sopot1 250 000140ZarezerwowanaAnna Nowak
Słoneczna 8/4, Gdańsk380 00036SprzedanaPiotr Kowalski
Ogrodowa 15, Gdynia890 000120Dostępna(puste)
Morska 21/3, Gdynia640 00062NowaPiotr Kowalski
Polna 2/7, Gdańsk520 00055DostępnaEwa Wiśniewska

Pozostałe pola tych samych nieruchomości:

NameAmenities__cIs_Furnished__cAvailable_From__c
Lipowa 5/12, GdańskBalkon;Windatrue2026-11-01
Kwiatowa 3, SopotMiejsce parkingowe;Ogród;Piwnicafalse(puste)
Słoneczna 8/4, GdańskBalkontrue2026-03-01
Ogrodowa 15, GdyniaMiejsce parkingowe;Ogródfalse2026-10-01
Morska 21/3, GdyniaWinda;Miejsce parkingowetrue2026-12-15
Polna 2/7, GdańskPiwnicafalse2026-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:

NameAmount__cNieruchomość (Property__c)
OF-00001395 000Lipowa 5/12, Gdańsk
OF-00002430 000Lipowa 5/12, Gdańsk
OF-000031 200 000Kwiatowa 3, Sopot
OF-00004360 000Słoneczna 8/4, Gdańsk
OF-00005375 000Słoneczna 8/4, Gdańsk
OF-00006380 000Słoneczna 8/4, Gdańsk
OF-00007850 000Ogrodowa 15, Gdynia
OF-00008505 000Polna 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. Bez ORDER BY Salesforce 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

OperatorZnaczeniePrzykład
= / !=równe / różneIndustry = 'Technology'
< <= > >=mniejsze / większeAmount >= 10000
LIKEpasuje do wzorcaName LIKE 'Acme%'
IN / NOT INjest / nie jest na liścieStageName IN ('Prospecting', 'Qualification')
INCLUDES / EXCLUDESwartości listy wielokrotnego wyboruAmenities__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):

NameArea_m2__c
Lipowa 5/12, Gdańsk50
Morska 21/3, Gdynia62
Polna 2/7, Gdańsk55

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):

NameStatus__c
Kwiatowa 3, SopotZarezerwowana
Morska 21/3, GdyniaNowa

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.
  • LIKE uż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):

NamePrice__c
Lipowa 5/12, Gdańsk450 000
Słoneczna 8/4, Gdańsk380 000
Polna 2/7, Gdańsk520 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):

NameStatus__c
Ogrodowa 15, GdyniaDostę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):

NameAmenities__c
Lipowa 5/12, GdańskBalkon;Winda
Słoneczna 8/4, GdańskBalkon
Morska 21/3, GdyniaWinda;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):

NameAmenities__c
Lipowa 5/12, GdańskBalkon;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):

NameAmenities__c
Lipowa 5/12, GdańskBalkon;Winda
Słoneczna 8/4, GdańskBalkon
Morska 21/3, GdyniaWinda;Miejsce parkingowe
Polna 2/7, GdańskPiwnica

Łą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):

NameStatus__cPrice__cIs_Furnished__c
Lipowa 5/12, GdańskDostępna450 000true
Polna 2/7, GdańskDostępna520 000false

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):

NameStatus__cPrice__cIs_Furnished__c
Lipowa 5/12, GdańskDostępna450 000true
Słoneczna 8/4, GdańskSprzedana380 000true
Morska 21/3, GdyniaNowa640 000true
Polna 2/7, GdańskDostępna520 000false

Daty i godziny

Salesforce ma dwa typy pól czasowych i każdy zapisuje się inaczej:

Typ polaPrzykład polaFormat w zapytaniu
DateCloseDate, Birthdate2026-10-01
Date/TimeCreatedDate, LastModifiedDate2026-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, TOMORROWdziś, wczoraj, jutro
THIS_WEEK, LAST_WEEK, NEXT_WEEKbieżący, poprzedni, następny tydzień
THIS_MONTH, LAST_MONTH, NEXT_MONTHbieżący, poprzedni, następny miesiąc
THIS_QUARTER, THIS_YEAR, LAST_YEARbieżący kwartał, bieżący i poprzedni rok
LAST_N_DAYS:30, NEXT_N_DAYS:7ostatnie 30 dni, najbliższe 7 dni
THIS_FISCAL_YEARbieżą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):

NameAvailable_From__c
Ogrodowa 15, Gdynia2026-10-01
Polna 2/7, Gdańsk2026-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):

NameAvailable_From__c
Lipowa 5/12, Gdańsk2026-11-01
Polna 2/7, Gdańsk2026-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):

NameAvailable_From__c
Słoneczna 8/4, Gdańsk2026-03-01
Ogrodowa 15, Gdynia2026-10-01

Sortowanie i stronicowanie

  • ORDER BY Pole ASC (rosnąco, domyślnie) albo DESC (malejąco). Możesz sortować po kilku polach: ORDER BY StageName, Amount DESC.
  • NULLS FIRST / NULLS LAST decyduje, gdzie trafią rekordy z pustym polem.
  • LIMIT 100 ogranicza liczbę wyników. Dodawaj go przy pierwszych testach, żeby nie pobierać tysięcy rekordów.
  • OFFSET 100 pomija 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):

NameAvailable_From__c
Kwiatowa 3, Sopot(puste)
Słoneczna 8/4, Gdańsk2026-03-01
Ogrodowa 15, Gdynia2026-10-01
Polna 2/7, Gdańsk2026-10-20
Lipowa 5/12, Gdańsk2026-11-01
Morska 21/3, Gdynia2026-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):

NamePrice__c
Morska 21/3, Gdynia640 000
Polna 2/7, Gdańsk520 000

Ćwiczenie

Na obiektach z modułu 2 kursu podstaw i obiektach standardowych napisz zapytania, które zwrócą:

  1. Nieruchomości o statusie Dostępna lub Zarezerwowana, z ceną od 300 000 do 800 000, posortowane od najtańszej.
  2. Nieruchomości bez uzupełnionego pola Sprzedający.
  3. Nieruchomości umeblowane, które mają jednocześnie balkon i miejsce parkingowe.
  4. Kontakty utworzone w tym miesiącu, których adres e-mail kończy się na @example.com.
  5. 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.