Spark SQL 是 Spark 用于结构化数据(structured data)处理的 Spark 模块
MapReduce
-> hive-sql
为了解决mr
程序编程困难。Hive SQL 转换成 MapReduce提交到集群上执行,执行效率慢
spark-core
-> spark-sql
为了解决rdd
编程困难。Spark SQL 转换成 RDD提交到集群上执行,执行效率非常快
Spark SQL 它提供了2个编程抽象, 类似 Spark Core 中的 RDD
三种方式创建DF :
// 读取 json 文件
scala> val df = spark.read.json("/opt/module/spark-local/examples/src/main/resources/employees.json")
df: org.apache.spark.sql.DataFrame = [name: string, salary: bigint]
// 展示结果
scala> df.show
+-------+------+
| name|salary|
+-------+------+
|Michael| 3000|
| Andy| 4500|
| Justin| 3500|
| Berta| 4000|
+-------+------+
SQL 语法风格是指我们查询数据的时候使用 SQL 语句来查询.
这种风格的查询必须要有临时视图或者全局视图来辅助
scala> val df = spark.read.json("/opt/module/spark-local/examples/src/main/resources/people.json")
df: org.apache.spark.sql.DataFrame = [age: bigint, name: string]
scala> df.createOrReplaceTempView("people")
scala> spark.sql("select * from people").show
+----+-------+
| age| name|
+----+-------+
|null|Michael|
| 30| Andy|
| 19| Justin|
+----+-------+
注意:
1.临时视图只能在当前 Session 有效, 在新的 Session 中无效.
2.想在新的Session中生效,可以创建全局视图. 访问全局视图需要全路径:如global_temp.xxx
scala> val df = spark.read.json("/opt/module/spark-local/examples/src/main/resources/people.json")
df: org.apache.spark.sql.DataFrame = [age: bigint, name: string]
scala> df.createGlobalTempView("people")
scala> spark.sql("select * from global_temp.people")
res31: org.apache.spark.sql.DataFrame = [age: bigint, name: string]
scala> res31.show
+----+-------+
| age| name|
+----+-------+
|null|Michael|
| 30| Andy|
| 19| Justin|
+----+-------+
scala> spark.newSession.sql("select * from global_temp.people")
res33: org.apache.spark.sql.DataFrame = [age: bigint, name: string]
scala> res33.show
+----+-------+
| age| name|
+----+-------+
|null|Michael|
| 30| Andy|
| 19| Justin|
+----+-------+
DataFrame提供一个特定领域语言(domain-specific language, DSL)去管理结构化的数据. 可以在 Scala, Java, Python 和 R 中使用 DSL
使用 DSL 语法风格不必去创建临时视图了
scala> val df = spark.read.json("/opt/module/spark-local/examples/src/main/resources/people.json")
df: org.apache.spark.sql.DataFrame = [age: bigint, name: string]
scala> df.printSchema
root
|-- age: long (nullable = true)
|-- name: string (nullable = true)
1.只查询name列数据
scala> df.select($"name").show
+-------+
| name|
+-------+
|Michael|
| Andy|
| Justin|
+-------+
scala> df.select("name").show
+-------+
| name|
+-------+
|Michael|
| Andy|
| Justin|
+-------+
2.查询name和age
scala> df.select("name", "age").show
+-------+----+
| name| age|
+-------+----+
|Michael|null|
| Andy| 30|
| Justin| 19|
+-------+----+
3.查询name和age + 1
scala> df.select($"name", $"age" + 1).show
+-------+---------+
| name|(age + 1)|
+-------+---------+
|Michael| null|
| Andy| 31|
| Justin| 20|
+-------+---------+
注意:
4.设计到运算的时候, 每列都必须使用$
5.查询age大于20的数据
scala> df.filter($"age" > 21).show
+---+----+
|age|name|
+---+----+
| 30|Andy|
+---+----+
6.按照age分组,查看数据条数
scala> df.groupBy("age").count.show
+----+-----+
| age|count|
+----+-----+
| 19| 1|
|null| 1|
| 30| 1|
+----+-----+
1. 手动转换
import org.apache.spark.sql.SparkSession
/**
* @Author jaffe
* @Date 2020/05/12 16:25
*/
object RDD2DF {
def main(args: Array[String]): Unit = {
// 创建 SparkSession
val spark = SparkSession.builder()
.master("local[2]")
.appName("RDD2DD")
.getOrCreate()
import spark.implicits._ //表示 SparkSession 的对象的隐式转换,用于实现rdd.toDF
//得到df, 使用df编程
//val df = spark.read.json("E:\\bigdata\\text\\input\\spark\\sql\\person.json")
//df.createOrReplaceTempView("p")
//spark.sql("select * from p").show
// 把rdd转成df
val rdd = spark.sparkContext.parallelize(List(("a", 1), ("b", 2), ("c", 3)))
val result = rdd.toDF("name", "age")
result.show()
spark.close()
}
}
2.通过样例类转换
import org.apache.spark.sql.SparkSession
/**
* @Author jaffe
* @Date 2020/05/12 16:42
*/
object RDD2DF_2 {
def main(args: Array[String]): Unit = {
val spark = SparkSession.builder()
.master("local[2]")
.appName("RDD2DD")
.getOrCreate()
import spark.implicits._
val list = User(10, "a") :: User(20, "b") :: User(30, "c") :: User(40, "d") :: Nil
val rdd = spark.sparkContext.parallelize(list)
val f = rdd.toDF
f.show()
spark.close()
}
}
case class User(age: Int, name: String)
直接调用DataFrame的rdd方法就完成了从转换
import org.apache.spark.sql.SparkSession
/**
* @Author jaffe
* @Date 2020/05/12 18:53
*/
object DF2RDD {
def main(args: Array[String]): Unit = {
//1创建SparkSession
val spark = SparkSession.builder()
.master("local[2]")
.appName("DF2RDD")
.getOrCreate()
val df = spark.read.json("E:\\bigdata\\text\\input\\spark\\sql\\person.json")
df.printSchema()
// 从df转成rdd之后, rdd内存储的永远是Row
// Row当成一个集合, 表示一行数据, 弱类型(类型与jdbc中的ResultSet)
val rdd = df.rdd
val rdd2 = rdd.map(row => {
// 方式1
// val age = if(row.isNullAt(0)) -1 else row.getLong(0)
// val name = row.getString(1)
//方式2
val age = row.getAs[Long]("age")
val name = row.getAs[String]("name")
//方式3
// val age = row.get(0)
// val name = row.get(1)
(age, name)
})
rdd2.collect.foreach(println)
spark.close()
}
}
DataSet是具有强类型的数据集合,更加安全,需要提供对应的类型信息。
import org.apache.spark.sql.SparkSession
/**
* @Author jaffe
* @Date 2020/05/14 09:04
*/
object CreateDateSet {
def main(args: Array[String]): Unit = {
val spark = SparkSession.builder()
.master("local[2]")
.appName("CreateDateSet")
.getOrCreate()
import spark.implicits._
/*
1.使用基本类型的序列得到 DataSet
val list = List(20,30,40,50,60)
val ds = list.toDS
ds.createOrReplaceTempView("a")
ds.filter(_>30).show()
ds.filter("value > 30").show() //dsl
spark.sql("select * from a where value > 30").show()*/
// 2. 使用样例类的序列得到DataSet
val list1 = User(10, "a") :: User(20, "b") :: User(30, "c") :: User(40, "d") :: Nil
val ds1 = list1.toDS()
ds1.createOrReplaceTempView("b")
spark.sql("select * from b where age > 20").show()
ds1.filter(_.age>20).show()
}
}
case class User(age: Int, name: String)
直接调用 toDS
import org.apache.spark.sql.SparkSession
/**
* @Author jaffe
* @Date 2020/05/14 09:28
*/
object RDD2DS {
def main(args: Array[String]): Unit = {
val spark = SparkSession.builder()
.master("local[2]")
.appName("RDD2DS")
.getOrCreate()
import spark.implicits._
val list1 = User(10, "a") :: User(20, "b") :: User(30, "c") :: User(40, "d") :: Nil
val rdd = spark.sparkContext.parallelize(list1)
val ds = rdd.toDS()
ds.show()
}
}
case class User(age: Int, name: String)
调用rdd方法即可
import org.apache.spark.sql.SparkSession
/**
* @Author jaffe
* @Date 2020/05/14 09:32
*/
object DS2RDD {
def main(args: Array[String]): Unit = {
val spark = SparkSession.builder()
.master("local[2]")
.appName("RDD2DS")
.getOrCreate()
import spark.implicits._
val list1 = User(10, "a") :: User(20, "b") :: User(30, "c") :: User(40, "d") :: Nil
val rdd = spark.sparkContext.parallelize(list1)
val ds = rdd.toDS()
ds.show()
//转成RDD
val rdd1 = ds.rdd
rdd1.collect.foreach(println)
spark.close()
}
}
case class Use(age: Int, name: String)
df -> ds
ds -> df
import org.apache.spark.sql.SparkSession
/**
* @Author jaffe
* @Date 2020/05/14 10:01
*/
object DF2DS {
def main(args: Array[String]): Unit = {
val spark = SparkSession.builder()
.master("local[2]")
.appName("DF2DS")
.getOrCreate()
import spark.implicits._
// df -> ds (简单->复杂)
val df = spark.read.json("E:\\bigdata\\text\\input\\spark\\sql\\person.json")
val ds = df.as[Person]
ds.show()
// ds -> df
val df1 = ds.toDF()
df1.show()
spark.close()
}
}
case class Person(name: String, age: Long)
版本发展:
RDD (Spark1.0) —> Dataframe(Spark1.3) —> Dataset(Spark1.6)
1.RDD一般和spark mlib同时使用
2.RDD不支持sparksql操作
1.与RDD和Dataset不同,DataFrame每一行的类型固定为Row,每一列的值没法直接访问,只有通过解析才能获取各个字段的值,
2.DataFrame与DataSet一般不与 spark mlib 同时使用
3.DataFrame与DataSet均支持 SparkSQL 的操作,比如select,groupby之类,还能注册临时表/视窗,进行 sql 语句操作
4.DataFrame与DataSet支持一些特别方便的保存方式,比如保存成csv,可以带上表头,这样每一列的字段名一目了然
1.Dataset和DataFrame拥有完全相同的成员函数,区别只是每一行的数据类型不同。 DataFrame其实就是DataSet的一个特例
2.DataFrame也可以叫Dataset[Row],每一行的类型是Row,不解析,每一行究竟有哪些字段,各个字段又是什么类型都无从得知,只能用上面提到的getAS方法或者共性中的第七条提到的模式匹配拿出特定字段。而Dataset中,每一行是什么类型是不一定的,在自定义了case class之后可以很自由的获得每一行的信息
scala> val df = spark.read.json("examples/src/main/resources/people.json")
df: org.apache.spark.sql.DataFrame = [age: bigint, name: string]
scala> df.show
+----+-------+
| age| name|
+----+-------+
|null|Michael|
| 30| Andy|
| 19| Justin|
+----+-------+
// 注册一个 udf 函数: toUpper是函数名, 第二个参数是函数的具体实现
scala> spark.udf.register("toUpper", (s: String) => s.toUpperCase)
res1: org.apache.spark.sql.expressions.UserDefinedFunction = UserDefinedFunction(<function1>,StringType,Some(List(StringType)))
scala> df.createOrReplaceTempView("people")
scala> spark.sql("select toUpper(name), age from people").show
+-----------------+----+
|UDF:toUpper(name)| age|
+-----------------+----+
| MICHAEL|null|
| ANDY| 30|
| JUSTIN| 19|
+-----------------+----+
import org.apache.spark.sql.{Row, SparkSession}
import org.apache.spark.sql.expressions.{MutableAggregationBuffer, UserDefinedAggregateFunction}
import org.apache.spark.sql.types.{DataType, DoubleType, StructField, StructType}
/**
* @Author jaffe
* @Date 2020/05/14 11:20
*/
object UDAF_SUM {
def main(args: Array[String]): Unit = {
val spark = SparkSession.builder()
.master("local[2]")
.appName("UDAF_SUM")
.getOrCreate()
import spark.implicits._
spark.udf.register("my_sum", new MySum)
val df = spark.read.json("E:\\bigdata\\text\\input\\spark\\sql\\person.json")
df.createOrReplaceTempView("p")
spark.sql("select my_sum(age) from p").show()
spark.close()
}
}
//UDAF
class MySum extends UserDefinedAggregateFunction { //用户定义聚合函数
//输入的数据类型 StructType 可以封装多个 Double
override def inputSchema: StructType = StructType(StructField("ele", DoubleType) :: Nil)
//缓冲区的数据类型 Double
override def bufferSchema: StructType = StructType(StructField("sum", DoubleType) :: Nil)
//最终聚合的返回值结果类型 Double
override def dataType: DataType = DoubleType
//确定性,相同的输入是否返回相同的输出 相同的输入有相同的输出
override def deterministic: Boolean = true
//初始化:对缓冲区初始化
override def initialize(buffer: MutableAggregationBuffer): Unit = {
//缓冲区可以看成一个集合,当前只缓冲一个值 buffer(0)
buffer(0) = 0D
}
//分区内聚合 参数1:缓冲区 参数2:传过来的每行数据 行的列数由传给聚合函数的参数来定
override def update(buffer: MutableAggregationBuffer, input: Row): Unit = {
//每个row中传一个值过来
// 有个bug TODO
buffer(0) = buffer.getDouble(0) + input.getDouble(0)
}
//分区间聚合 把buffer2中的数据,聚合到buffer1中
override def merge(buffer1: MutableAggregationBuffer, buffer2: Row): Unit = {
//分区间的聚合一定是每个分区内聚合好的
buffer1(0) = buffer1.getDouble(0) + buffer2.getDouble(0)
}
override def evaluate(buffer: Row): Any = buffer.getDouble(0)
}
import java.text.DecimalFormat
import org.apache.spark.sql.{Row, SparkSession}
import org.apache.spark.sql.expressions.{MutableAggregationBuffer, UserDefinedAggregateFunction}
import org.apache.spark.sql.types.{DataType, DoubleType, StringType, StructField, StructType}
/**
* @Author jaffe
* @Date 2020/05/14 23:35
*/
object UDAF_AVG {
def main(args: Array[String]): Unit = {
val spark = SparkSession.builder()
.appName("UDAF_AVG")
.master("local[2]")
.getOrCreate()
import spark.implicits._
spark.udf.register("my_avg", new MyAVG)
val df = spark.read.json("E:\\bigdata\\text\\input\\spark\\sql\\person.json")
df.createOrReplaceTempView("p")
spark.sql("select my_avg(age) from p").show()
spark.close()
}
}
class MyAVG extends UserDefinedAggregateFunction {
//输入的数据类型 StructType 可以封装多个 Double
override def inputSchema: StructType = StructType(StructField("ele", DoubleType) :: Nil)
//缓冲区的数据类型 Double
override def bufferSchema: StructType = StructType(StructField("sum", DoubleType) :: StructField("num", DoubleType) :: Nil)
//最终聚合的返回值结果类型 Double
override def dataType: DataType = StringType
//确定性,相同的输入是否返回相同的输出 相同的输入有相同的输出
override def deterministic: Boolean = true
//初始化:对缓冲区初始化
override def initialize(buffer: MutableAggregationBuffer): Unit = {
buffer(0) = 0D
buffer(1) = 0D
}
//分区内聚合 参数1:缓冲区 参数2:传过来的每行数据 行的列数由传给聚合函数的参数来定
override def update(buffer: MutableAggregationBuffer, input: Row): Unit = {
if (!input.isNullAt(0)) {
buffer(0) = buffer.getDouble(0) + input.getDouble(0)
buffer(1) = buffer.getDouble(1) + 1
}
}
//分区间聚合 把buffer2中的数据,聚合到buffer1中
override def merge(buffer1: MutableAggregationBuffer, buffer2: Row): Unit = {
//分区间的聚合一定是每个分区内聚合好的
buffer1(0) = buffer1.getDouble(0) + buffer2.getDouble(0)
buffer1(1) = buffer1.getDouble(1) + buffer2.getDouble(1)
}
override def evaluate(buffer: Row): Any =
new DecimalFormat(".00").format(buffer.getDouble(0) / buffer.getDouble(1))
}
默认数据源是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")
说明:
1.spark.read.load 是加载数据的通用方法.
2.df.write.save 是保存数据的通用方法.
手动给数据源指定一些额外的选项. 数据源应该用全名称来指定, 但是对一些内置的数据源也可以使用短名称:json, parquet, jdbc, orc, libsvm, csv, text
val peopleDF = spark.read.format("json").load("examples/src/main/resources/people.json")
peopleDF.select("name", "age").write.format("parquet").save("namesAndAges.parquet")
我们前面都是使用read API 先把文件加载到 DataFrame, 然后再查询. 其实, 我们也可以直接在文件上进行查询
scala> spark.sql(“select * from json.examples/src/main/resources/people.json
”)
说明:
json表示文件的格式. 后面的文件具体路径需要用反引号括起来.
保存操作可以使用 SaveMode, 用来指明如何处理数据. 使用mode()方法来设置.
这些 SaveMode 都是没有加锁的, 也不是原子操作. 如果你执行的是 Overwrite 操作, 在写入新的数据之前会先删除旧的数据.
Spark SQL 可以通过 JDBC 从关系型数据库中读取数据的方式创建 DataFrame,通过对DataFrame一系列的计算后,还可以将数据再写回关系型数据库中
注意: 如果想在spark-shell操作 jdbc, 需要把相关的 jdbc 驱动 copy 到 jars 目录下
导入依赖:
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.27</version>
</dependency>
import org.apache.spark.sql.SparkSession
/**
* @Author jaffe
* @Date 2020/05/14 15:29
*/
object JDBCRead {
val url = "jdbc:mysql://hadoop103:3306/gmall"
val user = "root"
val pwd = "123456"
def main(args: Array[String]): Unit = {
val spark = SparkSession
.builder()
.master("local[2]")
.appName("JDBCRead")
.getOrCreate()
import spark.implicits._
//通用的读法
val df = spark.read.format("jdbc")
.option("url", url)
.option("user", user)
.option("password", pwd)
.option("dbtable", "user")
.load()
df.show()
spark.close()
}
}
import org.apache.spark.sql.SparkSession
/**
* @Author jaffe
* @Date 2020/05/15 00:48
*/
object JDBCWrite {
val url = "jdbc:mysql://hadoop103:3306/gmall"
val user = "root"
val pwd = "123456"
def main(args: Array[String]): Unit = {
val spark = SparkSession
.builder()
.master("local[2]")
.appName("JDBCRead")
.getOrCreate()
import spark.implicits._
val df = spark.read.json("E:\\bigdata\\text\\input\\spark\\sql\\person.json")
df.write.format("jdbc")
.options(Map(
"url" -> url,
"user" -> user,
"password" -> pwd,
"dbtable" -> "user1128"
))
.mode("append")
.save()
spark.close()
}
}
若要把 Spark SQL 连接到一个部署好的 Hive 上,你必须把 hive-site.xml 复制到 Spark的配置文件目录中($SPARK_HOME/conf)。即使没有部署好 Hive,Spark SQL 也可以运行。
注意:如果你没有部署好Hive,Spark SQL 会在当前的工作目录中创建出自己的 Hive 元数据仓库,叫作 metastore_db。此外,如果你尝试使用 HiveQL 中的 CREATE TABLE (并非 CREATE EXTERNAL TABLE)语句来创建表,这些表会被放在你默认的文件系统中的 /user/hive/warehouse 目录中(如果你的 classpath 中有配好的 hdfs-site.xml,默认的文件系统就是 HDFS,否则就是本地文件系统)
如果使用 Spark 内嵌的 Hive, 则什么都不用做, 直接使用即可.
Hive 的元数据存储在 derby 中, 仓库地址:$SPARK_HOME/spark-warehouse
scala> spark.sql("show tables").show
+--------+---------+-----------+
|database|tableName|isTemporary|
+--------+---------+-----------+
+--------+---------+-----------+
scala> spark.sql("create table aa(id int)")
19/02/09 18:36:10 WARN HiveMetaStore: Location: file:/opt/module/spark-local/spark-warehouse/aa specified for non-external table:aa
res2: org.apache.spark.sql.DataFrame = []
scala> spark.sql("show tables").show
+--------+---------+-----------+
|database|tableName|isTemporary|
+--------+---------+-----------+
| default| aa| false|
+--------+---------+-----------+
向表中加载本地数据数据
scala> spark.sql("load data local inpath './ids.txt' into table aa")
res8: org.apache.spark.sql.DataFrame = []
scala> spark.sql("select * from aa").show
+---+
| id|
+---+
|100|
|101|
|102|
|103|
|104|
|105|
|106|
+---+
1.准备工作
2.启动 spark-shell
scala> spark.sql("show tables").show
+--------+---------+-----------+
|database|tableName|isTemporary|
+--------+---------+-----------+
| default| emp| false|
+--------+---------+-----------+
scala> spark.sql("select * from emp").show
19/02/09 19:40:28 WARN LazyStruct: Extra bytes detected at the end of the row! Ignoring similar problems.
+-----+-------+---------+----+----------+------+------+------+
|empno| ename| job| mgr| hiredate| sal| comm|deptno|
+-----+-------+---------+----+----------+------+------+------+
| 7369| SMITH| CLERK|7902|1980-12-17| 800.0| null| 20|
| 7499| ALLEN| SALESMAN|7698| 1981-2-20|1600.0| 300.0| 30|
| 7521| WARD| SALESMAN|7698| 1981-2-22|1250.0| 500.0| 30|
| 7566| JONES| MANAGER|7839| 1981-4-2|2975.0| null| 20|
| 7654| MARTIN| SALESMAN|7698| 1981-9-28|1250.0|1400.0| 30|
| 7698| BLAKE| MANAGER|7839| 1981-5-1|2850.0| null| 30|
| 7782| CLARK| MANAGER|7839| 1981-6-9|2450.0| null| 10|
| 7788| SCOTT| ANALYST|7566| 1987-4-19|3000.0| null| 20|
| 7839| KING|PRESIDENT|null|1981-11-17|5000.0| null| 10|
| 7844| TURNER| SALESMAN|7698| 1981-9-8|1500.0| 0.0| 30|
| 7876| ADAMS| CLERK|7788| 1987-5-23|1100.0| null| 20|
| 7900| JAMES| CLERK|7698| 1981-12-3| 950.0| null| 30|
| 7902| FORD| ANALYST|7566| 1981-12-3|3000.0| null| 20|
| 7934| MILLER| CLERK|7782| 1982-1-23|1300.0| null| 10|
| 7944|zhiling| CLERK|7782| 1982-1-23|1300.0| null| 50|
+-----+-------+---------+----+----------+------+------+------+
3.使用spark-sql cli
在spark-shell执行 hive 方面的查询比较麻烦.spark.sql("").show
Spark 专门给我们提供了书写 HiveQL 的工具: spark-sql cli
4.使用hiveserver2 + beeline
spark-sql 得到的结果不够友好, 所以可以使用hiveserver2 + beeline
启动 thrift服务器
sbin/start-thriftserver.sh
–master yarn \
启动beeline客户端
bin/beeline
然后输入
!connect jdbc:hive2://hadoop103:10000
然后按照提示输入用户名和密码
步骤1: 拷贝 hive-site.xml 到 resources 目录下
步骤2: 添加依赖
<dependency>
<groupId>org.apache.spark</groupId>
<artifactId>spark-hive_2.11</artifactId>
<version>2.1.1</version>
</dependency>
步骤3: 代码
1.查数据
import org.apache.spark.sql.SparkSession
/**
* @Author jaffe
* @Date 2020/05/15 11:04
*/
object HiveDemo {
System.setProperty("HADOOP_USER_NAME","jaffe")
def main(args: Array[String]): Unit = {
val spark = SparkSession
.builder()
.master("local[2]")
.appName("HiveDemo")
.enableHiveSupport()
.getOrCreate()
import spark.implicits._
spark.sql("use gmall").show
spark.sql("show tables").show
//spark.sql("show tables").show
// spark.sql("select count(*) from ods_activity_info").show
spark.close()
}
}
2.写数据
import org.apache.spark.sql.SparkSession
object HiveWrite {
def main(args: Array[String]): Unit = {
//设置权限,一般写的时候涉及到权限
System.setProperty("HADOOP_USER_NAME", "jaffe")
val spark: SparkSession = SparkSession
.builder()
.master("local[*]")
.appName("HiveWrite")
.enableHiveSupport()
// 配置仓库的地址,没有配置的话默认是在本地仓库
.config("spark.sql.warehouse.dir", "hdfs://hadoop102:9000/user/hive/warehouse")
.getOrCreate()
spark.sql("use spark1128")
// 1. 先有df
import spark.implicits._
val df = spark.read.json("E:\\bigdata\\text\\input\\spark\\sql\\person.json")
//val df = List(("a", 11L), ("b", 22L)).toDF
df.printSchema()
// 2. 写法1: 使用saveAsTable
//df.write.saveAsTable("user_1")
// df.write.mode("append").saveAsTable("user_1")
// 3. 写法2: 使用 insertInto
df.write.insertInto("user_1") // 大致等价于: df.write.mode("append").saveAsTable("user_1")
// df.write.insertInto("user_1") // 大致等价于: df.write.mode("append").saveAsTable("user_1")
// 4. 写法3: 使用 hive的insert 语句
// spark.sql("insert into table user_1 values('zs', 100)").show
//
spark.sql("select * from user_1").show
// 创建数据库
//spark.sql("create database test1129").show
// spark.sql("show databases").show
spark.close()
}
}
/*
读的时候不需要权限, 写的时候一般才需要权限.
saveAsTable
在保存的时候, 看列名, 只要列名一致, 顺序不重要
insertInto
不看列名, 只看顺序(类型):
*/
在 Hive 中创建表, 并导入数据.
一共有 3 张表: 1 张用户行为表, 1 张城市表, 1 张产品表
CREATE TABLE `user_visit_action`(
`date` string,
`user_id` bigint,
`session_id` string,
`page_id` bigint,
`action_time` string,
`search_keyword` string,
`click_category_id` bigint,
`click_product_id` bigint,
`order_category_ids` string,
`order_product_ids` string,
`pay_category_ids` string,
`pay_product_ids` string,
`city_id` bigint)
row format delimited fields terminated by '\t';
load data local inpath '/opt/module/datas/user_visit_action.txt' into table test1129.user_visit_action;
CREATE TABLE `product_info`(
`product_id` bigint,
`product_name` string,
`extend_info` string)
row format delimited fields terminated by '\t';
load data local inpath '/opt/module/datas/product_info.txt' into table test1129.product_info;
CREATE TABLE `city_info`(
`city_id` bigint,
`city_name` string,
`area` string)
row format delimited fields terminated by '\t';
load data local inpath '/opt/module/datas/city_info.txt' into table test1129.city_info;
热门商品是从点击量的维度来看的
计算各个区域前三大热门商品,并备注上每个商品在主要城市中的分布比例,超过两个城市用其他显示
例如:
地区 | 商品名称 | 点击次数 | 城市备注 |
---|---|---|---|
华北 | 商品A | 100000 | 北京21.2%,天津13.2%,其他65.6% |
华北 | 商品P | 80200 | 北京63.0%,太原10%,其他27.0% |
华北 | 商品M | 40000 | 北京63.0%,太原10%,其他27.0% |
东北 | 商品J | 92000 | 大连28%,辽宁17.0%,其他 55.0% |
import java.text.DecimalFormat
import org.apache.spark.sql.{Row, SaveMode, SparkSession}
import org.apache.spark.sql.expressions.{MutableAggregationBuffer, UserDefinedAggregateFunction}
import org.apache.spark.sql.types.{DataType, LongType, MapType, StringType, StructField, StructType}
/**
* @Author jaffe
* @Date 2020/05/15 14:59
*/
/*----
各区域热门商品 Top3
地区 商品名称 点击次数 城市备注
华北 商品A 100000 北京21.2%,天津13.2%,其他65.6%
华北 商品P 80200 北京63.0%,太原10%,其他27.0%
华北 商品M 40000 北京63.0%,太原10%,其他27.0%
东北 商品J 92000 大连28%,辽宁17.0%,其他 55.0%
----*/
object SqlApp {
def main(args: Array[String]): Unit = {
System.setProperty("HADOOP_USER_NAME", "jaffe")
val spark: SparkSession = SparkSession
.builder()
.master("local[*]")
.appName("SqlApp")
.enableHiveSupport()
.config("spark.sql.shuffle.partitions", 10)
.getOrCreate()
spark.sql("use spark1128")
spark.udf.register("remark", new CityRemarkUDAF)
spark.sql(
"""
|SELECT
|t2.area,
|t2.product_name,
|count,
|remark
|from
|(SELECT
|t1.area,
|t1.product_name,
|remark,
|count,
|rank() over(PARTITION by t1.area order by count desc) rk
|from
|(select
|remark(ci.city_name) remark,
|ci.area,
|pi.product_name,
|COUNT(*) count
|from user_visit_action uva JOIN
|city_info ci on uva.city_id = ci.city_id JOIN
|product_info pi on uva.click_product_id = pi.product_id
|group by ci.area,pi.product_name) t1
|) t2
|where rk <= 3
|""".stripMargin
).coalesce(1) // 降低分区
// .write
// .mode(SaveMode.Overwrite)
// .saveAsTable("result")
//spark.sql("select * from result").show
.show(1000,false)//显示1000行,默认true是截断显示行内容,false是完整显示行内容
spark.close()
}
}
// 城市备注的聚合函数
class CityRemarkUDAF extends UserDefinedAggregateFunction {
// 输入数据的类型 城市名 StringType
override def inputSchema: StructType = StructType(StructField("city", StringType) :: Nil)
// 北京->1000 天津->10 Map 缓冲区 MapType(key类型, value类型)
// 最好再缓冲一个总数
override def bufferSchema: StructType = StructType(StructField("map", MapType(StringType, LongType)) :: StructField("total", LongType) :: Nil)
// 输出类型 StringType
override def dataType: DataType = StringType
// 确定性
override def deterministic: Boolean = true
// 初始化 对缓冲区初始化
override def initialize(buffer: MutableAggregationBuffer): Unit = {
buffer(0) = Map[String, Long]()
buffer(1) = 0L
}
// 分区内聚合
override def update(buffer: MutableAggregationBuffer, input: Row): Unit = {
input match {
// 传入了一个城市
case Row(cityName: String) =>
// 北京
// 1. 先更新总数
buffer(1) = buffer.getLong(1) + 1L
// 2. 再去更新具体城市的数量
val map = buffer.getMap[String, Long](0)
buffer(0) = map + (cityName -> (map.getOrElse(cityName, 0L) + 1L))
// 传入了一个null
case _ =>
}
}
// 分区间聚合
override def merge(buffer1: MutableAggregationBuffer, buffer2: Row): Unit = {
// 把数据合并, 再放入到buffer1中
val map1 = buffer1.getMap[String, Long](0)
val total1 = buffer1.getLong(1)
val map2 = buffer2.getMap[String, Long](0)
val total2 = buffer2.getLong(1)
buffer1(1) = total1 + total2
buffer1(0) = map1.foldLeft(map2) {
case (map, (city, count)) =>
map + (city -> (map.getOrElse(city, 0L) + count))
}
}
// 返回最终的值
override def evaluate(buffer: Row): Any = {
val cityCount = buffer.getMap[String, Long](0)
val total = buffer.getLong(1)
val cityCountTop2 = cityCount.toList.sortBy(-_._2).take(2)
val cityRemarkTop2: List[CityRemark] = cityCountTop2.map {
case (city, count) => CityRemark(city, count.toDouble / total)
}
val cityRemarks = cityRemarkTop2 :+ CityRemark("其它", cityRemarkTop2.foldLeft(1D)(_ - _.rate))
cityRemarks.mkString(", ")
}
}
case class CityRemark(city: String, rate: Double) {
val f = new DecimalFormat("#.##%")
override def toString: String = s"$city:${f.format(math.abs(rate))}"
}