SparkSql-数据源

本文介绍了Spark SQL的数据源,包括默认的Parquet数据源、如何加载/保存数据、JSON文件和JSON Datasets的处理,以及Parquet文件的分区发现。此外,文章详细讲解了如何与Hive表交互,包括内嵌Hive的使用和外部Hive的配置。
摘要由CSDN通过智能技术生成
常用加载/保存

默认数据源是parquet ,除非用spark.sql.sources.default配置参数定义为其他。

val usersDF = spark.read.load("examples/src/main/resources/users.parquet")
usersDF.select("name", "favorite_color").write.save("namesAndFavColors.parquet")

不想使用parquet文件时,刻意手动指定其他数据源。数据源需要指定全包名(如:org.apache.spark.sql.parquet)。如果数据源是内建的,只需短别名即可(json, parquet, jdbc, orc, libsvm, csv, text)。

  • 加载JSON文件:
val peopleDF = spark.read.format("json").load("examples/src/main/resources/people.json")
peopleDF.select("name", "age").write.format("parquet").save("namesAndAges.parquet")
  • 加载CSV文件:
val peopleDFCsv = spark.read.format("csv")
  .option("sep", ";")
  .option("inferSchema", "true")
  .option("header", "true")
  .load("examples/src/main/resources/people.csv")
  • 或者直接在文件上查sql
val sqlDF = spark.sql("SELECT * FROM parquet.`examples/src/main/resources/users.parquet`")
保存模式

通过使用SaveMode枚举类来定义行为。

Scala/Java选项意义
SaveMode.ErrorIfExists(默认)“error” 或 “errorifexists”如果文件存在,将抛出异常
SaveMode.Append“append”如果文件存在则追加
SaveMode.Overwrite“overwrite”如果文件存在,将被覆写
SaveMode.Ignore“ignore”如果文件存在,则忽略不作任何操作
JSON 文件或JSON Datasets

Spark-SQL使用SparkSession.read.json()对JSON文件或 Dataset[String]进行推断,自动获取结构类型信息,然后返回一个Dataset[Row]

一个典型JSON文件,每一行必须是一个独立、自包含的JSON对象
如果读取JSON对象是多行的文件,需添加属性multiLine 为true

import spark.implicits._

// path 应是一个文本文件或一个包含文本文件的目录
val path = "examples/src/main/resources/people.json"
val peopleDF = spark.read.json(path)

peopleDF.printSchema()
// root
//  |-- age: long (nullable = true)
//  |-- name: string (nullable = true)

// 另外, DataFrame 可由 Dataset[String]创建,其中每一个String都存储一个JSON对象
val otherPeopleDataset = spark.createDataset("""{"name":"Yin","address":{"city":"Columbus","state":"Ohio"}}""" :: Nil)
val otherPeople = spark.read.json(otherPeopleDataset)
otherPeople.show()
// +---------------+----+
// |        address|name|
// +---------------+----+
// |[Columbus,Ohio]| Yin|
// +---------------+----+
Parquet 文件
import spark.implicits._

val peopleDF = spark.read.json("examples/src/main/resources/people.json")

// 保存为parquet文件
peopleDF.write.parquet("people.parquet")

// 读取parquet文件
val parquetFileDF = spark.read.parquet("people.parquet")

parquetFileDF.createOrReplaceTempView("parquetFile")
val namesDF = spark.sql("SELECT name FROM parquetFile WHERE age BETWEEN 13 AND 19")
namesDF.map(attributes => "Name: " + attributes(0)).show()
// +------------+
// |       value|
// +------------+
// |Name: Justin|
// +------------+
分区发现

Spark-SQL自动妥善加载以分区形式存储的文件,其中内建的文件数据源有(Text/CSV/JSON/ORC/Parquet)。当传递path/to/table的路径参数给SparkSession.read.parquet 或 SparkSession.read.load时,自动提取分区路径上的信息,比如以下分区存储的目录:

path
└── to
    └── table
        ├── gender=male
        │   ├── ...
        │   │
        │   ├── country=US
        │   │   └── data.parquet
        │   ├── country=CN
        │   │   └── data.parquet
        │   └── ...
        └── gender=female
            ├── ...
            │
            ├── country=US
            │   └── data.parquet
            ├── country=CN
            │   └── data.parquet
            └── 

被加载为:

root
|-- name: string (nullable = true)
|-- age: long (nullable = true)
|-- gender: string (nullable = true)
|-- country: string (nullable = true)

加载后,分区列数据类型自动推断,比如数值、date、timestamp、字符串。如果取消推断,可配置spark.sql.sources.partitionColumnTypeInference.enabled为false。

import spark.implicits._

// 创建一个DataFrame保存进一个分区目录
val squaresDF = spark.sparkContext.makeRDD(1 to 5).map(i => (i, i * i)).toDF("value", "square")
squaresDF.write.parquet("data/test_table/key=1")

