sparkSQL11(Spark SQL编程)

文章目录

  • 以编程方式执行Spark SQL查询
    • 1、编写Spark SQL程序实现RDD转换成DataFrame
    • 2、编写Spark SQL程序操作HiveContext
  • 数据源
    • 1、SparkSql从MySQL中加载数据
    • 2、通过spark-shell运行加载mysql当中的数据
    • 3、SparkSql将数据写入到MySQL中
  • sparkSQL整合hive
    • 1、sparkSQL整合hive
    • 2、sparkSQL使用

以编程方式执行Spark SQL查询

1、编写Spark SQL程序实现RDD转换成DataFrame

前面我们学习了如何在Spark Shell中使用SQL完成查询,现在我们通过IDEA编写Spark SQL查询程序。
Spark官网提供了两种方法来实现从RDD转换得到DataFrame,第一种方法是利用反射机制,推导包含某种类型的RDD,通过反射将其转换为指定类型的DataFrame,适用于提前知道RDD的schema。第二种方法通过编程接口与RDD进行交互获取schema,并动态创建DataFrame,在运行时决定列及其类型。
第一步:创建maven工程并导入依赖jar包

<properties>
        <scala.version>2.11.8</scala.version>
        <spark.version>2.2.0</spark.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.scala-lang</groupId>
            <artifactId>scala-library</artifactId>
            <version>${scala.version}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.spark</groupId>
            <artifactId>spark-core_2.11</artifactId>
            <version>${spark.version}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.spark</groupId>
            <artifactId>spark-sql_2.11</artifactId>
            <version>${spark.version}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.hadoop</groupId>
            <artifactId>hadoop-client</artifactId>
            <version>2.7.5</version>
        </dependency>
    </dependencies>
    <build>
        <sourceDirectory>src/main/scala</sourceDirectory>
        <testSourceDirectory>src/test/scala</testSourceDirectory>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.0</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <encoding>UTF-8</encoding>
                    <!--    <verbal>true</verbal>-->
                </configuration>
            </plugin>
            <plugin>
                <groupId>net.alchim31.maven</groupId>
                <artifactId>scala-maven-plugin</artifactId>
                <version>3.2.0</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>compile</goal>
                            <goal>testCompile</goal>
                        </goals>
                        <configuration>
                            <args>
                                <arg>-dependencyfile</arg>
                                <arg>${project.build.directory}/.scala_dependencies</arg>
                            </args>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-shade-plugin</artifactId>
                <version>3.1.1</version>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals>
                            <goal>shade</goal>
                        </goals>
                        <configuration>
                            <filters>
                                <filter>
                                    <artifact>*:*</artifact>
                                    <excludes>
                                        <exclude>META-INF/*.SF
                                        META-INF/*.DSA
                                        META-INF/*.RSA
                                    
                                
                            
                            
                                
                                    
                                
                            
                        
                    
                
            
        
    

第一种方式创建DF:通过反射配合样例类推断Schema
Scala支持使用case class类型导入RDD转换为DataFrame,通过case class创建schema,case class的参数名称会被利用反射机制作为列名。这种RDD可以高效的转换为DataFrame并注册为表。
代码开发如下:
读取资料当中的person.txt,通过RDD,转换成为DF

import org.apache.spark.SparkContext
import org.apache.spark.rdd.RDD
import org.apache.spark.sql.{Column, DataFrame, Dataset, SparkSession}

case class Person(id:Int,name:String,age:Int)

object SparkSqlStudy {

  def main(args: Array[String]): Unit = {
    //第一步:获取sparkSession对象
    val sparkSession: SparkSession = SparkSession.builder().appName("sparkSqlStudy").master("local[2]").getOrCreate()
    //第二步:获取sparkContext
    val sparkContext: SparkContext = sparkSession.sparkContext
    sparkContext.setLogLevel("WARN")
    //读取文件,生成RDD
    val rddFile: RDD[String] = sparkContext.textFile("file:///F:\\scala与spark课件资料教案\\spark课程\\3、spark第三天\\资料\\person.txt")
    //将每一行的内容按照空格切分,生成一个单词数组
    val wordArray: RDD[Array[String]] = rddFile.map(_.split(" "))
    val personRDD: RDD[Person] = wordArray.map(x => Person(x(0).toInt,x(1),x(2).toInt))

    //导入隐式转换的类,我们的RDD才可以转换成DataFrame
    import sparkSession.implicits._
    //将我们的RDD转换成DataFrame
    val personDF: DataFrame = personRDD.toDF()

    /*********************************sparkSql DSL风格语法 开始**************************************/
    personDF.printSchema()

    personDF.show()

    personDF.show(2)

    println(personDF.head())
    //查询name字段的所有值
    personDF.select("name").show()
    personDF.select($"name").show()
    personDF.select(personDF.col("name")).show()
    personDF.select(new Column("name")).show()
    //将年龄的值进行 + 1
    personDF.select($"id",$"name",$"age",$"age"+1).show()
    //按照年龄进行分组
    personDF.groupBy($"age").count().show()
    /*********************************sparkSql DSL风格语法 结束**************************************/
    /*********************************sparkSql sql风格语法 开始**************************************/
    println("**************************")
    val personTable: Unit = personDF.registerTempTable("t_person")
    val personView: Unit = personDF.createTempView("t_person_view")
    //查询表当中所有的数据
    sparkSession.sql("select * from t_person").show()
    sparkSession.sql("select * from t_person_view").show()
    sparkSession.sql("select * from t_person_view where name = 'wangwu'").show()
    /*********************************sparkSql sql风格语法 结束**************************************/
    sparkContext.stop()
    sparkSession.close()
  }
}

