spark dataframe 遇到的问题
maven jar
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<scala.version>2.12</scala.version>
<spark.version>3.0.0</spark.version>
<es.version>7.11.1</es.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!--spark-core 依赖-->
<dependency>
<groupId>org.apache.spark</groupId>
<artifactId>spark-core_${scala.version}</artifactId>
<version>${spark.version}</version>
</dependency>
<!--spark-sql 依赖-->
<dependency>
<groupId>org.apache.spark</groupId>
<artifactId>spark-sql_${scala.version}</artifactId>
<version>${spark.version}</version>
</dependency>
<!--spark-es 依赖-->
<dependency>
<groupId>org.elasticsearch</groupId>
<artifactId>elasticsearch-spark-30_${scala.version}</artifactId>
<version>7.12.1</version>
</dependency>
<!--spark-excel 依赖-->
<dependency>
<groupId>com.crealytics</groupId>
<artifactId>spark-excel_${scala.version}</artifactId>
<version>3.0.3_0.18.7</version>
</dependency>
<!--雪花算法id 依赖-->
<dependency>
<groupId>cn.ipokerface</groupId>
<artifactId>snowflake-id-generator</artifactId>
<version>2.5.0</version>
</dependency>
</dependencies>
1、spark 读写取 excel文件
val schema = new StructType(Array(
StructField("id",DataTypes.StringType),
StructField("name",DataTypes.StringType),
StructField("addr",DataTypes.StringType)
}
def main(args: Array[String]): Unit = {
val spark = SparkSession
.builder()
.master("local[*]")
.appName("xxx")
// .config("es.index.auto.create",true)
.getOrCreate()
// ------------------读取excel---------------------------
val excelReadDF = spark.read
.option("header", "true") //是否带有表头
.option("inferSchema", "true") // 自行推断表头
.option("dataAddress", "0!A1") // "0!A1" 0 代表第一个sheet 页 !A1 从A1单元格读取
// .option("encoding","GBK") // 处理 csv 时中文乱码
.schema(schema) // 固定表头
// .format("csv") // 文件格式 csv
.format("com.crealytics.spark.excel") //excel 文件格式
.load("G:\\data\\读文件.xlsx") //本地文件地址
// ------------------写excel---------------------------
val savefilePath = "G:\\data\\写文件.xlsx";
excelReadDF.write
.format("com.crealytics.spark.excel")
.option("dataAddress", "0!A1")
.option("useHeader", "false")
.option("header", "true")
.mode(SaveMode.Overwrite)
.save(savefilePath)
}
2、读写es
def main(args: Array[String]): Unit = {
val spark: SparkSession = SparkSession.builder.appName("readEs").master("local[*]").getOrCreate()
val options: Map[String, String] = Map("es.nodes" -> "ip",
"es.port" -> "9200", "pushdown" -> "true",
//spark和es非同一网段时增加该配置
"es.nodes.wan.only" -> "true",
"es.mapping.date.rich" -> "false", //处理es 日期转换格式问题
"es.field.read.empty.as.null" -> "false", // scala.None$ is not a valid external type for schema of string
//并发更新时, 如果更新在同一条记录则会冲突,增加该配置
"es.update.retry.on.conflict" -> "3",
//决定spark会生成多少个partition对应执行的task
"es.input.max.docs.per.partition" -> "5000")
// ------------------读取es---------------------------
// 1、直接读取成df,这种方式不支持复杂查询,如果需要复杂查询请采用 方式2 读取成rdd
val inputDF: DataFrame = spark.read.format("org.elasticsearch.spark.sql")
.options(options).load("es-index-name") //es 索引名称
.where("pubtime>'2023-05-01 00:00:00'") // 条件
.select("nameZh", "title", "content", "pubtime") // 查询字段
.orderBy(desc("id"),asc("pubtime")) //字段排序 全局排序
inputDF.printSchema()
//2、 读取成rdd
val rdd = spark.sparkContext.esJsonRDD("es-index-name",
"""
|{
| "query": {
| "match": {"_id": "xxxxxxxxxx"}
| }
|}
|""".stripMargin)
rdd.foreach(println)
// ------------------写入es---------------------------
inputDF.write.format("org.elasticsearch.spark.sql")
.options(options).mode(SaveMode.Append)
.save("es-index-name")
3、 dataframe 中转化使用java 对象
public class SchoolJava implements Serializable {
private String id;
private String name;
private String addr;
//省略 get set
}
import spark.implicits._ //隐式转换
val userDS: Dataset[SchoolJava] = inputDF.as(Encoders.bean(classOf[org.example.dto.SchoolJava]))
4、 dataframe 中转化使用scala 对象
case class SchoolScala(id: String, name: String, addr: String)
import spark.implicits._
val userDS: Dataset[SchoolScala] = inputDF.as[SchoolScala]
4、es 日期转换异常和空值异常 scala.None$ is not a valid external type for schema of string
“es.mapping.date.rich” -> “false”,
“es.field.read.empty.as.null” -> “false”,
5、df 里新增列
1)直接调用withColumn() 函数,但是 添加的列 只能是 依赖于已经存在的列 或者 添加常值列(这一列的值都一样)
2) 增加列 非依赖于已存在的列 或者 常值列的数值不一致的
这种情况下 就需要 先将dataframe 转成rdd 在rdd中添加列然后再转成dataframe,其中可能会遇到问题 1-df转成rdd后schema 丢失变成RDD[ROW],在对RDD[ROW] 无法调用 toDF() 将rdd 转化成df了。可以调用 createDataFrame()函数,生成新的DF。具体如下
3)可以直接在sparksql 中直接根据拼接
… createTempView(“tt”) //先创建临时表 通过 concat_ws函数 将不同的列 以 :拼接,不带分隔符用concat()函数
val frame: DataFrame = spark.sql("select *, concat(column10,column11) as column15 from tt ")
val frame: DataFrame = spark.sql("select *, concat_ws(\":\", column10, column11,column12) as column15 from tt ")
package org.example
import cn.ipokerface.snowflake.SnowflakeIdGenerator
import org.apache.spark.rdd.RDD
import org.apache.spark.sql.catalyst.expressions.GenericRowWithSchema
import org.apache.spark.sql.functions._
import org.apache.spark.sql.types._
import org.apache.spark.sql.{DataFrame, Row, SparkSession}
import scala.collection.mutable
object DataDeal {
val generator = new SnowflakeIdGenerator(1, 1)
def main(args: Array[String]): Unit = {
System.setProperty("hadoop.home.dir", "D:\\spark-hadoop-win")
val spark: SparkSession = SparkSession.builder.appName("DataDeal").master("local[*]").getOrCreate()
val options: Map[String, String] = Map("es.nodes" -> "xxxx",
"es.port" -> "9200", "pushdown" -> "true",
//spark和es非同一网段时增加该配置
"es.nodes.wan.only" -> "true",
"es.mapping.date.rich" -> "false", //处理es 日期转换格式问题
"es.field.read.empty.as.null" -> "false", //scala.None$ is not a valid external type for schema of string
//并发更新时, 如果更新在同一条记录则会冲突,增加该配置
"es.update.retry.on.conflict" -> "3",
//决定spark会生成多少个partition对应执行的task
"es.input.max.docs.per.partition" -> "5000")
// 隐式转换
import spark.implicits._
// 注册udf函数
spark.udf.register("getNewId", getNewId _)
//直接从es 读取成df
val inputDF: DataFrame = spark.read.format("org.elasticsearch.spark.sql")
.options(options).load("es-index")
.where("pubtime>'2023-05-10 00:00:00'")
.select("id", "name_zh", "sub_content", "json_content")
inputDF.printSchema()
inputDF.show()
//使用get_json_object 进行json 中字段提取生成msg字段
val jsonobj: DataFrame = inputDF.select(get_json_object($"json_content", "$.msg").as("msg")
)
jsonobj.printSchema()
jsonobj.show()
// 设置字段类型
val fieldsConf = new mutable.HashMap[String, DataType]()
fieldsConf += (("new_id_str", StringType))
//将df 转换成rdd, 丢失类型转化成Row 类型,动态添加字段到rdd[Row] 中
val dataSF: RDD[Row] = jsonobj.rdd.map(
row => {
val buffer = Row.unapplySeq(row).get.toBuffer
buffer.append("" + getNewId())
var schemaNew: StructType = row.schema
fieldsConf.foreach(conf => {
schemaNew = schemaNew.add(conf._1, conf._2)
})
// 使用Row的子类GenericRowWithSchema创建新的Row
val newSchema = new GenericRowWithSchema(buffer.toArray, schemaNew).asInstanceOf[Row]
newSchema
})
dataSF.foreach(row => println(row))
dataSF.repartition(2)
//设置shema
var schema: StructType = StructType(List
(
StructField("msg", StringType, false),
StructField("new_id_str", StringType, false)
))
// 根据RDD[Row] 和shema 生成新的df
var newDF: DataFrame = spark.createDataFrame(dataSF, schema)
newDF.printSchema()
newDF.show()
}
// 雪花算法生成new_id
def getNewId(): String = {
generator.nextId() + ""
}
6、df里获取字段值是json 的处理
可以直接使用 get_json_object 函数,参考 5 中代码
7、df 里去重 可以采用distinct() 或者 dropDuplicates()
distinct() 所有字段值完全相同的行
dropDuplicates() 指定某个或多个字段相同 去重处理
8、 大小写不敏感导致字段重名
apache.spark.sql.AnalysisException: Found duplicate column(s) in the data schema: xxx
;
sparksession.sql("set spark.sql.caseSensitive=true") //设置大小写敏感