Free Interactive Course · Spark + Scala

Filtering & Conditional Logic

How to keep the rows you want and derive new columns from conditions. filter/where and every operator, then the flagship when/otherwise for CASE-style logic, and finally null handling with coalesce — the functions you'll use in literally every Spark job.

The dataset

This module uses the two DataFrames from Foundations:

peopleid, name, country, age, signup_date
ordersid, user_id, product, category, amount, qty, status, ordered_at
2.1

filter & where — keeping rows

filter and where are the exact same methodwhere is just a SQL-friendly alias. Both keep only the rows for which the condition is true. You can pass a Column expression or a SQL string.

Signature
def filter(condition: Column): Dataset[Row]
def filter(conditionExpr: String): Dataset[Row]
def where(condition: Column): Dataset[Row]   // alias of filter
def where(conditionExpr: String): Dataset[Row]

1. A Column expression

The idiomatic form — build the condition with column operators. Keep only people from India:

Scala
people.filter($"country" === "India").show()
Run the equivalent Spark SQL — edit it and press Run
SQL
🔮Before you run — how many people are from India?
⌘/Ctrl + Enter

2. A SQL string

Sometimes a plain SQL string is more readable — especially for people coming from SQL. where makes it read naturally:

Scala
people.where("age > 30 AND country = 'India'").show()
Run the equivalent Spark SQL — edit it and press Run
SQL
⌘/Ctrl + Enter

3. Chaining = implicit AND

Stacking filter calls is the same as joining them with AND — handy when conditions are built up in stages:

Scala
people
  .filter($"country" === "India")
  .filter($"age" > 30)
  .show()
