SparkSQL DataFrame Datasets

概述:

1)Spark SQL 是 Spark 处理结构化数据的一个模块.与基础的 Spark RDD API 不同, Spark SQL 提供了查询结构化数据及计算结果等信息的接口.
2)Spark SQL可以加载任何地方的数据,例如mysql,hive,hdfs,hbase等,而且支持很多种格式如json, parquet, avro, csv格式。我们可以开发出任意的外部数据源来连接到Spark SQL,然后我们就可以通过外部数据源API来进行操作。
3)我们通过外部数据源API读取各种格式的数据,会得到一个DataFrame,这是我们熟悉的方式啊,就可以使用DataFrame的API或者SQL的API进行操作
4)外部数据源的API可以自动做一些列的裁剪,什么叫列的裁剪,假如一个user表有id,name,age,gender4个列,在做select的时候你只需要id,name这两列,那么其他列会通过底层的优化去给我们裁剪掉
5)保存操作可以选择使用SaveMode,指定如何保存现有数据(如果存在)

准备工作:

1导入依赖:

   <dependency>
      <groupId>org.apache.spark</groupId>
      <artifactId>spark-sql_2.12</artifactId>
      <version>${spark.version}</version>
    </dependency>

2 Spark编程入口SparkSession 创建

import org.apache.spark.sql.SparkSession

val sparkSession= SparkSession
  .builder()
  .appName("Spark SQL basic example")
  .config("spark.some.config.option", "some-value")
  .getOrCreate()

// For implicit conversions like converting RDDs to DataFrames
import spark.implicits._

sparkSession.stop()

案例及源码分析:

DataFrame Datasets之间的关系:

源码中:
type DataFrame = Dataset[Row]

DataSets
一个 Dataset 是一个分布式的数据集合。 Dataset 是在 Spark 1.6 中被添加的新接口, 它提供了 RDD 的优点(强类型化, 能够使用强大的 lambda 函数)
与Spark SQL执行引擎的优点.一个 Dataset 可以从 JVM 对象来 构造 并且使用转换功能(map, flatMap, filter, 等等)

DataFrame --()
一个 DataFrame 是一个 Dataset 组成的指定列.它的概念与一个在关系型数据库或者在 R/Python 中的表是相等的, 但是有很多优化. 
DataFrames 可以从大量的 sources 中构造出来, 比如: 结构化的文本文件, Hive中的表, 外部数据库, 或者已经存在的 RDDs.

1 读取 txt 文件数据
  def text(sparkSession: SparkSession) = {
    import sparkSession.implicits._

	// 标准写法
    val dataFrame: DataFrame = sparkSession.read.format("text").load("input/user.txt")
	// 另外一种写法 (后面源码分析差异)
    // sparkSession.read.text("input.user.txt")

    val ds: Dataset[String] = sparkSession.read.textFile("input/user.txt","input/traffics.txt")

    ds.show(20)
   // dataFrame.show(20)

   val result = ds.map(x=>{
      val strings = x.split(" ")

     //(strings(0),strings(1))
     strings(0)
    })



    val result2 = dataFrame.rdd.map(x => {//dataFrame.rdd 要转化
      val tmp = x.getString(0)
      val strs = tmp.split(" ")

      //(strs(0), strs(1))
      strs(0)
    })

	// 保存 可以使用压缩
    //result.write.option("compression","Gzip").mode(SaveMode.Overwrite).format("text").save("out")
    result.write.mode(SaveMode.Overwrite).format("text").save("out2")
  }

上述两种写法的源码分析差异:

 /**
   * Loads text files and returns a `DataFrame` whose schema starts with a string column named
   * "value", and followed by partitioned columns if there are any. See the documentation on
   * the other overloaded `text()` method for more details.
   *
   * @since 2.0.0
   */
  def text(path: String): DataFrame = {
    // This method ensures that calls that explicit need single argument works, see SPARK-16009
    text(Seq(path): _*)
  }
我们调用text() 方法其实进行了 overloaded 
def text(paths: String*): DataFrame = format("text").load(paths : _*)
这个方法就很熟悉了,其实就是我们的标准写法,一目了然。 后文的json(),csv()等方法均类似

注意

1.保存的文件夹不能存在,否则报错(默认情况下,可以选择不同的模式):org.apache.spark.sql.AnalysisException: path file:/home/hadoop/data already exists;
2.保存成文本(text)格式,只能保存一列,否则报错:org.apache.spark.sql.AnalysisException: Text data source supports only a single column, and you have 2 columns;

解决1中的问题:

保存模式意义
SaveMode.ErrorIfExists (default)将DataFrame保存到数据源时,如果数据已经存在,则预计会抛出异常
SaveMode.Append将DataFrame保存到数据源时,如果数据/表已存在,则DataFrame的内容预计会附加到现有数据
SaveMode.Overwrite覆盖模式意味着将DataFrame保存到数据源时,如果数据/表已存在,则预期现有数据将DataFrame的内容覆盖
SaveMode.Ignore忽略模式意味着将DataFrame保存到数据源时,如果数据已经存在,则保存操作不会保存DataFrame的内容,也不会更改现有的数据。这与SQL中的create table if not exists 类似

