前言
先说个题外话,如何给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|
+----+---+---+