预览
Spark SQL是用来处理结构化数据的Spark模块。有几种与Spark SQL进行交互的方式,包括SQL和Dataset API。
本指南中的所有例子都可以在spark-shell,pyspark shell或者spark R shell中执行。
SQL
Spark SQL的一个用途是执行SQL查询。Spark SQL还可以从现有的Hive中读取数据,本文下面有讲如何配置此功能。运行SQL时,结果会以Dataset/DataFrame返回。
Dataset和DataFrame
Dataset是分布式数据集合。Dataset是Spark1.6.0中添加的一个新接口,既提供了RDD的优点(强类型,支持lambda表达式),又提供了Spark SQL优化执行引擎的优点。Dataset API仅可用于Scala和Java,Python不支持Dataset API。
DataFrame可以从各种来源构建,如结构化数据文件,Hive中的表,外部数据库或者现有的RDD。DataFrame API适用于Scala,Java,Python和R。在Scala和Java中,DataFrame is represented by a Dataset of Rows。在Scala API中,DataFrame is simply a type alias of Datasets[Row]。在Java API中,DataFrame被表示成Dataset
Getting Started
Starting Point:SparkSession
Java API
编程入口是SparkSession类,在spark-sql-xxx.jar中,全类名是org.apache.spark.sql.SparkSession。用SparkSession.builder()方法来创建一个Builder实例(SparkSession内部类),进而调用其多个方法来创建SparkSession实例:
public static void main(String[] args) { SparkSession spark = SparkSession.builder()
.master("local") .appName("Java Spark SQL basic example") .enableHiveSupport() .getOrCreate(); }
Scala API
def main(args: Array[String]): Unit = { }
SparkSession类是从Spark2.0.0之后才有的,内置了对Hive的支持,包括用HiveSQL查询,使用Hive UDFs,以及从Hive表中读取数据。只需调用Builder实例的enableHiveSupport()方法启用Hive支持即可。
创建DataFrame
利用SparkSession实例,可以通过现有的RDD,或者从Hive表,或者从Spark其他的数据源(下面会讲)来创建DataFrame。
下面例子中,通过JSON文件创建了一个DataFrame:
public static void main(String[] args) { SparkSession spark = SparkSession.builder()
.master("local") .appName("Java Spark SQL basic example") .enableHiveSupport() .getOrCreate(); Datasetdf = spark.read().json("d:/people.json"); // Displays the content of the DataFrame to stdout df.show(); }
如上,用Dataset
通过调用SparkSession实例的read()方法获取DataFrameReader实例,通过该实例的各种方法来读取数据源生成DataFrame实例,如json(path)读取json文件,csv(path)读取csv文件,text(path)读取text文件,jdbc()方法读取关系型数据库的表,orc()方法读取ORC文件(启用hive支持后才能调用),parquet()方法读取Parquet文件。
上例中调用了Dataset
下面是使用Dataset处理结构化数据的例子:
public static void main(String[] args) { SparkSession spark = SparkSession.builder()
.master("local") .appName("Java Spark SQL basic example") .enableHiveSupport() .getOrCreate(); Datasetdf = spark.read().json("d:/people.json"); // Displays the content of the DataFrame to stdout df.show(); df.printSchema(); // root // |-- age: long (nullable = true) // |-- name: string (nullable = true) // Select only the "name" column df.select(col("name")).show();
// Select everybody, but increment the age by 1 df.select(col("name"), col("age").plus(1)).show();
// Select people older than 21 df.filter(col("age").gt(21)).show(); // Count people by age df.groupBy("age").count().show(); }
注意上例需要引入org.apache.spark.sql.functions.col,在spark-sql-xxx.jar中。
完整的代码示例参见spark-2.2.0-bin-hadoop2.7.tgz解压缩后的examples目录中的JavaSparkSQLExample.java。
上例中使用了Dataset
printSchema():在控制台打印出表头,亦即列名。
select(Column col1, Column col2, ...):传入一个或多个Column对象,返回一个新的Dataset
filter(Column condition):返回一个新的Dataset
groupBy(Column col1, Column col2, ...):传入要分组的列,返回一个RelationalGroupedDataset对象。
除了简单的列引用和表达式之外,Dataset还有丰富的函数库,包括字符串操作、时间函数及常见的数学运算等。具体可以查看org.apache.spark.sql.functions中的方法。
运行SQL查询
SparkSession实例的sql()方法可以运行SQL查询,结果以Dataset
java语言示例:
public static void main(String[] args) { SparkSession spark = SparkSession.builder()
.master("local") .appName("Java Spark SQL basic example") .enableHiveSupport() .getOrCreate(); Datasetdf = spark.read().json("d:/people.json"); // Displays the content of the DataFrame to stdout df.show(); // Creates a local temporary view using the given name.
// The lifetime of this temporary view is tied to the [[SparkSession]] that was used to create this Dataset. df.createOrReplaceTempView("people"); spark.sql("select * from people where age > 20").show(); // Creates a global temporary view using the given name. The lifetime of this temporary view is tied to this Spark application. df.createOrReplaceGlobalTempView("people"); // Global temporary view is cross-session spark.newSession().sql("select * from global_temp.people where age > 20").show(); }
利用SparkSession的createDataset()及createDataFrame()的多个重载方法可把RDD、List 转为Dataset
聚合
Spark SQL为操作DataFrame内置了很多函数,诸如functions.count(),functions.countDistinct(),functions.avg(),functions.max(),functions.min(),functions.sum()等常用聚合函数,具体可以查看org.apache.spark.sql.functions,在spark-sql-xxx.jar中。
数据源
Spark SQL可以通过DataFrame接口在各种数据源上进行操作。DataFrame可以使用关系转换进行操作???,也可以用来创建临时视图。将DataFrame注册为临时视图允许你对其数据运行SQL查询。本节介绍使用Spark Data Sources加载和保存数据的常用方法。
加载和保存的常用方法
用DataFrameReader的load(String path)方法加载数据,用DataFrameWriter的save(String path)方法保存数据。
注意load()方法默认是加载parquet文件的,在加载其他格式的文件时,需要在load前先调用format(String source)方法指定源数据格式。同理,save()方法也是默认写成parquet文件,如果要写成其他格式的文件,需要在save()前调用format(String source)指定文件格式。对于常用的数据格式,如json、csv、txt等,Spark提供了优化的方法来替代load()、save()方法,如DataFrameReader和DataFrameWriter的json()、parquet()、jdbc()、orc()、csv()、text()。实际上,观察源码可发现,DataFrameReader的json()就是format("json").load(),DataFrameWriter的json()就是format("json").save()。DataFrameReader的jdbc()就是format("jdbc").load(),DataFrameWriter的jdbc()就是format("jdbc").save(),等等,其他方法也是这样,都等于format().load()/save()。此外,数据源和数据最终保存的地方可以不一样,如数据源是json文件,但是数据最终保存到文本文件中,等等。
Java代码
public static void main(String[] args) { SparkSession spark = SparkSession.builder()
.master("local") .appName("Java Spark SQL basic example") .enableHiveSupport() .getOrCreate(); DatasetpeopleDF = spark.read().json("d:/people.json"); peopleDF.select("name").write().text("d:/people.text"); }
直接在文件上运行SQL
除了用DataFrameReader的各种方法将文件内容读取到DataFrame中查询外,你还可以直接对文件进行SQL查询。
Java代码
public static void main(String[] args) { SparkSession spark = SparkSession.builder()
.master("local") .appName("Java Spark SQL basic example") .enableHiveSupport() .getOrCreate(); DatasetsqlDF = spark.sql("SELECT * FROM json.`d:/people.json`"); sqlDF.show(); }
select * from json.`d:/people.json` 有2个注意点:
1、需要在文件前面用关键字指定文件格式,如json、csv等
2、文件名不是用单引号包裹,而是用tab键上面的那个键对应的标点符号包裹
保存模式
DataFrameWriter的save(String path)方法,默认保存模式是SaveMode.ErrorIfExists,这种模式下,当path已存在时,会报错。其他模式有SaveMode.Append、SaveMode.Overwrite、SaveMode.Ignore。可以用DataFrameWriter的mode(SaveMode saveMode)方法指定。
保存到持久化表
也可以使用DataFrameWriter的saveAsTable(String tableName)方法将DataFrame作为持久表保存。此时,hive的部署不是必需的。可以使用SparkSession的table(String tableName)方法读取该表数据到DataFrame中。
假如部署了hive,且在项目的resources目录(本地开发时)或SPARK_HOME的conf目录(实际生产时)中引入了hive-site.xml文件,则spark会把元数据放在hive的metastore中,把数据放在hdfs中。
假如没有部署hive,则spark会把元数据放在本地文件系统metastore_db目录中,把数据放在本地文件系统spark-warehouse目录中(由spark.sql.warehouse.dir指定,默认值是spark应用启动目录中的spark-warehouse目录,可以通过spark.conf()修改)。
假如是文件源,则在调用saveAsTable()前可以调用option("path", "/some/path")指定表数据路径(注意,不是元数据路径)。
Starting from Spark 2.1, persistent datasource tables have per-partition metadata stored in the Hive metastore. This brings several benefits:
①Since the metastore can return only necessary partitions for a query, discovering all the partitions on the first query to the table is no longer needed.
②Hive DDLs such as ALTER TABLE PARTITION ... SET LOCATION are now available for tables created with the Datasource API.
Note that partition information is not gathered by default when creating external datasource tables (those with a path option). To sync the partition information in the metastore, you can invoke MSCK REPAIR TABLE.
分桶、排序、分区
假如是文件源,则在输出的时候可以分桶、排序、分区。非文件源不支持分桶、排序、分区操作,否则会报
Exception in thread "dag-scheduler-event-loop" java.lang.StackOverflowError
at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1427)
at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1178)
java代码示例:
public static void main(String[] args) { System.setProperty("hadoop.home.dir", "D:/Users/KOUSHENGRUI976/winutils-master/winutils-master/hadoop-2.6.4"); SparkSession spark = SparkSession.builder() .appName("heihei") .master("local[*]") .enableHiveSupport() .getOrCreate(); DataFrameReader reader = spark.read(); Datasetdf = reader.json("d:/data"); spark.sql("drop table if exists people"); DataFrameWriter
writer = df.write(); writer.mode(SaveMode.Overwrite) .format("json") .bucketBy(5, "name") .sortBy("age")
.partitionBy("province")
.saveAsTable("people"); }
需要注意的是:bucketBy()及sortBy()不能和save()、json()、parquet()、text()、csv()、orc()不能与共用,否则会报org.apache.spark.sql.AnalysisException: 'save' does not support bucketing right now。partitionBy()无所谓。
hive tables
saprk.sql()支持任何hive语句,如
spark.sql("create table if not exists pokes (foo int, bar string)");
spark.sql("load data local inpath 'd:/data/kv1.txt' overwrite into table pokes");
java代码示例:
public static void main(String[] args) { SparkSession spark = SparkSession.builder() .appName("heihei").master("local[*]") .enableHiveSupport() .getOrCreate(); spark.sql("use default"); spark.sql("create table if not exists tests (id string, name string, password string) stored as textfile"); Datasetdf = spark.read().json("d:/2.json"); df.createOrReplaceTempView("tt"); spark.sql("insert into table tests select * from tt"); // 从hive表中读取数据 spark.sql("select * from tests").write().mode(SaveMode.Overwrite).json("d:/3.json"); }
与数据库交互
Spark SQL也可以通过JDBC与数据库交互。使用DataFrameReader的load()方法或者jdbc()方法。使用load()方法时,需要使用 option()方法或者options()方法指定连接数据库的用户名、密码等属性。而使用jdbc()方法时,只需在properties参数中指定就好了。常用的属性有:
user:数据库用户名
password:user用户对应的密码
url:JDBC URL,如 jdbc:postgresql://192.168.56.11/postgres 其中postgres是库名
driver:驱动类,如 org.postgresql.Driver
dbtable:表名。可以是数据库中现成的表名,如test,也可以是子查询名,如(select * from a inner join b on a.agentNo = b.agentNo) r
fetchsize:The JDBC fetch size, which determines how many rows to fetch per round trip. This can help performance on JDBC drivers which default to low fetch size (eg. Oracle with 10 rows). 仅在读的时候设置。
batchsize:The JDBC batch size, which determines how many rows to insert per round trip. This can help performance on JDBC drivers. 仅在写的时候设置,默认是1000。
isolationLevel:当前连接的事务隔离级别,仅在写的时候设置,默认是READ_UNCOMMITTED。
truncate:当使用SaveMode.Overwrite保存模式时,是否使用truncate表或者删除重建表。默认是false,值为true时表示会使用truncate表,这样比删除重建更快些。仅在写的时候设置。
partitionColumn、lowerBound、upperBound、numPartitions:这4个属性要么都指定,要么都不指定。仅在读的时候设置。分区时设置。
java代码示例:
public static void main(String[] args) { SparkSession spark = SparkSession.builder() .appName("heihei") .master("local[*]") .enableHiveSupport() .getOrCreate(); DataFrameReader reader = spark.read(); Properties properties = new Properties(); properties.put("user", "postgres"); properties.put("password", "123456"); properties.put("driver", "org.postgresql.Driver"); properties.put("fetchsize", "10000"); // 从test1表按照一定条件读取数据 String jdbcUrl = "jdbc:postgresql://192.168.56.11/postgres"; Datasetdf = reader.jdbc(jdbcUrl, "(select * from test where age <= 60 order by age desc)t", properties); DataFrameWriter writer = df.write(); properties.put("truncate", "true"); properties.put("batchsize", "10000"); // 写数据到test2表 writer.mode(SaveMode.Overwrite).jdbc(jdbcUrl, "public.test2", properties); }
DataFrame中数据类型转换
模拟场景,读取1.json文件,内容是两行json字符串
用json()方法读取