Running SQL Queries on DataFrames

Spark tutorial · PySpark.in

Running SQL Queries on DataFrames

Now that we know how to register DataFrames as tables or views, we can explore running SQL queries directly on these views using PySpark. The spark.sql() function is the workhorse for this  it allows you to execute a SQL query as a string and returns the result as a new DataFrame. This means you can leverage the full expressiveness of SQL (SELECT, WHERE, GROUP BY, HAVING, JOIN, etc.) on your distributed data.

Using Spark SQL queries in PySpark typically follows these steps:

  1. Create or load a DataFrame (using spark.read or from an existing RDD or collection).
  2. Register the DataFrame as a temporary view with a name.
  3. Call spark.sql("SQL QUERY") to execute your query string, referring to the view name in the FROM clause.
  4. Get back a DataFrame as the query result, which you can further .show(), transform, or save.

Let’s walk through an example continuing with our people data from earlier. First, ensure the DataFrame is registered as a temp view:

Now we can run various SQL queries against the “people” view. Spark SQL supports all the standard clauses, such as SELECT, WHERE, GROUP BY, ORDER BY, etc. Here are a few examples:

Basic SELECT: Retrieve all records and columns (similar to df.show() but via SQL):

PYTHON
1from pyspark.sql import SparkSession
2# Initialize a SparkSession
3spark = SparkSession.builder \
4 .appName("SparkSQLBasics") \
5 .getOrCreate()
6# Example: creating a DataFrame (this could also be loaded from a file or other source)
7data = [("Michael", 29), ("Andy", 30), ("Justin", 19)]
8columns = ["name", "age"]
9people_df = spark.createDataFrame(data, schema=columns)
10people_df.createOrReplaceTempView("people")
11# Select all columns from the people view
12result_df = spark.sql("SELECT * FROM people")
13result_df.show()
▶ Output will appear here.

Each row of the DataFrame is now accessible in SQL. The result_df is a new DataFrame (the result of the query), so you could use it programmatically as well. For instance, you could call result_df.where("age >= 21") or any DataFrame operation on it if you wanted.

Filtering with WHERE: You can filter rows using a WHERE clause just like in any SQL:

PYTHON
1from pyspark.sql import SparkSession
2# Initialize a SparkSession
3spark = SparkSession.builder \
4 .appName("SparkSQLBasics") \
5 .getOrCreate()
6# Example: creating a DataFrame (this could also be loaded from a file or other source)
7data = [("Michael", 29), ("Andy", 30), ("Justin", 19)]
8columns = ["name", "age"]
9people_df = spark.createDataFrame(data, schema=columns)
10people_df.createOrReplaceTempView("people")
11adults_df = spark.sql("SELECT name, age FROM people WHERE age >= 21")
12adults_df.show()
▶ Output will appear here.

Notice that we can choose to select specific columns (name, age) instead of * as needed, and the WHERE clause restricts the rows.

Aggregations and GROUP BY: Spark SQL lets you perform aggregations like counting, summing, averaging, etc., combined with grouping. For example, let’s count how many people fall into each age value:

PYTHON
1from pyspark.sql import SparkSession
2# Initialize a SparkSession
3spark = SparkSession.builder \
4 .appName("SparkSQLBasics") \
5 .getOrCreate()
6# Example: creating a DataFrame (this could also be loaded from a file or other source)
7data = [("Michael", 29), ("Andy", 30), ("Justin", 19)]
8columns = ["name", "age"]
9people_df = spark.createDataFrame(data, schema=columns)
10people_df.createOrReplaceTempView("people")
11age_counts_df = spark.sql("SELECT age, COUNT(*) AS count FROM people GROUP BY age")
12age_counts_df.show()
▶ Output will appear here.

This will group the records by age and count how many entries per age. Based on our small dataset:

Each age appears once (since all are distinct in our tiny set). If there were duplicates, their counts would reflect that. You could also use other aggregate functions like SUM, AVG (average), MIN, MAX similarly. (We will explore more SQL functions in the next section.)

Sorting results: To sort the output, use ORDER BY. For example, to list people by descending age:

PYTHON
1from pyspark.sql import SparkSession
2# Initialize a SparkSession
3spark = SparkSession.builder \
4 .appName("SparkSQLBasics") \
5 .getOrCreate()
6# Example: creating a DataFrame (this could also be loaded from a file or other source)
7data = [("Michael", 29), ("Andy", 30), ("Justin", 19)]
8columns = ["name", "age"]
9people_df = spark.createDataFrame(data, schema=columns)
10sorted_df = spark.sql("SELECT name, age FROM people ORDER BY age DESC")
11sorted_df.show()
▶ Output will appear here.