解决2中的问题:
数据保存之前先想办法将数据由多列压缩成单列,concat_ws : 用指定的字符连接字符

  def mulitColumnOutput(df:DataFrame,delimiter:String=","): Unit ={
    val columns = df.columns.mkString(",")
    val result = df.selectExpr(s"concat_ws('$delimiter',$columns)as colums_name")

    result.write.mode(SaveMode.Overwrite).format("text").save("out/")

  }
2 读取json
  def json(sparkSession: SparkSession) = {
	import sparkSession.implicits._
     //标准写法
    val df = sparkSession.read.format("json").load("input/imput.log")
    //写法2 (原理同上)
    val dataFrame = sparkSession.read.json("input/imput.log")

    df.printSchema()

	// 所有字段展示
    df.show(2)
    
+-----------------+--------+-------------+--------+-------------------+-------+------------+-------+
|            appId|duration|           ip|platform|               time|traffic|        user|version|
+-----------------+--------+-------------+--------+-------------------+-------+------------+-------+
|www.ruozedata.com|    6666|182.89.87.243|     IOS|2019-09-19 10:59:13|   ~~~~|ruozedataLog|  1.0.0|
|www.ruozedata.com|    6666| 123.235.55.4| Android|2019-09-19 10:59:16|   1242|ruozedataLog|  1.0.0|
+-----------------+--------+-------------+--------+-------------------+-------+------------+-------+


    //根据条件截取我们所需要的列
    df.select("appId","platform","ip").filter('platform === "IOS")
	 // .filter($"ip" === "106.95.18.65") $ 符号也可以
	.show(3)
+-----------------+--------+--------------+
|            appId|platform|            ip|
+-----------------+--------+--------------+
|www.ruozedata.com|     IOS| 182.89.87.243|
|www.ruozedata.com|     IOS|123.233.211.65|
|www.ruozedata.com|     IOS|  106.95.18.65|
+-----------------+--------+--------------+

 	// 保存数据 也支持压缩等
    result.write.mode(SaveMode.Overwrite).format("json").save("out")
  }
3 读取CSV 文件
  def csv(sparkSession: SparkSession) = {
    import sparkSession.implicits._

	//不加修饰的读取
    val df = sparkSession.read.format("csv").load("input/people.csv").show(3)
+------------------+
|               _c0|
+------------------+
|      name;age;job|
|Jorge;30;Developer|
|  Bob;32;Developer|
+------------------+
// 添加表头,和分隔符
val df = sparkSession.read.option("header","true").option("sep",";").format("csv").load("input/people.csv").show(3)
+-----+---+---------+
| name|age|      job|
+-----+---+---------+
|Jorge| 30|Developer|
|  Bob| 32|Developer|
+-----+---+---------+

df.select("name","age").filter($"age" > 30)
      .write.format("csv").mode("overwrite")
      .save("out")
  }
4 jdbc 方式读取
 def jdbc(sparkSession: SparkSession) = {
    import sparkSession.implicits._

    val jdbcDF = sparkSession.read
      .format("jdbc")
      .option("url", "jdbc:mysql://192.168.76.109:3306/database")
     // .option("dbtable", "database.table")
      .option("user", "username")
      .option("password", "password")
      .option("query", "select id,nick from user")
      .load()

       jdbcDF.show(2)

    // 保存到一个新表中
    jdbcDF.filter('id === "2681")
      .write.format("jdbc")
      .option("url", "jdbc:mysql://192.168.76.109:3306")
      .option("dbtable", "database.table")
      .option("user", "username")
      .option("password", "password")
     // .mode(SaveMode.Overwrite)
      .save()
  }

生产中的操作:
通过将option 中的连接信息写入到配置文件中去
这里选用的scalikejdbc-config

    <dependency>
      <groupId>org.scalikejdbc</groupId>
      <artifactId>scalikejdbc-config_2.12</artifactId>
      <version>3.3.0</version>
    </dependency>

创建 src/main/resources/application.conf 文件,并写入配置

 def jdbcFromProperties(sparkSession: SparkSession) = {

    import sparkSession.implicits._

	// 从配置文件中导入连接信息
    val config = ConfigFactory.load()
    val url = config.getString("db.default.url")
    val user = config.getString("db.default.user")
    val password = config.getString("db.default.password")
    val srcTable = config.getString("db.default.srctable")
    val targetTable = config.getString("db.default.targettable")


    val jdbcDF = sparkSession.read
      .format("jdbc")
      .option("url", url)
      //.option("dbtable", "user")
      .option("user", user)
      .option("password",password)
      .option("query", "select id,nick from user")
      .load()

    jdbcDF.show(2)

    // 保存到一个新表中
    jdbcDF.filter('id === "2685")
      .write.format("jdbc")
      .option("url", url)
      .option("dbtable", targetTable)
      .option("user", user)
      .option("password", password)
      .mode(SaveMode.Overwrite)
      .save()

  }
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

冬瓜螺旋雪碧

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

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

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

打赏作者

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

抵扣说明:

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

余额充值