第二种方式创建DF:通过StructType配合Row直接指定Schema
当case class不能提前定义好时,可以通过以下三步创建DataFrame
(1)将RDD转为包含Row对象的RDD
(2)基于StructType类型创建schema,与第一步创建的RDD相匹配
(3)通过sparkSession的createDataFrame方法对第一步的RDD应用schema创建DataFrame

import org.apache.spark.SparkContext
import org.apache.spark.rdd.RDD
import org.apache.spark.sql.types.{IntegerType, LongType, StringType, StructType}
import org.apache.spark.sql.{DataFrame, Row, SparkSession}

object SparkSqlSchema {

  def main(args: Array[String]): Unit = {
    //创建sparkSession
    val sparkSession: SparkSession = SparkSession.builder().appName("SparkSqlSchema").master("local[2]").getOrCreate()
    //通过sparkSession构建SparkContext对象
    val sparkContext: SparkContext = sparkSession.sparkContext
    //设置日志级别
    sparkContext.setLogLevel("WARN")
    //读取文件内容,通过文件内容构建RDD
    val fileRdd: RDD[String] = sparkContext.textFile("file:///F:\\scala与spark课件资料教案\\spark课程\\3、spark第三天\\资料\\person.txt")
    //文本内容的每一行进行切分
    val arrayRdd: RDD[Array[String]] = fileRdd.map(x => x.split(" "))
    //将每一个数组里面的三个值取出来,构建row对象
    val rowRdd: RDD[Row] = arrayRdd.map(x => Row(x(0).toInt,x(1),x(2).toInt))
    //构建StructType对象
    val struct:StructType = (new StructType).add("id", IntegerType, true, "id第一个字段").add("name", StringType, true, "name第二个字段").add("age", IntegerType, true, "age第三个字段")
    //通过row以及structType来得到DataFrame
    val personDataFrame: DataFrame = sparkSession.createDataFrame(rowRdd,struct)
    //打印schema信息
    personDataFrame.printSchema()
    //将我们获取的DataFrame
    val personView: Unit = personDataFrame.createTempView("t_person_view")

    sparkSession.sql("select * from t_person_view").show()

    sparkContext.stop()
    sparkSession.close()
  }
}

2、编写Spark SQL程序操作HiveContext

HiveContext是对应spark-hive这个项目,与hive有部分耦合, 支持hql,是SqlContext的子类,在Spark2.0之后,HiveContext和SqlContext在SparkSession进行了统一,可以通过操作SparkSession来操作HiveContext和SqlContext。
需求:创建hive表,加载student.csv,进行数据分析
第一步:添加pom依赖

<dependency>
            <groupId>org.apache.spark</groupId>
            <artifactId>spark-hive_2.11</artifactId>
            <version>2.2.0</version>
