SELECT a1,a2,a3 FROM tableA Where condition
(1)SQL语句的结构
该语句是由Projection(al,a2,a3)、Data Source(tableA)、Filter(condition)组成的,分别对应SQL查询过程中的Result、Data Source、Operation。也就是说,SQL 语句是按Result –> Data Source –> Operation的次序来描述的。但在实际的执行过程中,是按Operation –> Data Source –> Result的次序来进行的,和SQL语句的次序刚好相反。
(2)SQL语句的运行过程
在执行过程中,有时候甚至不需要读取物理表就可以返回结果,比如重新运行刚运行过的SQL语句,可能直接从数据库的缓冲池中获取返回结果。
Spark SQL的表数据在内存中存储不是采用原生态的JVM对象存储方式,而是采用内存列存储,如图所示。该存储方式无论在『空间占用量』和『读取吞吐率』上都占有很大优势。
(1)对于原生态的JVM对象存储方式:
(2)对于内存列存储来说:
Spark SQL对SQL语句的处理和关系型数据库对SQL语句的处理采用了类似的方法,首先会将SQL语句进行解析(Parse),然后形成一个Tree。后续的如绑定、优化等处理过程都是对Tree的操作,而操作的方法是采用Rule,通过模式匹配,对不同类型的节点采用不同的操作。
在整个SQL语句的处理过程中,Tree和Rule相互配合,完成了解析、绑定(在Spark SQL中称为Analysis)、优化、物理计划等过程,最终生成可以执行的物理计划。
Spark SQL有两个分支:sqlContext和hiveContext:
(1)SQL 语句经过SqlParse解析成Unresolved LogicalPlan。
(2)使用analyzer 结合数据数据字典(catalog)进行绑定,生成resolved LogicalPlan。
(3)使用optimizer对resolved LogicalPlan进行优化,生成optimized LogicalPlan。
(4)使用SparkPlan将LogicalPlan 转换成PhysicalPlan。
(5)使用prepareForExecution()将PhysicalPlan转换成可执行物理计划。
(6)使用execute)执行可执行物理计划。
(7)生成SchemaRDD。
(1)SQL语句经过HiveQl.parseSql解析成Unresolved LogicalPlan,在这个解析过程中对hiveql 语句使用getAst()获取AST树,然后再进行解析。
(2)使用analyzer 结合数据Hive源数据Metastore(新的catalog)进行绑定,生成resolved LogicalPlan。
(3)使用optimizer对resolved LogicalPlan 进行优化,生成optimized LogicalPlan,优化前使用了ExtractPythonUdfs(catalog.PrelnsertionCasts(catalog.CreateTables(analyzed)))进行预处理。
(4)使用hivePlanner将LogicalPlan 转换成PhysicalPlan。
(5)使用prepareForExecution()将PhysicalPlan 转换成可执行物理计划。
(6)使用execute()执行可执行物理计划。
(7)执行后,使用map(_.copy)将结果导入SchemaRDD。
hiveContext继承自sqlContext,所以在hiveContext的的运行过程中除了override的函数和变量,可以使用和sqlContext一样的函数和变量。
Spark SQL引入了一种新的RDD——SchemaRDD(DataFrame),SchemaRDD由行对象(row)以及描述行对象中每列数据类型的schema组成。SchemaRDD很像传统数据库中的表。SchemaRDD可以通过RDD、Parquet 文件、JSON文件或者通过使用HiveQL 查询Hive数据来建立。
SchemaRDD除了可以和RDD一样操作外,还可以通过registerTempTable注册成临时表,然后通过SQL语句进行操作。
input = hiveCtx.jsonFile(inputFile)
// 注册输入的SchemaRDD
input.registerTempTable("tweets")
// 依据retweetCount( 转发计数)选出推文
topTweets = hiveCtx.sql("""SELECT text, retweetCount FROM tweets
ORDER BY retweetCount LIMIT 10""")
使用registerTempTable注册表是一个临时表,生命周期只在所定义的sqlContext或hiveContext实例之中。换言之,在一个sqlContext(或hiveContext)中registerTempTable的表不能在另一个sqlContext(或hiveContext)中使用。
Spark SQL1.1对数据的查询分成了2个分支:sqlContext和hiveContext。至于两者之间的关系,HiveSQL继承了sqlContext,所以拥有sqlontext的特性之外,还拥有自身的特性(最大的特性就是支持Hive)。
首先创建sqlContext,并引入sqlContext.createSchemaRDD以完成RDD隐式转换成SchemaRDD:
val sqlContext = new org.apache.spark.sql.SQLContext(sc)
import sqlContext.createSchemaRDD
(1)case class方式
//RDD1演示
case class Person(name:String,age:Int)
val rddpeople = sc.textFile("/SparkSQL/people.txt").map(_.split(",")).map(p =>
Person(pc0),p(1).trim.toInt))
rddpeople.registerTempTable("rddTable")
sqlContext.sql("SELECT name FROM rddTable WHERE age >= 13 AND age <= 19").
map(t =>"Name:"+t(0)).collect().foreach(println)
(2)applySchema方式
//RDD2演示
//导入spark SQL的数据类型和Row
import org.apache.spark.sql._
//创建于数据结构匹配的 schema
val schemaString = "name age"
val schema = StructType(schemaString.split("").map(fieldName =>
StructField(fieldName,StringType,true)))
//创建 rowRDD
val rowRDD = sc.textFile("/SparkSQL/people.txt").map(_.split(",")).map(p =>Row(p(0),p(1).trim))
//用applySchema将schema应用到rowRDD
val rddpeople2 = sqlContext.applySchema(rowRDD,schema)
rddpeople2.registerTempTable("rddTable2")
sqlContext.sql("SELECT name FROM rddTab1e2 WHERE age >= 13 AND age <= 19").map(t =>
"Name:"+t(0)).collect().foreach(println)
将建立的SchemaRDD:people保存成parquet文件:
rddpeople.saveAsParquetFile("/Spark sQL/people.parquet")
将people.parquet读入,注册成表parquetTable:
//parquet演示
val parquetpeople = sqlContext.parquetFile("/Spark SQL/people.parquet")
parquetpeople.registerTempTable("parquetTable")
sqlContext.sql("SELECT name FROM parquetTable WHERE age> = 25").map(t =>
"Name:"+t(0)).collect().foreach(println)
//JSON演示
val jsonpeople = sqlContext.jsonFile("/Spark SQL/people.json")
jsonpeople.registerTempTable("jsonTable")
sqlcontext.sql("SELECT name FROM jsonTable WHERE age >= 25").map(t =>
"Name:"+t(0)).collect().foreach(println)
构建hiveContext:
val hiveContext = new org.apache.spark.sql.hive.HiveContext(sc)
Hive数据进行操作:
hiveContext.sql("use saledata")
hiveContext.sql("show tables").collect().foreach(println)
查询所有订单中每年的销售单数、销售总额:
//所有订单中每年的销售单数、销售总额
//三个表连接后以count(distinct a.ordernumber)计销售单数,sum(b.amount)计销售总额
hiveContext.sql("select c.theyear, count(distinct a.ordernumber),sum(b.amount)
from tblStock a join tblstockDetail b on a.ordernumber = b.ordernumber
join tbldate c on a.dateid = c.dateid group by c.theyear order by c.theyear").
collect().foreach(println)
在sqlContext或hiveContext中,来源于不同数据源的表在各自生命周期中可以混用,但是不同实例之间的表不能混合使用。
在sqlContext中混合使用:
//在sqlContext中混合使用
//sqlContext中来自rdd的表rddgab1e和来自parquet文件的表parquetTable混合使用
sqlContext.sql("select a.name,a.age,b.age from rddTable a join parquetTable b
on a.name = b.name").collect().foreach(println)
在hiveContext中混合使用:
//在hiveContext中混合使用
//创建一个hiveTable,并将数据加载,注意people.txt 第二列有空格,所以age取string类型
hiveContext.sql("CREATE TABLE hiveTable(name string,age string)
ROW FORMAT DELIMITED FIELDS TERMINATED BY','
LINES TERMINATED BY'\n'")
hiveContext.sql("LOAD DATA LOCAL INPATH '/home/mmicky/mboo/MyClass/doc/Spark SQL/data/people.txt'
INTO TABLE hiveTable")
//创建一个源自parquet文件的表parquetTable2,然后和hiveTable混合使用
hiveContext.parquetFile("/Spark SQL/people.parquet").registerAsTable("parquetTable2")
hiveContext.sql("select a.name,a.age,b.age from hiveTable a join parquetTable2 b
on a.name = b.name").collect().foreach(println)
Spark SQL的cache可以使用两种方法来实现:
在sqlContext里可以如下使用cache:
//sqlContext的 cache 使用
sqlContext.cacheTable("rddTable")
sqlContext.sql("SELECT name FROM rddTable WHERE age >= 13 AND age <= 19").map(t =>
"Name:"+t(0)).collect().foreach(println)
sqlContext.sql("CACHE TABLE parquetTable")
sqlContext.sql("SELECT name FROM parquetTable WHERE age > = 13 AND age <= 19").map(t =>
"Name:"+t(0)).collect().foreach(println)
观察webUI,可以看到cache的信息(注意cache是“lazy”的,要有action才会实现;uncache是“eager”的,可以立即实现)。
使用如下命令可以取消cache:
sqlContext.uncacheTable("rddTable")
sqlContext.sql("UNCACHE TABLE parquetTable")
通过ThriftServer连接JDBC访问Spark SQL:
package doc
import java.sql.DriverManager
object SQLJDBC{
def main(args: Array[ String]){
Class.forName("org.apache.hive.jdbc.HiveDriver")
val conn = DriverManager.getConnection("jdbc:hive2://hadoop2:10000","hadoop","")
try{
val statement = conn.createStatement
val rs = statement.executeQuery("select ordernumber, amount from tblstockDetail where amount > 3000")
while(rs.next){
val ordernumber = rs.getstring("ordernumber")
val amount = rs.getString("amount")
print1n("ordernumber = $s, amount = %s".format(ordernumber, amount))
}
} catch {
case e: Exception => e.printStackTrace
}
conn.close
}
}
店铺分类:根据销售额对店铺分类,使用Spark SQL和MLlib
使用Spark SQL+MLlib的聚类算法,采用IntelliJ IDEA调试代码,最后生成doc.jar,然后使用spark-submit提交给集群运行。
package doc
import org.apache.log4j.{Level,Logger}
import org.apache.spark.sql.catalyst.expressions.Row
import org.apache.spark.{SparkConf,SparkContext}
import org.apache.spark.sql.hive.HiveContext
import org.apache.spark.mllib.clustering.KMeans
import org.apache.spark.mllib.linalg.Vectors
object SQLMLlib{
def main(args:Array[String]){
//屏蔽不必要的日志显示在终端上
Logger.getLogger("org.apache.spark").setLevel(Level.WARN)
Logger.getLogger("org.eclipse.jetty.server").setLevel(Level.OFF)
//设置运行环境
val sparkConf = new SparkConf().setAppName("SQLMLlib")
val sc = new SparkContext(sparkConf)
val hiveContext = new HiveContext(sc)
//使用Spark SQL查出每个店的销售数量和金额
hiveContext.sql("use saledata")
hiveContext.sql("SET spark.sql.shuffle.partitions = 20")
val sqldata = hiveContext.sql("select a.locationid, sum(b.qty)totalqty, sum(b.amount)totalamount from tblStock a join tblstockdetail b on a.ordernumber
= b.ordernumber group by a.locationid")
//将查询数据转换成向量
val parsedData = sqldata.map{
case Row(_, totalgty, totalamount) =>
val features = Array[Double](totalqty.tostring.toDouble, totalamount.tostring.toDouble)
Vectors.dense(features)
}
//对数据集聚类,3个类,20次迭代,形成数据模型
//注意这里会使用设置的partition数20
val numClusters = 3
val numIterations = 20
val model = KMeans.train(parsedData, numclusters, numIterations)
//用模型对读入的数据进行分类,并输出
//由于partition没设置,输出为200个小文件,可以使用bin/hdfs dfs-getmerge合并下载到本地
val result2 = sqldata.map{
case Row(locationid, totalqty, totalamount) =>
val features = Array[Double](totalqty.tostring.toDouble, totalamount.toString.toDouble)
val linevectore = Vectors.dense(features)
val prediction = model.predict(linevectore)
locationid+" "+totalqty+" "+ totalamount +" "+prediction
}.saveAsTextFile(args(0))
sc.stop()
}
}