Run the equivalent Spark SQL — edit it and press Run
SQL
⌘/Ctrl + Enter
filter and where are 100% interchangeable — pick one per codebase for consistency. Spark teams usually use filter in Scala (it's the Dataset method name) and where when the condition reads like SQL.
2.2

Comparison & boolean operators

Spark Columns override the operators. Equality is === (triple), inequality is =!=not == or !=, which compare object references and are a classic beginner bug. Combine conditions with && (and), || (or), and negate with !.

Signature
===   equal to           =!=   not equal to
<  <=  >  >=            less / greater (and-or-equal)
&&   and   ||   or   !   not      (wrap each side in parens!)
Scala
people.filter(
  ($"country" === "India") && ($"age" =!= 34)
).show()
Run the equivalent Spark SQL — edit it and press Run
SQL
🔮India, but NOT age 34. How many rows survive?
⌘/Ctrl + Enter

An OR across countries, negating with ! is also common — everyone not in India or the USA:

Scala
people.filter(!(($"country" === "India") || ($"country" === "USA"))).show()
Run the equivalent Spark SQL — edit it and press Run
SQL
⌘/Ctrl + Enter
The #1 gotcha: because && and || have lower precedence than === in Scala, always wrap each comparison in parentheses: ($"a" === 1) && ($"b" === 2). Forget the parens and you get a compile error or wrong results.
2.3

isin & between

isin matches a column against a list of values (like SQL IN). between matches an inclusive range. Both replace long chains of || / &&.

Signature
def isin(list: Any*): Column
def between(lowerBound: Any, upperBound: Any): Column   // inclusive
Scala
orders.filter($"category".isin("Electronics", "Home")).show()
Run the equivalent Spark SQL — edit it and press Run
SQL
🔮Orders in Electronics OR Home. How many?
⌘/Ctrl + Enter

between is inclusive on both ends — orders with an amount from 10 to 50:

Scala
orders.filter($"amount".between(10, 50)).show()
Run the equivalent Spark SQL — edit it and press Run
SQL
⌘/Ctrl + Enter
isin takes a varargs list; to pass a Scala Seq, splat it with : _*$"category".isin(cats: _*). And between is inclusive, so between(10, 50) keeps both 10 and 50.
2.4

Pattern matching: like, contains, startsWith, rlike

For string conditions, Spark gives you SQL like (with % and _ wildcards), the convenience methods contains / startsWith / endsWith, and rlike for full regular expressions.

Signature
def like(literal: String): Column        // % = any run, _ = one char
def contains(other: Any): Column
def startsWith(literal: String): Column
def endsWith(literal: String): Column
def rlike(literal: String): Column        // Java regex

1. like with wildcards

Products whose name ends in 'e' (%e = anything then an 'e'):

Scala
orders.filter($"product".like("%e")).select($"product").show()
Run the equivalent Spark SQL — edit it and press Run
SQL
⌘/Ctrl + Enter

2. contains / startsWith / endsWith

These are the readable shortcuts — no wildcards to remember. Products that contain the word 'Cable':

Scala
orders.filter($"product".contains("Cable")).select($"product").show()
Run the equivalent Spark SQL — edit it and press Run
SQL
⌘/Ctrl + Enter

3. rlike — regular expressions

rlike takes a full Java regex — reach for it when wildcards aren't enough. Match products that start with 'Wireless' or 'USB':

Scala
orders.filter($"product".rlike("^(Wireless|USB)"))
  .select($"product").show()
rlike("^(Wireless|USB)") — regex anchored at the start
product
Wireless Mouse
USB-C Cable
baked output — Spark-only type, not runnable in the browser engine
rlike uses Java regex syntax and is case-sensitive by default (prefix (?i) for case-insensitive). like is simpler and faster when you only need %/_ wildcards — don't pull in a regex you don't need.
2.5

when / otherwise — CASE logic

when / otherwise is Spark's CASE WHEN — the workhorse for deriving a column from conditions. Chain multiple whens for multiple branches; end with otherwise for the fallback (skip it and unmatched rows get null). Import it from org.apache.spark.sql.functions.

Signature
def when(condition: Column, value: Any): Column
def otherwise(value: Any): Column
// chain: when(c1, v1).when(c2, v2).otherwise(vDefault)

1. Single when + otherwise

Scala
import org.apache.spark.sql.functions.when

people.select(
  $"name",
  when($"age" >= 30, "senior").otherwise("junior").as("tier")
).show()
Run the equivalent Spark SQL — edit it and press Run
SQL
⌘/Ctrl + Enter

2. Chained whens — multiple branches

Each when is tested in order; the first match wins. Bucket orders by amount:

Scala
orders.select(
  $"product", $"amount",
  when($"amount" >= 50, "premium")
    .when($"amount" >= 20, "standard")
    .otherwise("budget").as("band")
).show()
Run the equivalent Spark SQL — edit it and press Run
SQL
⌘/Ctrl + Enter

3. No otherwisenull

Drop the otherwise and any row that matches no branch gets null — useful when you only want to label some rows:

Scala
orders.select(
  $"product",
  when($"status" === "cancelled", "⚠ refund").as("flag")
).show()
Run the equivalent Spark SQL — edit it and press Run
SQL
⌘/Ctrl + Enter
Conditions are evaluated top-to-bottom and the first true branch wins, so order your whens from most specific to least. Overlapping ranges in the wrong order (e.g. >= 20 before >= 50) is a common logic bug.
2.6

Null handling: isNull, coalesce, na.fill / na.drop

Nulls are unavoidable — they show up from joins, bad data, and optional fields. Test for them with isNull / isNotNull, fill them with coalesce (first non-null wins), or handle them in bulk with the DataFrame.na helpers fill, drop, and replace.

Signature
def isNull: Column          def isNotNull: Column
def coalesce(e: Column*): Column   // first non-null argument
df.na.fill(value)   df.na.drop()   df.na.replace(col, map)

To see nulls in action, here's a tiny contacts DataFrame where some phone numbers are missing:

Scala
val contacts = Seq(
  ("Aisha", "555-0101"),
  ("Rohan", null),
  ("Mia",   "555-0199"),
  ("Liam",  null)
).toDF("name", "phone")
input DataFrame: contacts
namephone
Aisha555-0101
Rohannull
Mia555-0199
Liamnull
baked output — Spark-only type, not runnable in the browser engine

1. Find the gaps with isNull

Scala
contacts.filter($"phone".isNull).show()
Run the equivalent Spark SQL — edit it and press Run
SQL
⌘/Ctrl + Enter

2. Fill nulls with coalesce

coalesce returns the first argument that isn't null — perfect for a default value:

Scala
import org.apache.spark.sql.functions.{coalesce, lit}

contacts.select(
  $"name",
  coalesce($"phone", lit("no phone on file")).as("phone")
).show()
Run the equivalent Spark SQL — edit it and press Run
SQL
⌘/Ctrl + Enter

3. Bulk handling with na.fill / na.drop

The na helpers act on many columns at once. na.fill replaces nulls with a default; na.drop removes any row containing a null:

Scala
contacts.na.fill("unknown").show()   // every null string -> "unknown"
contacts.na.drop().show()            // keep only fully-populated rows
na.drop() removes Rohan & Liam (their phone is null)
namephone (na.drop result)
Aisha555-0101
Mia555-0199
baked output — Spark-only type, not runnable in the browser engine
coalesce the function (fills nulls in a column) is different from the coalesce method on a DataFrame (which reduces partitions — see the I/O module). Same name, unrelated jobs. Also: na.fill("unknown") only touches string columns; pass a number to fill numeric ones.
P
PriyaFounder · Kettle & Co.now
Doing a quick order review 📦 — can you label every order by size? Call it big if the amount is $30 or more, otherwise small. Give me the product, the amount, and that size label.
🎯 Your mission From orders, return product, amount, and a size column that is 'big' when amount ≥ 30 and 'small' otherwise.
SQL
⌘/Ctrl + Enter

Ready for the full data stack?

These Spark functions are one piece. When you want pipelines, Kafka, lakehouse table formats, and a full end-to-end project, the Data Engineering course is free too.

Explore Data Engineering →

Comments

0

Join the conversation. Sign in to leave a comment — questions and feedback welcome.