码到成功
sparksql的常见用法

# Spark SQL 常用语法实战:把常见查询写法一次讲清楚
如果你已经接触过 MySQL、PostgreSQL、SQL Server 这类数据库,Spark SQL 的上手门槛并不高。
它的核心价值在于:
- 保留了 SQL 的表达力
- 可以直接运行在 Spark 的分布式执行引擎上
- 很适合做清洗、聚合、宽表拼接、指标计算、明细分析
这篇文章围绕 Spark SQL 的常用写法展开,重点覆盖:
- 临时视图怎么用
- 常见查询怎么写
- 聚合、连接、窗口函数怎么写
- 数组、JSON、空值处理怎么写
- 写 Spark SQL 时有哪些实用建议
---
## 1. Spark SQL 是什么
Spark SQL 可以理解成 Spark 提供的一套结构化数据处理接口。
你可以用它来查询:
- Hive 表
- Parquet 文件
- ORC 文件
- JSON 文件
- CSV 文件
- DataFrame 注册出来的视图
对于开发者来说,最常见的工作方式有两种:
1. 直接写 SQL 查询
2. 用 DataFrame 读数据,再注册成视图,然后写 SQL
很多项目里,第二种方式更常见,因为读取数据和执行 SQL 可以拆开处理。
---
## 2. 准备一组示例数据
为了把常见语法讲清楚,下面准备三张逻辑表:
- `users`:用户表
- `products`:商品表
- `orders`:订单明细表
字段如下:
### 2.1 `users`
| 字段 | 含义 |
| --- | --- |
| `user_id` | 用户编号 |
| `user_name` | 用户名 |
| `city` | 城市 |
| `user_level` | 用户等级 |
### 2.2 `products`
| 字段 | 含义 |
| --- | --- |
| `product_id` | 商品编号 |
| `product_name` | 商品名 |
| `category` | 类目 |
| `price` | 单价 |
### 2.3 `orders`
| 字段 | 含义 |
| --- | --- |
| `order_id` | 订单编号 |
| `user_id` | 用户编号 |
| `product_id` | 商品编号 |
| `quantity` | 购买数量 |
| `amount` | 订单金额 |
| `status` | 订单状态 |
---
## 3. 把 DataFrame 注册成临时视图
Spark SQL 最常见的入口就是临时视图。
例如在 PySpark 里:
```python
users_df.createOrReplaceTempView("users")
products_df.createOrReplaceTempView("products")
orders_df.createOrReplaceTempView("orders")
```
注册完成后,就可以直接写 SQL:
```python
spark.sql("""
select *
from users
""").show()
```
如果你用的是 Spark Shell 或 Scala,写法也类似:
```scala
usersDf.createOrReplaceTempView("users")
productsDf.createOrReplaceTempView("products")
ordersDf.createOrReplaceTempView("orders")
```
这里有两个容易混淆的概念:
- `createOrReplaceTempView`:会话级临时视图
- `createOrReplaceGlobalTempView`:全局临时视图
日常开发中,大多数场景用临时视图就够了。
---
## 4. 最基础的查询写法
### 4.1 查询全部字段
```sql
select *
from orders;
```
这是最简单的写法,但在正式任务里通常不建议长期依赖 `select *`,原因是:
- 字段不够明确
- 上游表结构变动会带来不必要影响
- 读取字段过多时会增加开销
更常见的写法是显式列出字段:
```sql
select
order_id,
user_id,
product_id,
amount,
status
from orders;
```
### 4.2 条件过滤
```sql
select
order_id,
user_id,
amount,
status
from orders
where amount >= 100
and status = 'paid';
```
常见条件包括:
- 比较运算:`=`, `<>`, `>`, `<`, `>=`, `<=`
- 区间判断:`between ... and ...`
- 集合判断:`in (...)`
- 空值判断:`is null`, `is not null`
- 多条件组合:`and`, `or`, `not`
例如:
```sql
select *
from users
where city in ('Shanghai', 'Beijing')
and user_level is not null;
```
### 4.3 排序与取前几行
```sql
select
order_id,
user_id,
amount
from orders
order by amount desc
limit 10;
```
这个写法常用于:
- 查看金额最高的订单
- 观察样本数据
- 检查计算结果是否符合预期
---
## 5. 去重、别名与表达式
### 5.1 去重
```sql
select distinct city
from users;
```
如果需要按多个字段去重:
```sql
select distinct user_id, city
from users;
```
### 5.2 字段别名
```sql
select
user_id as uid,
user_name as uname,
user_level as level_name
from users;
```
### 5.3 直接写计算表达式
```sql
select
order_id,
quantity,
amount,
amount / quantity as unit_amount
from orders
where quantity > 0;
```
Spark SQL 支持在 `select` 中直接写大量表达式,这也是它非常适合做指标计算的原因之一。
---
## 6. `CASE WHEN`:做条件判断最常用
`CASE WHEN` 在 Spark SQL 里出现频率很高,经常用于:
- 分类打标
- 业务映射
- 规则判断
- 指标分层
示例:
```sql
select
user_id,
amount,
case
when amount >= 1000 then 'high'
when amount >= 300 then 'middle'
else 'low'
end as amount_tag
from orders;
```
再比如对订单状态做中文映射:
```sql
select
order_id,
status,
case
when status = 'paid' then '已支付'
when status = 'cancelled' then '已取消'
when status = 'refunded' then '已退款'
else '其他状态'
end as status_name
from orders;
```
---
## 7. 聚合查询:`GROUP BY`、`HAVING`
聚合是 Spark SQL 里最常见的一类操作。
### 7.1 按用户统计订单金额
```sql
select
user_id,
count(*) as order_cnt,
sum(amount) as total_amount,
avg(amount) as avg_amount,
max(amount) as max_amount,
min(amount) as min_amount
from orders
group by user_id;
```
### 7.2 按城市统计用户数
```sql
select
city,
count(*) as user_cnt
from users
group by city;
```
### 7.3 使用 `HAVING` 过滤聚合结果
```sql
select
user_id,
sum(amount) as total_amount
from orders
group by user_id
having sum(amount) >= 1000;
```
`WHERE` 和 `HAVING` 的区别非常关键:
- `WHERE`:过滤明细行
- `HAVING`:过滤聚合结果
---
## 8. 多表连接:`JOIN` 是高频语法
Spark SQL 的很多核心任务,本质上都是多表拼接。
### 8.1 内连接
```sql
select
o.order_id,
u.user_name,
p.product_name,
o.quantity,
o.amount
from orders o
join users u
on o.user_id = u.user_id
join products p
on o.product_id = p.product_id;
```
内连接只保留关联成功的数据。
### 8.2 左连接
```sql
select
u.user_id,
u.user_name,
o.order_id,
o.amount
from users u
left join orders o
on u.user_id = o.user_id;
```
左连接适合:
- 保留主表全部数据
- 查看哪些记录没有匹配上
- 做标签补充
### 8.3 连接时的常见注意点
连接查询里最容易出问题的是这几类情况:
- 连接键类型不一致
- 一对多关系导致结果放大
- 字段重名没有加表别名
- 维表重复导致明细被意外膨胀
所以在写连接 SQL 时,建议明确:
- 连接键是什么
- 主表是谁
- 连接后预期行数会不会变化
---
## 9. 子查询与 CTE
复杂 SQL 往往离不开子查询和 CTE。
### 9.1 子查询
```sql
select *
from (
select
user_id,
sum(amount) as total_amount
from orders
group by user_id
) t
where t.total_amount >= 1000;
```
### 9.2 CTE 写法
CTE 就是 `WITH` 子句,适合把复杂逻辑拆开写。
```sql
with user_amount as (
select
user_id,
sum(amount) as total_amount
from orders
group by user_id
),
user_profile as (
select
u.user_id,
u.user_name,
u.city,
a.total_amount
from users u
left join user_amount a
on u.user_id = a.user_id
)
select *
from user_profile
where total_amount >= 1000;
```
这种写法的优点很明显:
- 可读性更高
- 分层更清晰
- 更适合维护复杂业务逻辑
---
## 10. 窗口函数:做排名、组内统计特别方便
窗口函数是 Spark SQL 里非常实用的一块内容。
### 10.1 组内排名
```sql
select
user_id,
order_id,
amount,
row_number() over (
partition by user_id
order by amount desc
) as rn
from orders;
```
这个写法常用于:
- 每个用户金额最高的一笔订单
- 每个类目金额最高的商品
- 每个分组取前 N 条记录
### 10.2 每组取金额最高的一条
```sql
with ranked_orders as (
select
user_id,
order_id,
amount,
row_number() over (
partition by user_id
order by amount desc, order_id asc
) as rn
from orders
)
select
user_id,
order_id,
amount
from ranked_orders
where rn = 1;
```
### 10.3 累计求和
```sql
select
user_id,
order_id,
amount,
sum(amount) over (
partition by user_id
order by amount desc
rows between unbounded preceding and current row
) as running_amount
from orders;
```
除了 `row_number()`,常见窗口函数还包括:
- `rank()`
- `dense_rank()`
- `sum() over (...)`
- `avg() over (...)`
- `count() over (...)`
---
## 11. 空值处理:`null` 相关写法要熟
空值处理在数据开发里非常常见。
### 11.1 判断空值
```sql
select *
from users
where city is null;
```
```sql
select *
from users
where city is not null;
```
### 11.2 用默认值填充
```sql
select
user_id,
coalesce(city, 'unknown') as city_name
from users;
```
`coalesce` 的作用是返回第一个非空值。
例如:
```sql
select
user_id,
coalesce(city, 'unknown') as city_name,
coalesce(user_level, 'normal') as level_name
from users;
```
### 11.3 避免空值参与计算带来歧义
```sql
select
order_id,
coalesce(amount, 0) as safe_amount
from orders;
```
---
## 12. 字符串处理函数
字符串处理也是 Spark SQL 的常见需求。
### 12.1 拼接字符串
```sql
select
user_id,
concat(user_name, '_', city) as user_tag
from users;
```
### 12.2 截取与替换
```sql
select
product_name,
substr(product_name, 1, 4) as short_name,
regexp_replace(product_name, ' ', '') as clean_name
from products;
```
### 12.3 大小写转换与去空格
```sql
select
trim(user_name) as user_name_clean,
upper(city) as city_upper,
lower(city) as city_lower
from users;
```
常见字符串函数包括:
- `concat`
- `substr`
- `length`
- `trim`
- `upper`
- `lower`
- `regexp_replace`
- `split`
---
## 13. 数组与拆分:`explode` 很实用
实际业务里,经常遇到一行里带一个数组字段,或者某个字段里存着分隔字符串。
这时 `explode` 很好用。
### 13.1 拆分字符串再展开
假设有一个字段 `tags`,内容类似:
```text
phone,electronics,hot
```
可以这样写:
```sql
select
product_id,
explode(split(tags, ',')) as tag
from product_tags;
```
执行后,一行会被拆成多行。
例如原始数据:
```text
product_id = 101, tags = phone,electronics,hot
```
可能会变成:
```text
101 phone
101 electronics
101 hot
```
### 13.2 `explode_outer`
如果担心空数组或空字段导致数据直接丢掉,可以考虑:
```sql
select
product_id,
explode_outer(split(tags, ',')) as tag
from product_tags;
```
---
## 14. JSON 处理:Spark SQL 也很常见
很多原始数据都带 JSON 字段,Spark SQL 对这类场景支持不错。
### 14.1 提取 JSON 字段
假设表 `event_log` 中有个字段 `payload`:
```json
{"page":"home","channel":"app","score":98}
```
可以直接取值:
```sql
select
get_json_object(payload, '$.page') as page_name,
get_json_object(payload, '$.channel') as channel_name
from event_log;
```
### 14.2 把 JSON 解析成结构化字段
```sql
select
from_json(
payload,
'page string, channel string, score int'
) as payload_obj
from event_log;
```
再进一步取子字段:
```sql
with parsed_log as (
select
from_json(
payload,
'page string, channel string, score int'
) as payload_obj
from event_log
)
select
payload_obj.page,
payload_obj.channel,
payload_obj.score
from parsed_log;
```
---
## 15. `UNION` 与 `UNION ALL`
如果要合并多份结构一致的数据,通常会用到这两个语法。
### 15.1 `UNION ALL`
```sql
select user_id, amount
from orders_a
union all
select user_id, amount
from orders_b;
```
`UNION ALL` 会直接拼接结果,不去重。
### 15.2 `UNION`
```sql
select user_id, amount
from orders_a
union
select user_id, amount
from orders_b;
```
`UNION` 会去重,代价通常更高。
如果业务不要求去重,优先考虑 `UNION ALL`。
---
## 16. 去重保留一条:这是高频需求
很多明细表会出现重复数据,常见处理方式是配合窗口函数去重。
```sql
with dedup_orders as (
select
*,
row_number() over (
partition by order_id
order by amount desc
) as rn
from orders
)
select *
from dedup_orders
where rn = 1;
```
这个模式很常见,关键点在于:
- 用什么字段定义“重复”
- 用什么排序规则决定“保留哪一条”
---
## 17. 创建结果表与写入结果
Spark SQL 不只是查数据,也可以落结果。
### 17.1 建表查询
```sql
create table user_order_summary as
select
user_id,
count(*) as order_cnt,
sum(amount) as total_amount
from orders
group by user_id;
```
### 17.2 插入已有表
```sql
insert overwrite table user_order_summary
select
user_id,
count(*) as order_cnt,
sum(amount) as total_amount
from orders
group by user_id;
```
不同环境里,表管理方式可能不同:
- 有的基于 Hive Metastore
- 有的基于 Lakehouse 体系
- 有的直接查外部文件
SQL 写法会有一些差别,但整体思路基本一致。
---
## 18. 写 Spark SQL 时的几个实用建议
### 18.1 少用 `select *`
显式列字段通常更稳:
- 可读性更好
- 更方便排查问题
- 有助于减少无用列读取
### 18.2 大表连接时看清主键关系
如果维表有重复,连接结果可能被放大很多倍。
这是 Spark SQL 任务里非常常见的问题来源。
### 18.3 复杂逻辑尽量拆成 CTE
与其写一条极长 SQL,不如按语义拆成多段 `WITH`。
### 18.4 关注执行计划
可以用:
```sql
explain
select
u.city,
sum(o.amount) as total_amount
from users u
join orders o
on u.user_id = o.user_id
group by u.city;
```
执行计划能帮助你判断:
- 有没有多余扫描
- 有没有不必要的 shuffle
- 连接策略是否合理
### 18.5 小表可以考虑广播
在部分场景里,可以显式提示广播小表:
```sql
select /*+ broadcast(u) */
o.order_id,
o.amount,
u.user_name
from orders o
join users u
on o.user_id = u.user_id;
```
这类写法常用于:
- 明细大表关联小维表
- 减少 shuffle 成本
是否生效还要结合执行计划来看。
---
## 19. 一组很常用的 Spark SQL 片段
如果只保留最常用的几类写法,基本可以记住下面这些:
### 19.1 过滤
```sql
select *
from orders
where status = 'paid'
and amount >= 100;
```
### 19.2 聚合
```sql
select
user_id,
sum(amount) as total_amount
from orders
group by user_id;
```
### 19.3 连接
```sql
select
o.order_id,
u.user_name
from orders o
left join users u
on o.user_id = u.user_id;
```
### 19.4 排名
```sql
select
user_id,
order_id,
amount,
row_number() over (
partition by user_id
order by amount desc
) as rn
from orders;
```
### 19.5 空值处理
```sql
select
user_id,
coalesce(city, 'unknown') as city_name
from users;
```
---
## 20. 总结
Spark SQL 的优势不在“语法有多特别”,而在于它把熟悉的 SQL 能力放进了 Spark 的分布式执行环境里。
对于大多数数据开发任务,真正高频的内容就是这几类:
- 过滤
- 聚合
- 连接
- 窗口函数
- 空值处理
- 数组与 JSON 解析
如果你已经能把这几类写顺手,很多 Spark 数据处理任务就能比较自然地展开。
可以用一句话概括这篇文章的重点:
> Spark SQL 的核心,不是记住所有函数,而是熟练掌握常见数据处理模式。
---
## 21. 附:一段可直接运行的示例结构
下面是一段很常见的 PySpark + Spark SQL 组合写法:
```python
users_df.createOrReplaceTempView("users")
orders_df.createOrReplaceTempView("orders")
result_df = spark.sql("""
with user_amount as (
select
user_id,
count(*) as order_cnt,
sum(amount) as total_amount
from orders
where status = 'paid'
group by user_id
)
select
u.user_id,
u.user_name,
u.city,
a.order_cnt,
a.total_amount
from users u
left join user_amount a
on u.user_id = a.user_id
""")
result_df.show()
```
这段代码里已经包含了 Spark SQL 最核心的几种能力:
- 临时视图
- 条件过滤
- 聚合
- CTE
- 左连接
把这些内容掌握扎实,Spark SQL 的日常开发就已经具备不错的基础了。