</dependency>

第二步:代码实现

import org.apache.spark.sql.SparkSession

object SparkHiveSql {
  def main(args: Array[String]): Unit = {
    val sparkSession: SparkSession = SparkSession
                                        .builder()
                                        .master("local[2]")
                                        .appName("SparkHiveSql")
                                        .enableHiveSupport()//开启hive的支持
                                        .getOrCreate()
    sparkSession.sql("create table if not exists student2(id int,name String,age int) row format delimited fields terminated by ','")
    sparkSession.sql("load data local inpath './datas/student.csv' overwrite into table student2 ")
    sparkSession.sql("select * from student2").show()
    sparkSession.stop()
  }
}

数据源

Spark SQL可以通过JDBC从关系型数据库中读取数据的方式创建DataFrame,通过对DataFrame一系列的计算后,还可以将数据再写回关系型数据库中。

1、SparkSql从MySQL中加载数据

第一步:添加jdbc连接驱动jar包

<dependency>
	<groupId>mysql</groupId>
	<artifactId>mysql-connector-java</artifactId>
	<version>5.1.38</version>
</dependency>

第二步:开发代码,读取mysql数据库当中的数据

object SparkMysql {
  def main(args: Array[String]): Unit = {
    //创建sparkSession对象
    val sparkSession: SparkSession = SparkSession.builder().appName("sparkMySql").master("local[2]").getOrCreate()
    val url ="jdbc:mysql://localhost:3306/userdb"
    val tableName = "emp";
    val properties = new Properties()
    //注意:用户名和密码的属性只能是 user   password
    properties.setProperty("user","root")
    properties.setProperty("password","admin")
    val tableDatas: Unit = sparkSession.read.jdbc(url,tableName,properties).show()
    sparkSession.close()
  }
}

2、通过spark-shell运行加载mysql当中的数据

通过spark-shell运行
(1)、启动spark-shell(必须指定mysql的连接驱动包)

bin/spark-shell  \
--master spark://node01:7077 \
--executor-memory 1g \
--total-executor-cores 2 \
--jars /export/servers/hive-1.1.0-cdh5.14.0/lib/mysql-connector-java-5.1.38.jar \
--driver-class-path /export/servers/hive-1.1.0-cdh5.14.0/lib/mysql-connector-java-5.1.38.jar

(2)、从mysql中加载数据

val mysqlDF = spark.read.format("jdbc").options(Map("url" -> "jdbc:mysql://192.168.0.107:3306/userdb", "driver" -> "com.mysql.jdbc.Driver", "dbtable" -> "emp", "user" -> "root", "password" -> "admin")).load()

(3)、执行查询

scala> mysqlDF.show

3、SparkSql将数据写入到MySQL中

通过IDEA编写SparkSql代码
将person.txt文本文件写入到mysql数据库表当中去
思路解析:通过sparkContext读取文件,然后转换成RDD,将RDD转换成为DataFrame,然后注册成为一张表,查询出来数据,插入保存到mysql当中去
代码实现:

import java.util.Properties
import org.apache.spark.SparkContext
import org.apache.spark.rdd.RDD
import org.apache.spark.sql.{DataFrame, SaveMode, SparkSession}
case class Peson(id:Int,name:String,age:Int)
object Spark2Mysql {
  /**
    * 读取文本文件数据,然后写入到mysql数据库表当中去
    * @param args
    */
  def main(args: Array[String]): Unit = {
    //获取sparkSession
    val sparkSession: SparkSession = SparkSession.builder().appName("spark2Mysql").master("local[2]").getOrCreate()
    //通过sparkSession得到sparkContext
    val sparkContext: SparkContext = sparkSession.sparkContext
    //通过sparkContext 读取文本文件内容,得到RDD
    val arrRDD: RDD[Array[String]] = sparkContext.textFile("file:///F:\\scala与spark课件资料教案\\spark课程\\3、spark第三天\\资料\\person.txt").map(x => x.split(" "))
    //通过RDD,配合样例类,将我们的数据转换成样例类对象
    val personRDD: RDD[Person] = arrRDD.map(x => Person(x(0).toInt,x(1),x(2).toInt))
    //导入sparkSession当中的隐式转换,将我们的样例类对象转换成DataFrame
    import sparkSession.implicits._
    val personDF: DataFrame = personRDD.toDF()
    //打印dataFrame当中的数据
    val personDFShow: Unit = personDF.show()
    //将DataFrame注册成为一张表模型
    val personView: Unit = personDF.createTempView("person_view")
    //获取表当中的数据
    val result: DataFrame = sparkSession.sql("select * from person_view")
    //获取mysql连接
    val url ="jdbc:mysql://localhost:3306/userdb"
    val tableName = "person"
    val properties = new Properties()
    properties.setProperty("user","root")
    properties.setProperty("password","admin")
    //将我们查询的结果写入到mysql当中去
    val jdbc: Unit = result.write.mode(SaveMode.Append).jdbc(url,tableName,properties)
    sparkContext.stop()
    sparkSession.close()
  }
}

