向Spark的DataFrame增加一列数据

本文详细介绍如何在Spark的DataFrame中增加列,包括使用withColumn方法、内置函数lit、SQL语句和concat函数添加固定值,以及如何增加自增长列。同时,深入探讨monotonically_increasing_id函数在分区情况下的使用技巧。

前言

先说个题外话,如何给hive表增加一个列,并且该把该列的所有字段设为’China’?
如果仅仅是增加一列倒是很简单:

alter table test add columns(flag string)

可要把这个flag字段全部设置为China,看起来的确是有点难度,因为往Hive表中装载数据的唯一途径就是使用一种“大量”的数据装载操作(如何往Hive表加载数据请参考),这个时候,如果数据集中本来就没有flag对应的数据,难道非要手动把China添加上去?这种情况,可以通过静态分区就能够解决:

load data local inpath '/data/test.txt' overwrite into table test partition (flag = 'China')

有人说,这不扯淡吗?如果这个China字段,并不是我们经常需要访问的字段,何须作为分区字段呢?的确是这样的,这个时候还可以通过下面的方式来解决这个问题:

insert into table test1  select id, name,'China' as flag from test;

好了步入正题:如何向Spark的DataFrame增加一列数据

案例详解

准备数据集:

张三,23
李四,24
王五,25
赵六,26

程序入口SparkSession和加载数据代码这里不再描述:

val spark = SparkSession
      .builder()
      .appName(this.getClass.getSimpleName)
      .master(master = "local[*]")
      .getOrCreate()

    import spark.implicits._
    val df = spark.read.textFile("./data/clm")
      .map(_.split(","))
      .map(x => (x(0), x(1)))
      .toDF("name", "age")
      .cache()
  • withColumn
    这个API是数据DataSet的,官网是这么定义的:

通过添加列或替换具有相同名称的现有列来返回新的数据集
column的表达式只能引用此数据集提供的属性。 添加引用其他数据集的列是错误的

新的列只能通过现有列转换得到,这个就有点局限,不过也能解决一部分问题:
比如,我想再增加一列为所有age增加1作为新的一列:

df.withColumn("new_age", col = df("age") + 1).show()

结果:

+----+---+-------+
|name|age|new_age|
+----+---+-------+
|张三| 23|   24.0|
|李四| 24|   25.0|
|王五| 25|   26.0|
|赵六| 26|   27.0|
+----+---+-------+

那么如果我想像前言中做那样的操作怎么办?

  • 借助functions中的内置函数lit

lit函数的作用:Creates a [[Column]] of literal value. 创建[[Column]]的字面量值

df.withColumn("class",lit("一班")).show()

结果:

+----+---+-----+
|name|age|class|
+----+---+-----+
|张三| 23| 一班|
|李四| 24| 一班|
|王五| 25| 一班|
|赵六| 26| 一班|
+----+---+-----+
  • 使用sql增加默认列
df.createTempView(viewName = "view1")
import spark.sql
sql(sqlText = "select name,age,'一班' as class from view1").show()

结果:

+----+---+-----+
|name|age|class|
+----+---+-----+
|张三| 23| 一班|
|李四| 24| 一班|
|王五| 25| 一班|
|赵六| 26| 一班|
+----+---+-----+
  • 利用concat函数
sql(sqlText = "select name,age,concat('','一班') as class from view1").show()

结果:

+----+---+-----+
|name|age|class|
+----+---+-----+
|张三| 23| 一班|
|李四| 24| 一班|
|王五| 25| 一班|
|赵六| 26| 一班|
+----+---+-----+
  • 增加自增长列(类似于sql中的自增长主键)
    这里用到了functions.scala文件中的内置函数monotonically_increasing_id()

该函数官网的描述是:一个列表达式,用于生成单调递增的64位整数。但是请注意:这个自增列在分区内是连续的,但是分区间并不连续

先来个简单的使用案例:

import org.apache.spark.sql.functions._
df.withColumn("id", monotonically_increasing_id()).show()

结果:

+----+---+---+
|name|age| id|
+----+---+---+
|张三| 23|  0|
|李四| 24|  1|
|王五| 25|  2|
|赵六| 26|  3|
+----+---+---+

但是,monotonically_increasing_id() 方法生成单调递增仅仅是针对同一个分区,尽管不同分区之间生成的id都是不同的,可不同分区间id不连续,也会造成使用上面的困难,下面进行详细讲解

  • 手动分为2个分区,看结果
    df.repartition(2)
      .withColumn("id", monotonically_increasing_id())
      .show()

结果:

+----+---+----------+
|name|age|        id|
+----+---+----------+
|李四| 24|         0|
|赵六| 26|         1|
|张三| 23|8589934592|
|王五| 25|8589934593|
+----+---+----------+

显然,可以看出李四和赵六为同一分区,张三和王五为另一个分区,这两个分区间id虽然不同,但是并不连续

如何解决monotonically_increasing_id()分区不连续的问题

  • 使用rdd的zipWithIndex(),这里依然手动设置为两个分区
    val tmpRdd: RDD[(Row, Long)] = df.rdd.repartition(2).zipWithIndex()
    val record: RDD[Row] = tmpRdd.map(x => {
      Row(x._1.get(0), x._1.get(1), x._2)
    })
    val schema = new StructType().add("name", "string")
      .add("age", "string")
      .add("id", "long")
    spark.createDataFrame(record, schema).show()

结果:

+----+---+---+
|name|age| id|
+----+---+---+
|张三| 23|  0|
|王五| 25|  1|
|李四| 24|  2|
|赵六| 26|  3|
+----+---+---+
  • 使用row_number().over(Windo.orderBy(ColName)),生成按某列排序后,新增单调递增,连续的一列。操作完后分区数变为1。id列从1开始
    val w = Window.orderBy("age")
    df.repartition(2).withColumn("id", row_number().over(w)).show()

结果:

+----+---+---+
|name|age| id|
+----+---+---+
|张三| 23|  1|
|李四| 24|  2|
|王五| 25|  3|
|赵六| 26|  4|
+----+---+---+
  • 从上面大家也能看出,monotonically_increasing_id()分区不连续,那么如果我们在计算完后通过手动将分区设置为一个,那样也就解决了分区间不联系的问题,之后再通过repartition(n)进行重分区
    df.repartition(1)
      .withColumn("id", monotonically_increasing_id())
      .repartition(2)
      .show()

结果:

+----+---+---+
|name|age| id|
+----+---+---+
|张三| 23|  0|
|李四| 24|  1|
|王五| 25|  2|
|赵六| 26|  3|
+----+---+---+
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

SunnyRivers

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值