// 创建另一个DataFrame保存进另一个分区目录
val cubesDF = spark.sparkContext.makeRDD(6 to 10).map(i => (i, i * i * i)).toDF("value", "cube")
cubesDF.write.parquet("data/test_table/key=2")

// 读取分区表。mergeSchema合并结构,因为两个DataFrame结构不同,需要合并
val mergedDF = spark.read.option("mergeSchema", "true").parquet("data/test_table")
mergedDF.printSchema()
// 最终结构如下,三个业务数据列,一个分区列
// root
//  |-- value: int (nullable = true)
//  |-- square: int (nullable = true)
//  |-- cube: int (nullable = true)
//  |-- key: int (nullable = true)
Hive表
使用内嵌Hive

Spark二进制发布包已经内嵌了Hive,直接用。当没有配置 hive-site.xml时,Spark SQL会在当前的工作目录中创建出自己的Hive 元数据仓库,叫作 metastore_db,并创建spark.sql.warehouse.dir(默认工作目录下的spark-warehouse)指定的目录作为业务数据仓库。如果使用HDFS作为业务数据仓库,那么将core-site.xml和hdfs-site.xml 加入到conf目录下,否则只会在master节点上创建仓库目录,查询时会出现文件找不到的问题。所以最好使用HDFS。

import java.io.File

import org.apache.spark.sql.{Row, SaveMode, SparkSession}

case class Record(key: Int, value: String)

// 默认仓库路径
val warehouseLocation = new File("spark-warehouse").getAbsolutePath

val spark = SparkSession
  .builder()
  .appName("Spark Hive Example")
  .config("spark.sql.warehouse.dir", warehouseLocation)
  .enableHiveSupport()
  .getOrCreate()

import spark.implicits._
import spark.sql

sql("CREATE TABLE IF NOT EXISTS src (key INT, value STRING) USING hive")
sql("LOAD DATA LOCAL INPATH 'examples/src/main/resources/kv1.txt' INTO TABLE src")

// 使用HiveQL查询
sql("SELECT * FROM src").show()
// +---+-------+
// |key|  value|
// +---+-------+
// |238|val_238|
// | 86| val_86|
// |311|val_311|
// ...

// 统计
sql("SELECT COUNT(*) FROM src").show()
// +--------+
// |count(1)|
// +--------+
// |    500 |
// +--------+

// 查询结果是DataFrames,并支持所有通用操作
val sqlDF = sql("SELECT key, value FROM src WHERE key < 10 ORDER BY key")

// 每一行是一个Row
val stringsDS = sqlDF.map {
  case Row(key: Int, value: String) => s"Key: $key, Value: $value"
}
stringsDS.show()
// +--------------------+
// |               value|
// +--------------------+
// |Key: 0, Value: val_0|
// |Key: 0, Value: val_0|
// |Key: 0, Value: val_0|
// ...

val recordsDF = spark.createDataFrame((1 to 100).map(i => Record(i, s"val_$i")))
recordsDF.createOrReplaceTempView("records")

// 可以连接Hive表做查询
sql("SELECT * FROM records r JOIN src s ON r.key = s.key").show()
// +---+------+---+------+
// |key| value|key| value|
// +---+------+---+------+
// |  2| val_2|  2| val_2|
// |  4| val_4|  4| val_4|
// |  5| val_5|  5| val_5|
// ...

// 使用hive风格创建一个Praquet表
sql("CREATE TABLE hive_records(key int, value string) STORED AS PARQUET")
// 保存DataFrame到Hive表
val df = spark.table("src")
df.write.mode(SaveMode.Overwrite).saveAsTable("hive_records")

sql("SELECT * FROM hive_records").show()
// +---+-------+
// |key|  value|
// +---+-------+
// |238|val_238|
// | 86| val_86|
// |311|val_311|
// ...

// 准备一个 Parquet 数据目录
val dataDir = "/tmp/parquet_data"
spark.range(10).write.parquet(dataDir)
// 创建 Hive external Parquet table
sql(s"CREATE EXTERNAL TABLE hive_ints(key int) STORED AS PARQUET LOCATION '$dataDir'")

sql("SELECT * FROM hive_ints").show()
// +---+
// |key|
// +---+
// |  0|
// |  1|
// |  2|
// ...

spark.sqlContext.setConf("hive.exec.dynamic.partition", "true")
spark.sqlContext.setConf("hive.exec.dynamic.partition.mode", "nonstrict")
// 使用DataFrame API创建一个 Hive 分区表
df.write.partitionBy("key").format("hive").saveAsTable("hive_part_tbl")

sql("SELECT * FROM hive_part_tbl").show()
// +-------+---+
// |  value|key|
// +-------+---+
// |val_238|238|
// | val_86| 86|
// |val_311|311|
// ...

spark.stop()
使用外部Hive

将Hive中的hive-site.xml拷贝或者软连接到conf目录下。

使用spark shell,需带上带上访问Hive元数据库的JDBC驱动jar包
bin/spark-shell --jars mysql-connector-java-5.1.47.jar

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值