showing the rows sorted by age in descending order.

Limiting results: If you only want the first N results, use LIMIT. For example:

SQL
1spark.sql("SELECT * FROM people LIMIT 2").show()
▶ Output will appear here.

would show only 2 rows out of the 3 (an arbitrary two, since no explicit ORDER BY was given).

spark.sql() returns a DataFrame: It’s worth emphasizing that spark.sql() yields a DataFrame object representing the result set of the query. You can use this result DataFrame as you would any other – e.g., call .collect() to retrieve it to the driver, write it to storage, or even create a new temp view from it. This means you can chain SQL with further PySpark DataFrame operations if needed.

For example:

PYTHON
1young_people_df = spark.sql("SELECT * FROM people WHERE age < 30")
2# young_people_df is a DataFrame of the SQL result, we can continue using DataFrame API on it
3young_people_df.filter("name LIKE 'J%'").show()
▶ Output will appear here.

This would first use SQL to get under-30 folks, then use the DataFrame API to further filter those whose name starts with J.

Under the hood: When you run spark.sql(), Spark uses the Catalyst optimizer to plan the query, just as it would with DataFrame code. The same optimizations apply (for example, the filter in WHERE is pushed down to where the data resides if possible, aggregations use combiners, etc.). The Spark documentation highlights that running SQL queries programmatically via spark.sql returns the result as a DataFrame, unifying the concept of tables and DataFrames.

In summary, Spark SQL queries on DataFrames allow you to treat your distributed data as if it were in a SQL database. You get the expressiveness of SQL and the scalability of Spark. This can be extremely powerful for teams that are accustomed to SQL or for quickly writing complex queries that might be less straightforward in the DataFrame API. You can always achieve the same with DataFrame operations, but often SQL can be more concise for certain tasks (and vice versa). Spark gives you the freedom to use either approach, and because of the underlying architecture, you don’t sacrifice performance for using SQL.

SQL Functions (SUM, AVG, CASE WHEN, etc.)

Overview: Spark SQL comes with a rich library of built-in functions for data manipulation, similar to those found in traditional SQL databases. These include aggregate functions (for summarizing data), arithmetic and mathematical functions, string functions for text processing, date/time functions, conditional expressions, and more. Leveraging these functions can significantly simplify your queries and often yields better performance than writing custom logic. In this section, we’ll cover some of the most commonly used functions in Spark SQL and how to use them in your queries.

Spark SQL’s built-in functions span many categories. Here are some key types of functions and examples of each:

Using these functions in Spark SQL is straightforward – you call them in the SQL query string just like in standard SQL. Let’s illustrate some of them with examples, using our people view and a few other hypothetical examples:

Aggregate Functions Example: Suppose we have a view sales with schema (category, amount). We can aggregate the data:

# Example: Sum of amounts per category (assuming a 'sales' view exists)

# This is a hypothetical example for illustration

PYTHON
1from pyspark.sql import SparkSession
2# Initialize a SparkSession
3spark = SparkSession.builder \
4 .appName("SparkSQLBasics") \
5 .getOrCreate()
6data = [
7 ("North", "ProductA", 10, 20.0),
8 ("North", "ProductB", 5, 15.0),
9 ("South", "ProductA", 15, 10.0),
10 ("South", "ProductC", 8, 20.0),
11 ("East", "ProductD", 12, 5.0)
12]
13columns = ["region", "product", "quantity", "price"]
14# Create a DataFrame from the sample data
15df = spark.createDataFrame(data).toDF(*columns)
16df.createOrReplaceTempView("sales")
17spark.sql("""
18 SELECT category, SUM(amount) as total_amount, AVG(amount) as avg_amount, COUNT(*) as count
19 FROM sales
20 GROUP BY category
21""").show()
▶ Output will appear here.