(2)用maven将程序打包

修改代码进行打包
修改master提交地址

//获取sparkSession
    val sparkSession: SparkSession = SparkSession.builder()
                                                  .appName("spark2Mysql")
                                                //  .master("local[2]")  打包集群运行,不用再指定master的地址了,等会儿提交jar包的时候,我们手动指定
                                                  .getOrCreate()

修改文件读取路径

val arrRDD: RDD[Array[String]] = sparkContext.textFile(args(0)).map(x => x.split(" "))

(3)将Jar包提交到spark集群

spark-submit --master spark://node01:7077 \
--class cn.itcast.spark.sql.Spark2Mysql \
--executor-memory 1g \
--total-executor-cores 2 \
--jars /export/servers/hive-1.1.0-cdh5.14.0/lib/mysql-connector-java-5.1.38.jar \
--driver-class-path /export/servers/hive-1.1.0-cdh5.14.0/lib/mysql-connector-java-5.1.38.jar \
original-day03-1.0-SNAPSHOT.jar hdfs://node01.hadoop.com:8020/person.txt

(4)查看mysql中表的数据
sparkSQL11(Spark SQL编程)_第1张图片
如果出现以下错误

Caused by: java.sql.SQLException: Cannot execute statement: impossible to write to binary log since BINLOG_FORMAT = STATEMENT and at least one table uses a storage engine limited to row-based logging. InnoDB is limited to row-logging when transaction isolation level is READ COMMITTED or READ UNCOMMITTED.

在sqlyog当中直接执行这一句即可

SET GLOBAL binlog_format=mixed;

sparkSQL整合hive

Spark SQL主要目的是使得用户可以在Spark上使用SQL,其数据源既可以是RDD,也可以是外部的数据源(比如文本、Hive、Json等)。Spark SQL的其中一个分支就是Spark on Hive,也就是使用Hive中HQL的解析、逻辑执行计划翻译、执行计划优化等逻辑,可以近似认为仅将物理执行计划从MR作业替换成了Spark作业。SparkSql整合hive就是获取hive表中的元数据信息,然后通过SparkSql来操作数据

1、sparkSQL整合hive

第一步:将hive-site.xml拷贝到spark安装家路径的conf目录下
node03执行以下命令来拷贝hive-site.xml到所有的spark安装服务器上面去

cd /export/servers/hive-1.1.0-cdh5.14.0/conf
cp hive-site.xml  /export/servers/spark-2.2.0-bin-2.6.0-cdh5.14.0/conf/
scp hive-site.xml  node02:/export/servers/spark-2.2.0-bin-2.6.0-cdh5.14.0/conf/
scp hive-site.xml  node01:/export/servers/spark-2.2.0-bin-2.6.0-cdh5.14.0/conf/

第二步:将mysql的连接驱动包拷贝到spark的jars目录下
node03执行以下命令将连接驱动包拷贝到spark的jars目录下,三台机器都要进行拷贝

cd /export/servers/hive-1.1.0-cdh5.14.0/lib
cp mysql-connector-java-5.1.38.jar /export/servers/spark-2.2.0-bin-2.6.0-cdh5.14.0/jars/
scp mysql-connector-java-5.1.38.jar node02:/export/servers/spark-2.2.0-bin-2.6.0-cdh5.14.0/jars/
scp mysql-connector-java-5.1.38.jar node01:/export/servers/spark-2.2.0-bin-2.6.0-cdh5.14.0/jars/

