常用加载/保存
默认数据源是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