This query would produce output with one row per category, showing the sum of amount, average amount, and count of records in each category. Spark’s implementation of these aggregates will ignore nulls by default (e.g., SUM of values that include NULL will treat NULL as 0 in the sum, effectively summing non-nulls. Also, note that if you use any aggregate (like SUM or AVG) and also select non-aggregated columns, you must use a GROUP BY on those non-aggregated columns (just like standard SQL).

As a concrete example with our data, let’s use an aggregate on the people view. Maybe we want to know the average age of people in our dataset:

SQL
1spark.sql("SELECT AVG(age) AS avg_age, SUM(age) AS total_age, COUNT(*) AS number_of_people FROM people").show()
▶ Output will appear here.

indicating the average age is 26 (since (29+30+19)/3 = 26), total age is 78, and count is 3. (Spark will likely return avg_age as a double with one decimal as shown above.)

CASE WHEN (Conditional Expressions): The CASE WHEN expression in Spark SQL allows you to perform if-then-else logic within a query. It’s extremely useful for categorizing or transforming data based on conditions. The syntax is:

```

hide

CASE

WHEN condition1 THEN value1

WHEN condition2 THEN value2

ELSE value_else

END

```

You can have multiple WHEN clauses and an optional ELSE. Spark SQL’s CASE works just like SQL standard.

For example, let’s classify people as 'Adult' or 'Minor' based on their age:

PYTHON
1spark.sql("""
2 SELECT
3 name,
4 age,
5 CASE
6 WHEN age >= 18 THEN 'Adult'
7 ELSE 'Minor'
8 END AS age_group
9 FROM people
10""").show()
▶ Output will appear here.

In this example, all three are labeled "Adult" because they’re all 18 or over (if one had been under 18, they’d show "Minor"). If any age were NULL, the condition age >= 18 would be evaluated as FALSE (or unknown), so the result would fall into the ELSE and label as "Minor". You could add additional WHEN clauses for more categories if needed (e.g., different age brackets). The resulting column age_group is a derived categorical column based on the logic.

Another use of CASE is in aggregation with conditional sums or counts (sometimes known as “conditional aggregation”). For instance, you could combine CASE with SUM to count how many people are adults vs minors:

PLAINTEXT
1SELECT
2 SUM(CASE WHEN age >= 18 THEN 1 ELSE 0 END) AS adults,
3 SUM(CASE WHEN age < 18 THEN 1 ELSE 0 END) AS minors
4FROM people;
▶ Output will appear here.

This query uses CASE inside SUM to effectively count rows meeting a condition (since it adds 1 for each row that matches, 0 otherwise). Spark SQL supports this pattern, which can be very powerful for creating pivot-table like summaries in one query.

String Functions Example: Let’s demonstrate some string operations using Spark SQL. Imagine we want to standardize the format of names to uppercase and also find the length of each name. We can do:

SQL
1spark.sql("SELECT name, UPPER(name) AS name_upper, LENGTH(name) AS name_length FROM people").show()
▶ Output will appear here.

We see that UPPER(name) converted each name to uppercase. LENGTH(name) returned the number of characters in each name. Spark also has LOWER(name) to do the inverse (lowercase everything). Another common string function is CONCAT, which concatenates multiple strings (or columns). For example:

SQL
1spark.sql("SELECT CONCAT(name, ' is ', age, ' years old') AS sentence FROM people").show()
▶ Output will appear here.

would produce sentences like “Michael is 29 years old”. Behind the scenes, Spark’s string functions are optimized in Scala/Java, so they execute efficiently across the dataset.

Date Functions Example: Working with dates and timestamps is another common need. Spark SQL provides many functions to extract or manipulate date/time values. Suppose we have a view events with a column event_date of type date. We could do:

PYTHON
1from pyspark.sql import SparkSession
2from pyspark.sql.functions import current_timestamp
3spark = SparkSession.builder.getOrCreate()
4# Sample data
5data = [
6 ("Login", "2025-01-10"),
7 ("Logout", "2025-01-15"),
8 ("Signup", "2025-02-01")
9]
10columns = ["event_name", "event_date"]
11df = spark.createDataFrame(data, columns)
12# Convert event_date to timestamp
13df = df.withColumn("event_date", df.event_date.cast("timestamp"))
14# Register view
15df.createOrReplaceTempView("events")
16# Run SQL
17spark.sql("""
18 SELECT
19 event_date,
20 YEAR(event_date) AS year,
21 MONTH(event_date) AS month,
22 DAY(event_date) AS day
23 FROM events
24""").show()
▶ Output will appear here.

Here YEAR(), MONTH(), and DAY() pulled out parts of the date. Spark also has date_format(date, 'pattern') which can format dates as strings, and functions like current_date(), current_timestamp(), datediff(endDate, startDate) (difference in days), add_months(date, n), etc. You can also convert strings to dates with to_date(string, format) if your dates are in text form.

Important Note on using functions: When using Spark SQL functions:

More Spark tutorials

All tutorials · Try the free PySpark compiler · Practice challenges