第三步:测试sparksql整合hive是否成功
先启动hadoop集群,在启动spark集群,确保启动成功之后node01执行命令:

cd /export/servers/spark-2.2.0-bin-2.6.0-cdh5.14.0
bin/spark-sql --master spark://node01:7077 --executor-memory 1G --total-executor-cores 2

指明master地址、每一个executor的内存大小、一共所需要的核数、
mysql数据库连接驱动。
执行成功后的界面:进入到spark-sql 客户端命令行界面

查看当前有哪些数据库, 并创建数据库

show databases;
create database sparkhive;

在spark2.0版本后由于出现了sparkSession,在初始化sqlContext的时候,会设置默认的spark.sql.warehouse.dir=spark-warehouse,
此时将hive与sparksql整合完成之后,在通过spark-sql脚本启动的时候,还是会在哪里启动spark-sql脚本,就会在当前目录下创建一个spark.sql.warehouse.dir为spark-warehouse的目录,存放由spark-sql创建数据库和创建表的数据信息,与之前hive的数据息不是放在同一个路径下(可以互相访问)。但是此时spark-sql中表的数据在本地,不利于操作,也不安全。

所有在启动的时候需要加上这样一个参数:
–conf spark.sql.warehouse.dir=hdfs://node01:8020/user/hive/warehouse
保证spark-sql启动时不在产生新的存放数据的目录,sparksql与hive最终使用的是hive同一存放数据的目录。
如果使用的是spark2.0之前的版本,由于没有sparkSession,不会有spark.sql.warehouse.dir配置项,不会出现上述问题。
node01执行以下命令重新进去spark-sql

cd /export/servers/spark-2.2.0-bin-2.6.0-cdh5.14.0
bin/spark-sql \
 --master spark://node01:7077 \
 --executor-memory 1G --total-executor-cores 2 \
 --conf spark.sql.warehouse.dir=hdfs://node01:8020/user/hive/warehouse

2、sparkSQL使用

进入到sparkSQL之后便可以进行sparkSQL的开发了,但是实际工作当中我们一般都是将写好的HQL语句放到脚本文件里面去,然后通过定时执行脚本文件即可
第一步:准备表数据
node01服务器准备表数据

cd /export/servers
hdfs dfs -mkdir -p /sparkhivedatas
vim sparkhive.txt

1 zhangsan 18
2 lisi 28
3 wangwu 20
4 zhaoliu 38
5 chenqi 45

将数据上传到hdfs上面去

cd /export/servers
hdfs dfs -put sparkhive.txt /sparkhivedatas

第二步:开发sparkSQL脚本,并加载表数据
node01执行以下命令开发sparkSQL脚本

cd /export/servers
vim sparkhive.sql
create database if not exists sparkhivedb;
create table if not exists sparkhivedb.user(id int,name string,age int) row format delimited fields terminated by ' ';
load data inpath 'hdfs://node01:8020/sparkhivedatas' overwrite into table sparkhivedb.user;
drop table if exists sparkhivedb.userresult;
create table sparkhivedb.userresult as select * from sparkhivedb.user where age > 30;

第三步:开发shell脚本,使用shell脚本执行sparkSQL脚本
node01执行以下命令开发sparkSQL脚本

cd /export/servers
vim sparksql.sh

#!/bin/bash

SPARK_SQL="/export/servers/spark-2.2.0-bin-2.6.0-cdh5.14.0/bin/spark-sql --master spark://node01:7077  --executor-memory 1G -
-total-executor-cores 2  --conf spark.sql.warehouse.dir=hdfs://node01:8020/user/hive/warehouse"

$SPARK_SQL -f /export/servers/sparkhive.sql

#或者我们也可以直接将sql语句写到shell脚本里面通过 -e 来进行执行,例如

SPARK_HQL="create database if not exists sparkhqldb; create table if not exists sparkhqldb.myuser(id int,name string,age int)
 row format delimited fields terminated by ' ' ;"


$SPARK_SQL -e "$SPARK_HQL"

你可能感兴趣的:(spark,大数据,hive,大数据,spark)