Spark DSL 操作详解
Spark 的 DataFrame/Dataset API 提供了一套强大的领域特定语言(DSL),允许开发者以声明式方式处理数据。这些操作不仅语法简洁,还能被 Catalyst 优化器高效执行。以下是核心 DSL 操作详解:
🔍 1. 列选择:select()
作用:选择或转换指定列
特点:支持列表达式、别名、函数计算
# Python
from pyspark.sql import functions as F
df.select(
"name", # 直接选择列
F.col("age"), # 使用col函数
(F.col("salary") * 1.1).alias("new_salary"), # 计算+别名
F.current_date().alias("today") # 内置函数
)
// Scala
import org.apache.spark.sql.functions._
df.select(
col("name"),
$"age", // $是col的语法糖
(col("salary") * 1.1).as("new_salary"),
current_date().alias("today")
)
🔎 2. 数据过滤:filter()
/ where()
作用:按条件筛选行(两者等价)
特点:支持复杂逻辑表达式
# 过滤年龄>30且以"A"开头的名字
df.filter(
(F.col("age") > 30) &
(F.col("name").startswith("A"))
)
// 等价写法
df.where($"age" > 30 && $"name".startsWith("A"))
📊 3. 分组聚合:groupBy()
+ agg()
作用:按列分组并进行聚合计算
特点:支持多种聚合函数
df.groupBy("department") \
.agg(
F.count("*").alias("emp_count"),
F.avg("salary").alias("avg_salary"),
F.max("age").alias("max_age")
)
df.groupBy("department")
.agg(
count("*").as("emp_count"),
avg("salary").as("avg_salary"),
max("age").as("max_age")
)
🔗 4. 数据连接:join()
作用:合并两个 DataFrame
连接类型:
inner
(默认)left
,right
,full
(outer)cross
,left_semi
,left_anti
# 内连接
employees.join(departments,
employees.dept_id == departments.id,
"inner")
# 左外连接
employees.join(departments,
"dept_id", # 同名字段简写
"left")
// 使用Seq指定多个连接键
employees.join(departments,
Seq("dept_id", "location_id"),
"left_outer")
📈 5. 排序:orderBy()
/ sort()
作用:按指定列排序(两者等价)
特点:支持多列、升降序
df.orderBy(
F.col("salary").desc(), # 工资降序
"age", # 年龄升序(默认)
F.col("name").asc() # 姓名升序
)
df.orderBy(
col("salary").desc,
$"age", // 升序
asc("name") // 显式升序
)
➕ 6. 添加/替换列:withColumn()
作用:添加新列或替换现有列
最佳实践:链式调用处理多列转换
df.withColumn("bonus", F.col("salary") * 0.1) \
.withColumn("full_name", F.concat("first_name", F.lit(" "), "last_name")) \
.withColumn("salary", F.col("salary") * 1.05) # 替换现有列
df.withColumn("bonus", $"salary" * 0.1)
.withColumn("full_name", concat($"first_name", lit(" "), $"last_name"))
.withColumn("salary", $"salary" * 1.05)
❌ 7. 删除列:drop()
作用:移除指定列
df.drop("temp_column", "obsolete_column")
df.drop("temp_column", "obsolete_column")
🔄 其他关键操作
重命名列:withColumnRenamed()
df.withColumnRenamed("old_name", "new_name")
去重:distinct()
/ dropDuplicates()
df.select("department").distinct() # 单列去重
df.dropDuplicates(["name", "age"]) # 多列组合去重
空值处理:na
df.na.fill(0) # 所有数字列填0
df.na.fill({"salary": 5000, "age": 30}) # 指定列填充
df.na.drop() # 删除含空值的行
类型转换:cast()
df.withColumn("age_str", F.col("age").cast("string"))
💡 DSL 最佳实践
-
链式操作:利用 DSL 的链式特性保持代码简洁
(df.filter(F.col("age") > 18) .groupBy("city") .agg(F.avg("income").alias("avg_income")) .orderBy("avg_income", ascending=False)
-
优先内置函数:使用
pyspark.sql.functions
或org.apache.spark.sql.functions
中的函数而非 UDF,享受 Catalyst 优化 -
适时缓存:对重复使用的中间结果使用
.cache()
filtered_df = df.filter(F.col("year") == 2023).cache()
-
避免混用 API:在单个处理流程中保持使用 DSL 或 SQL 的一致性
⚡ DSL 与 SQL 对比
特性 | DSL | Spark SQL |
---|---|---|
语法风格 | 编程式(方法链) | 声明式(类SQL) |
类型检查 | 编译时(Scala)、运行时(Py) | 运行时 |
IDE支持 | 自动补全、类型提示 | 字符串无提示 |
复杂表达式 | 较直观 | 需写完整字符串 |
代码复用 | 可封装为函数 | 需拼接SQL字符串 |
执行计划优化 | 相同(均由Catalyst优化) | 相同 |
大多数场景下,DSL 是首选,尤其在需要复杂编程逻辑时。SQL 更适合纯数据分析人员或简单查询。