1、创建DataFrame的方式

package com.netcloud.bigdata.sparksql

import java.util.Properties

import org.apache.spark.sql.{SaveMode, SparkSession}

/**
  * DataFrame的创建
  * 从已经存在的RDD生成,从hive表、或者其他数据源(本地或者HDFS)
  * 1)在创建一个DataFrame时候,首先要用创建一个sparksession(sparksql程序的入口)
 *  2)通过读取其他数据源的方式 创建DataFrame
  *   a) 读取csv文件创建DataFrame
  *   b) 读取json文件创建DataFrame
  *   c) 读取txt文件创建DataFrame
  *   d) 读取parquet文件创建DataFrame
  *   e) 读取JDBC文件创建DataFrame
  * 3) 通过hive读取数据 创建DataFrame
  *   spark.sql("select * from user")
  * @author yangshaojun
  * #date  2019/3/8 16:59
  * @version 1.0
  */
object SparkSql_000_createDataFrame {
  def main(args: Array[String]): Unit = {
    val spark = SparkSession
      .builder()
      .appName("Spark SQL basic example")
      .master("local[2]")
      .config("hadoop.home.dir", "/user/hive/warehouse")//开启对hive的支持
      .enableHiveSupport()
      .getOrCreate()

    //1、通过读取csv文件的方式 创建DataFrame; option("header",true)表示csv文件第一行是字段名(scheme信息);
    //   读取csv文件的方式 如下几种
    val csvDF1=spark.read.option("header",true).csv("data/sparksql/bank_full.csv")// 本地文件
    //val csvDF2=spark.read.option("header",true).csv("hdfs:///ns1/data/bank_full.csv")//HDFS文件

    val csvDF3=spark.read.format("csv").option("header",true).load("data/sparksql/bank_full.csv")// 使用format指定读取的文件格式

    //2、通过读取json文件方式 创建DataFrame
    val jsonDF=spark.read.json("data/sparksql/people.json")
    //jsonDF.show()

    //3、读取parquet文件的方式 创建DataFrame
    val parquetDF=spark.read.parquet("data/parquet/*")
    //parquetDF.show()

    //4、读取text 文件创建DataFrame
    val textDF=spark.read.text("data/sparkcore/wordcount.txt")
    //textDF.show()

    //5、读取JDBC 文件创建DataFrame 然后保存到HDFS
    val url="jdbc:mysql://106.12.219.51:3306/maximai"
    val tabName="sys_user"
    val prop=new Properties()
    prop.setProperty("user","maximai")
    prop.setProperty("password","maximai")
    val jdbcDF=spark.read.jdbc(url,tabName,prop)
    jdbcDF.show()
    //保存模式 为覆盖 文件格式是csv
    //jdbcDF.write.mode(SaveMode.Overwrite).format("com.databricks.spark.csv").save("data/mysqlToHDFS/sys_user") //将mysql中的数据导入到HDFS

    //6、从Hive里面读取数据 开启对hive的支持
    spark.sql("select * from user").show()

  }
}
package com.netcloud.bigdata.sparksql

import org.apache.spark.sql.SparkSession

/**
  * DataSet的创建
  * @author yangshaojun
  * #date  2019/3/8 17:21
  * @version 1.0
  */
object SparkSql_001_createDataset {
  case class Person(name: String, age: Long)

  def main(args: Array[String]): Unit = {
    val spark = SparkSession
      .builder()
      .appName("Spark SQL basic example")
      .master("local[2]")
      .getOrCreate()
    import  spark.implicits._

    val path="data/sparksql/people.json"
    val peopleDS = spark.read.json(path).as[Person]
    peopleDS.show()
  }
}

2、DataFrame的常用操作

java版本

package com.netcloud.spark.sparksql;

import org.apache.spark.sql.*;

/**
 /**
 * java版本的DataFrame的常用操作。
 * 通常将DataFrame 存储在一张临时表中,后面通过执行sql的方式执行操作。
 *
 * @author yangshaojun
 * #date  2019/4/3 9:03
 * @version 1.0
 */
public class DataFrameNormalOperation {
    public static void main(String[] args) {

        SparkSession spark = SparkSession
                .builder()
                .appName("DataFrameNormalOperation")
                .master("local")
                .getOrCreate();
        // 等同于 SQLContext对象 sc.read().json("../.json")以及 sc.read().load(../.json)
        Dataset<Row> rowDataset = spark.read().json("data/sparksql/people.json");

        // 1、打印DataFrame数据信息
        rowDataset.show();
        // 2、打印schema信息
        rowDataset.printSchema();
        // 3、打印某列信息
        rowDataset.select("name").show();//等同于rowDataset.select(rowDataset.col("name")).show();
        // 4、查询多列信息;并且对列使用表达式操作 (df.col(columnName))
        rowDataset.select(rowDataset.col("name"), rowDataset.col("age").plus(1)).show();
        // 5、过滤出年龄大于25的people
        rowDataset.filter(rowDataset.col("age").gt(25)).show();
        rowDataset.select(rowDataset.col("name"), rowDataset.col("age"), rowDataset.col("age").gt(25)).show();
        // 6、跟根据某个字段分组 并统计总数
        rowDataset.groupBy("age").count().show();
        // 将DataFrame注册到临时表
        rowDataset.registerTempTable("people");
        // sparksession对象调用sql方法 方法参数书写sql表达式
        spark.sql("select * from people").show();

        // 将DataFrame注册全局表
        try {
            rowDataset.createGlobalTempView("peoples");
        } catch (AnalysisException e) {
            e.printStackTrace();
        }
        // sparksession对象调用sql方法 方法参数书写sql表达式
        // 全局临时视图绑定到系统保留的数据库`global_temp`
        spark.sql("select * from global_temp.peoples").show();



    }
}

 

scala版本

package com.netcloud.bigdata.sparksql

import org.apache.spark.sql.SparkSession

/**
  * scala版本的DataFrame的常用操作。
  * 通常将DataFrame 存储在一张临时表中,后面通过执行sql的方式执行操作。
  * @author yangshaojun
  * #date  2019/4/3 9:29
  * @version 1.0
  */
object SparkSql_002_DataFrameNormalOperation {
  def main(args: Array[String]): Unit = {
    val spark = SparkSession
      .builder()
      .master("local")
      .appName("SparkSql_003_DataFrameNormalOperation")
      .getOrCreate()

    import spark.implicits._

    // 等同于 SQLContext对象 sc.read().json("../.json")以及 sc.read().load(../.json)
    val df = spark.read.json("data/sparksql/people.json")

    // 1、打印DataFrame数据信息
    df.show()

    // 2、打印schema信息
    df.printSchema()

    // 3、打印某列信息
    df.select("name")

    // 4、查询多列信息;并且对列使用表达式操作 (df.col(columnName))
    df.select(df.col("name"), df.col("age") + 1, df.col("age").plus(2)).show()
    df.select($"name",$"age".plus(3),$"age"+4).show() // $columnName 的语法需要引入spark.implicits._

    // 5、过滤出年龄大于25的people
    df.filter($"age".gt(25)).show()

    // 6、跟根据某个字段分组 并统计总数
    df.groupBy("age").count().show()

    // 将DataFrame注册到临时表
    df.registerTempTable("people")
    // sparksession对象调用sql方法 方法参数书写sql表达式
    spark.sql("select * from people").show()

    // 将DataFrame注册全局表
    df.createOrReplaceGlobalTempView("people")
    // sparksession对象调用sql方法 方法参数书写sql表达式
    // 全局临时视图绑定到系统保留的数据库`global_temp`
    spark.sql("select * from global_temp.people").show()

  }
}

3、RDD转为DataFrame的方法

java版本

package com.netcloud.spark.sparksql;

import java.io.Serializable;

/**
 * 创建Person类 并实现序列化
 * Person类中仅支持简单数据类型,不能有复杂的数据类型 像List Array 或者自定类型
 * 对于scala的样例类时可以支持Array seq等类型的
 * @author yangshaojun
 * #date  2019/4/3 11:40
 * @version 1.0
 */
public class People implements Serializable {

    private static final long serialVersionUID = 555241500840980177L;
    private String name;
    private int age;
    public People() {

    }
    public People(String name,int age) {
        this.name = name;
        this.age=age;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "name:"+name+" "+"age:"+age;
    }
}



================================================================================

package com.netcloud.spark.sparksql;

import org.apache.spark.SparkConf;
import org.apache.spark.api.java.JavaRDD;
import org.apache.spark.sql.types.StructField;
import org.apache.spark.sql.types.StructType;
import org.apache.spark.api.java.JavaSparkContext;
import org.apache.spark.api.java.function.Function;
import org.apache.spark.sql.Dataset;
import org.apache.spark.sql.Row;
import org.apache.spark.sql.RowFactory;
import org.apache.spark.sql.SQLContext;
import org.apache.spark.sql.types.DataTypes;
import java.util.ArrayList;

import java.util.List;

/**
 * java版本使用反射和动态转换的方式将RDD转为DataFrame
 * @author yangshaojun
 * #date  2019/4/3 11:41
 * @version 1.0
 */
public class RDD2DataFrame {
    public static void main(String[] args) {

        JavaSparkContext sc = new JavaSparkContext(new SparkConf()
                .setMaster("local")
                .setAppName("RDD2DataFrame"));
        SQLContext sqlContext = new SQLContext(sc);
        JavaRDD<String> stringRDD = sc.textFile("data/sparksql/people.txt", 3);
        JavaRDD<People> peoples = stringRDD.map(new Function<String, People>() {

            @Override
            public People call(String str) throws Exception {
                String[] arrs = str.split(",");
                People people = new People(arrs[0], Integer.valueOf(arrs[1].trim()));
                return people;
            }
        });
        // 使用反射机制 将RDD转为DataFrame
        // People类必须实现序列化接口
        Dataset<Row> dataFrame = sqlContext.createDataFrame(peoples, People.class);
        // 将DataFrame注册为一个临时表;然后针对其中的数据执行SQL语句。
        dataFrame.registerTempTable("people");
        // 针对临时表执行SQL
        Dataset<Row> peopleDF = sqlContext.sql("select * from people where age>25");
        peopleDF.show();
        // 将DataFrame再次转为RDD
        JavaRDD<Row> peopleRDD = peopleDF.javaRDD();
        JavaRDD<People> res = peopleRDD.map(new Function<Row, People>() {
            @Override
            public People call(Row row) throws Exception {

                People people = new People();
                people.setAge(row.getInt(0));
                people.setName(row.getString(1));
                return people;
            }
        });
        List<People> retvalue = res.collect();
        for (People people : retvalue) {
            System.out.println(people);
        }

        /**
         *编码的方式 将RDD转为DataFrame
         */
        // 1、将 JavaRDD<String> 转为 JavaRDD<Row> 使用RowFactory.create()返回Row对象
        JavaRDD<Row> rowRDD = stringRDD.map(new Function<String, Row>() {

            @Override
            public Row call(String str) throws Exception {
                String[] arrs = str.split(",");
                String name = arrs[0];
                System.out.println(name);
                int age = Integer.valueOf(arrs[1].trim());

                return RowFactory.create(name, age);
            }
        });
        // 2、创建schema信息
        List<StructField> fields = new ArrayList<StructField>();
        StructField field = null;
        field = DataTypes.createStructField("name", DataTypes.StringType, true);
        fields.add(field);
        field = DataTypes.createStructField("age", DataTypes.IntegerType, true);
        fields.add(field);
        StructType schema = DataTypes.createStructType(fields);

        // 3 、将RDD和schema关联
        Dataset<Row> retsDF = sqlContext.createDataFrame(rowRDD, schema);
        retsDF.show();
    }
}

 

scala版本

package com.netcloud.bigdata.sparksql

import org.apache.spark.{SparkConf, SparkContext}
import org.apache.spark.sql.{Row, SparkSession}
import org.apache.spark.sql.types.{StringType, StructField, StructType}

/** scala版本的方式将RDD转为DataFrame
  * Spark SQL 支持两种不同的方法用于转换已存在的 RDD 成为 Dataset
  * 1)java 反射
  *  a)定义一个样例类;样例类定义了表的 Schema ;Case class 的参数名使用反射读取并且成为了列名
  *  b)使用toDF函数将RDD转为DataFrame
  * 2)以编程的方式指定 Schema
  *  a) 将从文本文件中创建的原始RDD使用map函数转为ROW类型的RDD。
  *  b) Step 1 被创建后,创建 Schema 表示一个 StructType(StructField(name,StringType,true),...) 匹配 RDD 中的 Rows(行)的结构。
  *  c) 通过 SparkSession 提供的 createDataFrame 方法应用 Schema 到 RDD 的 RowS(行)。
  * @author yangshaojun
  * #date  2019/3/8 17:33
  * @version 1.0
  */
object SparkSql_003_RDDtoDataFrame {
  case class Person(name:String,age:Int)
    def main(args: Array[String]): Unit = {
      val spark = SparkSession
        .builder()
        .master("local")
        .appName("SparkSql_003_DataFrameNormalOperation")
        .getOrCreate()
      import  spark.implicits._

      val rdd = spark.sparkContext.textFile("data/sparksql/people.txt")
      val peopleDF = rdd.map(_.split(",")).map(ar => Person(ar(0), ar(1).trim.toInt)).toDF()
      // 将DataFrame注册为一个view
      peopleDF.createOrReplaceTempView("people")
      val teenagersDF = spark.sql("SELECT name, age FROM people WHERE age BETWEEN 13 AND 19")
      teenagersDF.map(teenager => "Name: " + teenager(0)).show()
      // 获取某个列的值
      teenagersDF.map(teenager => "Name: " + teenager.getAs("name")).show()
      /**
        * 编码的方式将RDD转为DataFrame
        */
      val schemeString = "name age"
      // 1、使用StructType 创建schema信息
      val fields = schemeString.split(" ").map(fieldName => StructField(fieldName, StringType, nullable = true))
      val schema = StructType(fields)

      // 2、将原始的RDD[String] 转为 RDD[Row]
      val rowRDD = rdd.map(_.split(",")).map(attributes => Row(attributes(0), attributes(1).trim))
      // 3、使用createDataFrame方法将RDD和schema关联来创建DataFrame。
      val peopleDF2 = spark.createDataFrame(rowRDD, schema)
      peopleDF2.show()

    }

}

4、编程的方式加载parquet文件

object SparkSql_004_ParquetFile {
  def main(args: Array[String]): Unit = {
    val  spark=SparkSession
      .builder()
      .appName("SparkSql_004_ParquetFile")
      .master("local")
      .getOrCreate()
    // 读取parquet文件 创建一个DataFrame
    val parquetDF=spark.read.parquet("data/sparksql/people.parquet")
     // 将DataFrame注册为临时表,然后使用SQL查询需要的数据
    parquetDF.registerTempTable("people")

    // 对查询出来的DataFrame进行transform操作,处理数据,然后进行打印
    val resultDF=spark.sql("select * from people")
    resultDF.show()
    /**
      * +----+-------+
      * | age|   name|
      * +----+-------+
      * |null|Michael|
      * |  30|   Andy|
      * |  19| Justin|
      * +----+-------+
      */
    // 将DataFrame转为RDD ;然后遍历打印 name字段信息
    val result=resultDF.rdd.map(row => row(1)).collect().foreach(name => println(name))

  }
}

5、Parquet数据源自动分区推断

/**
  * parquet分区发现
  *
  * @author yangshaojun
  * #date  2019/4/8 13:15
  * @version 1.0
  */
object SparkSql_005_ParquetPartitionDiscovery {
  def main(args: Array[String]): Unit = {
    val  spark=SparkSession
      .builder()
      .appName("SparkSql_005_ParquetPartitionDiscovery")
      .master("local")
      .getOrCreate()
    // 读取parquet文件 创建一个DataFrame
    val parquetDF=spark.read.parquet("hdfs://netcloud01:9000/data/sparksql/users/gender=man/country=US/users.parquet")
    parquetDF.printSchema()
    parquetDF.show()
  }
}

6、合并 schema 

package com.netcloud.bigdata.sparksql

import org.apache.spark.sql.{SaveMode, SparkSession}

/**
  * @author yangshaojun
  * #date  2019/4/8 13:29
  * @version 1.0
  */
object SparkSql_006_ParquetMergeSchema {
  def main(args: Array[String]): Unit = {
    val  spark=SparkSession
      .builder()
      .appName("SparkSql_006_ParquetMergeSchema")
      .master("local")
      .getOrCreate()
    import spark.implicits._
    val basicData=Seq(("Andy",25),("Tome",26),("Jack",27))
    // 创建一个DataFrame 作为学生的基本信息 并写入到 parquet文件中
    val basicDF=spark.sparkContext.parallelize(basicData,2).toDF("name","age")
    basicDF.write.format("parquet").save("data/spark/sparksql/students")

    // 创建第二个DataFrame 作为学生的成绩信息 并写入到 同一个parquet文件中
    val scoreData=Seq(("ZhangSan",100),("Lisi",99),("Andy",150))
    val scoreDF=spark.sparkContext.parallelize(scoreData,2).toDF("name","score")
    scoreDF.write.mode(SaveMode.Append).format("parquet").save("data/spark/sparksql/students")

    // 首先,第一个DataFrame和第二个DataFrame的元数据肯定是不一样的
    // 一个是包含了 name和age两个列,一个包含了name 和score两个列
    // 所以这里期望的是,读取出来表的数据,自动合并两个文件的元数据,出现三个列 name 、age 、score
    // 用merageSchema的方式,读取数据进行元数据的合并。
    val mergeData= spark.sqlContext.read.option("mergeSchema","true").parquet("data/spark/sparksql/students")
    mergeData.printSchema()
    mergeData.show()
    /**
      * +--------+-----+----+
      * |    name|score| age|
      * +--------+-----+----+
      * |ZhangSan|  100|null|
      * |    Lisi|   99|null|
      * |    Andy|  150|null|
      * |    Tome| null|  26|
      * |    Jack| null|  27|
      * |    Andy| null|  25|
      * +--------+-----+----+
      */
  }
}

7、Hive数据源 

java版本

/**
 * @author yangshaojun
 * #date  2019/4/8 15:14
 * @version 1.0
 */
public class HiveDataSource {
    public static void main(String[] args) {
        SparkConf conf=new SparkConf();
        conf.setAppName("HiveDataSource");
        JavaSparkContext javaSparkContext=new JavaSparkContext(conf);
        HiveContext hiveContext=new HiveContext(javaSparkContext.sc());

        // 1、第一个功能:使用hiveContext 的sql或者hql方法;可以执行hive中能够执行的hqlQL语句

        // 判断是否存在students_info 表,如果存在就删除
        hiveContext.sql("DROP TABLE IF EXISTS  students_info");
        // 如果不存在,则创建这张表
        hiveContext.sql("CREATE TABLE IF NOT EXISTS  students_info (name STRING,age INT)");
        // 将学生基本信息导入 students_info 表中
        hiveContext.sql("LOAD DATA LOCAL INPATH '/data/sparksql/students.txt' INTO TABLE students_info");

        // 用同样的方式给 scores 导入数据
        hiveContext.sql("DROP TABLE IF EXISTS  students_score");
        // 如果不存在,则创建这张表
        hiveContext.sql("CREATE TABLE IF NOT EXISTS  students_score (name STRING,score INT)");
        // 将分数信息导入 students_score 表中
        hiveContext.sql("LOAD DATA LOCAL INPATH '/data/sparksql/scores.txt' INTO TABLE students_score");

        // 2、 第二个功能 执行sql返回一个DataFrame 用于查询。
        // 执行sql查询 关联两张表。查询出学生成绩大于80分的学生
        Dataset<Row> goodStudentDF = hiveContext.sql("select t1.name;t1.age,t2.score  " +
                "from students_info t1 join " +
                "students_score t2 on t1.name=t2.name " +
                "where score > =80 ");

        // 3、第三个功能 使用saveAsTable()方法 将DataFrame中的数据保存到hive表中。
        // 将好学生的信息保存到 goodstudent_info表中。
        hiveContext.sql("DROP TABLE IF EXISTS  goodstudent_info");
        goodStudentDF.write().saveAsTable("goodstudent_info");

        //4、第四个功能  可以使用table() 方法 针对hive表 直接创建DataFrame
        // 然后针对goodstudent_info表直接创建 DataFrame
        hiveContext.table("goodstudent_info");

    }
}

 

scala版本

/**
  * Hive 数据源
  * @author yangshaojun
  * #date  2019/4/8 15:01
  * @version 1.0
  */
object SparkSql_007_HiveDataSource {
  def main(args: Array[String]): Unit = {
    val  spark=SparkSession
      .builder()
      .appName("SparkSql_007_HiveDataSource")
      .getOrCreate()
    // 通过sparkContext 创建 HiveContext对象
    val hiveContext=new HiveContext(spark.sparkContext)

    // 1、第一个功能:使用hiveContext 的sql或者hql方法;可以执行hive中能够执行的hqlQL语句
    // 判断是否存在students_info 表,如果存在就删除
    hiveContext.sql("DROP TABLE IF EXISTS  students_info")
    // 如果不存在,则创建这张表
    hiveContext.sql("CREATE TABLE IF NOT EXISTS  students_info (name STRING,age INT)")
    // 将学生基本信息导入 students_info 表中
    hiveContext.sql("LOAD DATA LOCAL INPATH '/data/sparksql/students.txt' INTO TABLE students_info")

    // 用同样的方式给 scores 导入数据
    hiveContext.sql("DROP TABLE IF EXISTS  students_score")

    hiveContext.sql("CREATE TABLE IF NOT EXISTS  students_score (name STRING,score INT)")
    // 将分数信息导入 students_score 表中
    hiveContext.sql("LOAD DATA LOCAL INPATH '/data/sparksql/scores.txt' INTO TABLE students_score")

    // 2、 第二个功能 执行sql返回一个DataFrame 用于查询。
    // 执行sql查询 关联两张表。查询出学生成绩大于80分的学生
    val goodStudentDF = hiveContext.sql("select t1.name;t1.age,t2.score  " + "from students_info t1 join " + "students_score t2 on t1.name=t2.name " + "where score > =80 ")

    // 3、第三个功能 将DataFrame中的数据保存到hive表中。
    // 将好学生的信息保存到 goodstudent_info表中。
    hiveContext.sql("DROP TABLE IF EXISTS  goodstudent_info")
    goodStudentDF.write.saveAsTable("goodstudent_info")

    //4、第四个功能  可以使用table方法 针对hive表 直接创建DataFrame
    // 然后针对goodstudent_info表直接创建 DataFrame
    val retDF=hiveContext.table("goodstudent_info");
    for( ret <- retDF){
      println(ret)
    }
  }
}

8、JDBC 数据源 

package com.netcloud.spark.sparksql;

import org.apache.spark.SparkConf;
import org.apache.spark.api.java.JavaSparkContext;
import org.apache.spark.sql.Dataset;
import org.apache.spark.sql.Row;
import org.apache.spark.sql.SQLContext;
import org.apache.spark.sql.SparkSession;
import org.apache.spark.sql.hive.HiveContext;

import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

/**
 * java 版本 使用JDBC数据源 创建DataFrame
 * @author yangshaojun
 * #date  2019/4/8 17:22
 * @version 1.0
 */
public class JDBCDataSource {
    public static void main(String[] args) {
        SparkConf conf=new SparkConf();
        conf.setAppName("JDBCDataSource").setMaster("local");
        JavaSparkContext javaSparkContext=new JavaSparkContext(conf);
        SQLContext sqlContext=new SQLContext(javaSparkContext);

        // 方式一: 将配置文件放在Map中。
        Map<String,String> options=new HashMap<String,String>();
        options.put("url","jdbc:mysql://106.12.219.66:3306/test");
        options.put("dbtable","test");
        options.put("user","test");
        options.put("password","test");
        Dataset<Row> jdbcDF = sqlContext.read().format("jdbc").options(options).load();
        jdbcDF.show();

        // 等同于 如下的方式
        // 方式二: 将配置文件放在Properties中。
        String url = "jdbc:mysql://106.12.219.66:3306/test";
        String tabName = "test";
        Properties prop = new Properties();
        prop.setProperty("user", "test");
        prop.setProperty("password", "test");
        Dataset<Row> jdbcDFOther = sqlContext.read().jdbc(url, tabName, prop);
        jdbcDFOther.show();


    }
}
=========================================================================================
package com.netcloud.bigdata.sparksql

import java.util.Properties

import org.apache.spark.sql.SparkSession

/**
  * scala版本 JDBC 数据源
  *
  * @author yangshaojun
  * #date  2019/4/8 17:02
  * @version 1.0
  */
object SparkSql_008_JDBCDataSource {
  def main(args: Array[String]): Unit = {
    val spark = SparkSession
      .builder()
      .master("local")
      .appName("SparkSql_008_JDBCDataSource")
      .getOrCreate()

    // 读取JDBC 文件创建DataFrame 然后保存到HDFS
    val url = "jdbc:mysql://106.12.219.66:3306/test"
    val tabName = "test"
    val prop = new Properties()
    prop.setProperty("user", "test")
    prop.setProperty("password", "test")
    val jdbcDF = spark.read.jdbc(url, tabName, prop)
    jdbcDF.show()

    // 等同于下面的加载方式
    val jdbcDFother =spark.read.format("jdbc")
      .options(Map(
        "url" -> "jdbc:mysql://106.12.219.66:3306/test",
        "dbtable" -> "test",
        "user" -> "test",
        "password" -> "test"
      )).load()
    jdbcDFother.select("username").show()

  }

}

10、SparkSQL内置函数

package com.netcloud.bigdata.sparksql

import org.apache.spark.sql.types._
import org.apache.spark.sql.{Row, SparkSession}
import org.apache.spark.sql.functions._
/**
  * 根据用户每天的访问和购买日志,统计每天的uv和销售额。
  * @author yangshaojun
  * #date  2019/4/8 17:57
  * @version 1.0
  */
object SparkSql_009_DailyUV {
  def main(args: Array[String]): Unit = {
    val spark = SparkSession
      .builder()
      .master("local")
      .appName("SparkSql_009_DailyUV")
      .getOrCreate()

    // 要使用SparkSQL的内置函数,就必须导入SQLContext的隐式转换
    import spark.sqlContext.implicits._

    // 构造用户访问日志 并创建DataFrame
    // 日志用逗号隔开 第一列 日期;第二列 用户id ;第三列 销售额 sale
    val userAccessLog=Array(
      "2019-10-01,1122,23.15",
      "2019-10-01,1123,34.5",
      "2019-10-01,1122,45.6",
      "2019-10-02,1122,44,5",
      "2019-10-02,1122,56,5",
      "2019-10-01,1122,66.6",
      "2019-10-02,1123,12,4",
      "2019-10-01,1123,77.6",
      "2019-10-02,1124,23.8"
    )
    // 将模拟出来的用户访问日志RDD 转为DataFrame
    // 首先将普通的RDD 转为元素为Row的RDD
    val userAccessRDD=spark.sparkContext.parallelize(userAccessLog,2)
    val userAccessRowRDD=userAccessRDD.map(log => Row(log.split(",")(0),log.split(",")(1).toInt))

    // 构造DataFrame的元数据
    val structType=StructType(Array(StructField("date",StringType,true),StructField("userid",IntegerType,true)))
   val  userAccessRowDF=spark.createDataFrame(userAccessRowRDD,structType);

    // 接下来使用SparkSQL中的内置函数
    /**
      * 内置函数的用法
      * 1)对DataFrame调用groupBy() 对某一列进行分组
      * 2)然后调用 agg()方法 第一个参数是必须的 即:传入之前在groupBy()方法中出现的字段
      * 3)第二个参数 传入countDistinct 、sum、first等spark内置的函数。
      * 内置函数中,传入的参数,也是用单引号作为前缀的。
      */
    userAccessRowDF.groupBy("date").agg('date, countDistinct('userid)).rdd
      .map(row => Row(row(1), row(2)))
      .collect()
      .foreach(print)


    /**
      * 统计每天的销售额
      */
    val userAccessRowSaleRDD=userAccessRDD.map(log => Row(log.split(",")(0),log.split(",")(2).toDouble))
    // 构造DataFrame的元数据
    val schema=StructType(Array(StructField("date",StringType,true),StructField("sale",DoubleType,true)))
    val  userAccessRowSaleDF=spark.createDataFrame(userAccessRowSaleRDD,schema);

    // 接下来使用SparkSQL中的内置函数
    /**
      * 内置函数的用法
      * 1)对DataFrame调用groupBy() 对某一列进行分组
      * 2)然后调用 agg()方法 第一个参数是必须的 即:传入之前在groupBy()方法中出现的字段
      * 3)第二个参数 传入countDistinct 、sum、first等spark内置的函数。
      * 内置函数中,传入的参数,也是用单引号作为前缀的。
      */
    userAccessRowSaleDF.groupBy("date").agg('date, sum('sale)).rdd
      .map(row => Row(row(1), row(2)))
      .collect()
      .foreach(print)
  }
}

开窗函数:

package com.netcloud.spark.sparksql;

import org.apache.spark.SparkConf;
import org.apache.spark.api.java.JavaSparkContext;
import org.apache.spark.sql.hive.HiveContext;

/**
 *
 * @author yangshaojun
 * #date  2019/4/8 21:38
 * @version 1.0
 */
public class RowNumberWindowFunction {
    public static void main(String[] args) {

        SparkConf conf = new SparkConf().setAppName("RowNumberWindowFunction");
        JavaSparkContext javaSparkContext = new JavaSparkContext(conf);
        HiveContext hiveContext = new HiveContext(javaSparkContext.sc());
        // 删除销售额表, sales表
        hiveContext.sql("DROP TABLE IF EXISTS sales");
        hiveContext.sql("CREATE TABLE IF NOT EXISTS sales (product String,category String,revenue BIGINT )");
        hiveContext.sql("LOAD DATA LOCAL INPAT '/usr/local/sales.txt INTO TABLE sales'");

        // 开始编写统计逻辑:使用 row_number() 开窗函数
        // row_number()函数的作用: 给每个分组的数据,按照其顺序排序,然后打上一个分组的行号
        hiveContext.sql("SELECT product,category,revenue"
                + "FROM ("
                    + "SELECT "
                        + "product,"
                        + "category,"
                        + "revenue,"
                        + "row_number() OVER (PARTITION BY category ORDER BY revenue DESC) rank"
                +")"
                + " WHERE rank<=3");

    }
}
======================================================================================
package com.netcloud.bigdata.sparksql


import org.apache.spark.sql.SparkSession
import org.apache.spark.sql.hive.HiveContext

/**
  * @author yangshaojun
  * #date  2019/4/8 21:39
  * @version 1.0
  */
object SparkSql_010_RowNumberWindowFunction {
  def main(args: Array[String]): Unit = {
    val spark = SparkSession
      .builder()
      .master("local")
      .appName("SparkSql_010_RowNumberWindowFunction")
      .getOrCreate()

    val hiveContext = new HiveContext(spark.sparkContext);
    // 删除销售额表, sales表
    hiveContext.sql("DROP TABLE IF EXISTS sales")
    hiveContext.sql("CREATE TABLE IF NOT EXISTS sales (product String,category String,revenue BIGINT )")
    hiveContext.sql("LOAD DATA LOCAL INPAT '/usr/local/sales.txt INTO TABLE sales'")

    // 开始编写统计逻辑:使用 row_number() 开窗函数
    // row_number()函数的作用: 给每个分组的数据,按照其顺序排序,然后打上一个分组的行号
    hiveContext.sql("SELECT product,category,revenue" +
      "FROM ("
      + "SELECT "
      + "product,"
      + "category,"
      + "revenue,"
      + "row_number() OVER (PARTITION BY category ORDER BY revenue DESC) rank"
      + ")"
      + " WHERE rank<=3").w


  }

}

11、SparkSQL UDF 自定义函数的使用

package com.netcloud.bigdata.sparksql

import org.apache.spark.sql.types.{StringType, StructField, StructType}
import org.apache.spark.sql.{Row, SparkSession}

/**
  * 用户自定义函数
  * @author yangshaojun
  * #date  2019/4/9 16:50
  * @version 1.0
  */
object SparkSql_012_UDF {
  def main(args: Array[String]): Unit = {
    val spark = SparkSession
      .builder()
      .master("local")
      .appName("SparkSql_012_UDF")
      .getOrCreate()
    // 构造模拟数据
    val arr=Array("Tom","Jerry","PeiQi","Andy")
    val strRDD=spark.sparkContext.parallelize(arr,2)
    // 将 RDD(String) 转为 RDD(Row)
    val rowRDD=strRDD.map(name => Row(name))

    // 动态转换的方式创建 schema 将RDD转为 DataFrame
    val schema=StructType(Array(StructField("name",StringType,true)))
    // 将RowRDD 转为DataFrame 然后将其注册到临时表中
    spark.createDataFrame(rowRDD,schema).registerTempTable("names")

    // 定义和注册自定义函数
    // 定义函数: 自己写匿名函数
    // 注册函数: SQLContext.udf.register()
    spark.udf.register("strLen",(str:String) => str.length)

    // 使用自定义函数
    spark.sql("select name , strLen(name) from names")
      .collect()
      .foreach(println)

  }

}

12、SparkSQL UDAF 自定义聚合函数的使用

package com.netcloud.bigdata.sparksql

import org.apache.spark.sql.{Row, SparkSession}
import org.apache.spark.sql.expressions.{MutableAggregationBuffer, UserDefinedAggregateFunction}
import org.apache.spark.sql.types._

/**
  * 用户自定义聚合函数
  *
  * @author yangshaojun
  * #date  2019/4/9 17:21
  * @version 1.0
  */
object SparkSql_013_UDAFStringCount {
  def main(args: Array[String]): Unit = {
    val spark = SparkSession
      .builder()
      .master("local")
      .appName("SparkSql_012_UDF")
      .getOrCreate()
    // 构造模拟数据
    val arr=Array("Tom","Jerry","PeiQi","Andy","Tom","Andy","Tom")
    val strRDD=spark.sparkContext.parallelize(arr,2)
    // 将 RDD(String) 转为 RDD(Row)
    val rowRDD=strRDD.map(name => Row(name))

    // 动态转换的方式创建 schema 将RDD转为 DataFrame
    val schema=StructType(Array(StructField("name",StringType,true)))
    // 将RowRDD 转为DataFrame 然后将其注册到临时表中
    spark.createDataFrame(rowRDD,schema).registerTempTable("names")

    // 注册自定义聚合函数
    // 参数一:自定义聚合函数名称
    // 参数二: 继承UserDefinedAggregateFunction类的子类实例
    spark.udf.register("strCount",new StringCount)

    // 使用自定义函数
    spark.sql("select name,strCount(name)  from names group by name")
      .collect()
      .foreach(println)

    spark.stop()
  }

}

// 用户自定义聚合函数
class StringCount extends UserDefinedAggregateFunction {

  // inputSchema 指的是 输入数据的类型
  override def inputSchema: StructType = {
    StructType(Array(StructField("str", StringType, true)))
  }

  // bufferSchema 指的是 中间进行聚合时 所处理的数据类型
  override def bufferSchema: StructType = {
    StructType(Array(StructField("count", IntegerType, true)))
  }

  // dataType 指的是 函数返回值的数据类型
  override def dataType: DataType = {
    IntegerType
  }

  override def deterministic: Boolean = {
    true
  }

  // 为每个分组的数据进行初始化操作
  override def initialize(buffer: MutableAggregationBuffer): Unit = {
    buffer(0) = 0
  }

  // 指的是 每个分组有新的值进来的时候,如何进行分组对应的聚合值的计算
  override def update(buffer: MutableAggregationBuffer, input: Row): Unit = {
    buffer(0) = buffer.getAs[Int](0) + 1
  }

  // 由于spark是分布式的,所以一个分组的数据,可能在不同的节点上进行局部的聚合
  // 但是最后一个分组 在各个节点上的聚合值要进行Merage 也就是合并。
  override def merge(buffer1: MutableAggregationBuffer, buffer2: Row): Unit = {
    buffer1(0) = buffer1.getAs[Int](0) + buffer2.getAs[Int](0)
  }

  // 最后 一个分组的聚合值,如何通过中间的缓存聚合值,最后返回一个最终的聚合值
  override def evaluate(buffer: Row): Any = {
    buffer.getAs[Int](0)
  }
}

13、SparkSQL实战

package com.netcloud.spark.sparksql;

import com.netcloud.spark.utils.PropertiesUtil;
import org.apache.spark.SparkConf;
import org.apache.spark.api.java.JavaPairRDD;
import org.apache.spark.api.java.JavaRDD;
import org.apache.spark.api.java.JavaSparkContext;
import org.apache.spark.api.java.function.Function;
import org.apache.spark.api.java.function.PairFunction;
import org.apache.spark.broadcast.Broadcast;
import org.apache.spark.sql.Dataset;
import org.apache.spark.sql.Row;
import org.apache.spark.sql.RowFactory;
import org.apache.spark.sql.SQLContext;
import org.apache.spark.sql.types.*;
import scala.Tuple2;

import java.util.*;

/**
 * @author yangshaojun
 * #date  2019/4/10 13:32
 * @version 1.0
 */
public class ProjectPractice {
    public static void main(String[] args) {
        SparkConf conf =new SparkConf().setAppName("ProjectPractice");
        PropertiesUtil p = new PropertiesUtil("config.properties");
        String isLocal = p.readProperty("isLocal");

        JavaSparkContext sc=null;
        JavaRDD<String> rawRDD=null;

        // 判断是否在本地运行
        if(isLocal!=null && isLocal.equals("true")){
            conf.setMaster("local");
            sc=new JavaSparkContext(conf);
            rawRDD= sc.textFile("data/sparksql/keyword.txt");
        }else{
            sc=new JavaSparkContext(conf); 针对hdfs上的日志,创建一个RDD
            rawRDD= sc.textFile("hdfs://netcloud01:9000/data/sparksql/keyword.txt");

        }

        // 伪造一份数据 作为查询条件
        // 在实际的项目开发中,很可能这个查询条件是从J2EE平台插入到某个Mysql表中的
        // 然后呢 通常会用Spring框架和orm框架(mybatis) 去提取Mysql表的查询条件。
        Map<String,List<String>> queryParamMap=new HashMap<String,List<String>>();
        queryParamMap.put("city",Arrays.asList("beijing"));
        queryParamMap.put("platform",Arrays.asList("android"));
        queryParamMap.put("version",Arrays.asList("1.0","1.2","1.5","2.0"));

        // 根据实现思路的分析,这里最适合的方式就是,将查询参数map封装为一个broadcast广播变量
        // 这样可以进行优化,每个work节点,就拷贝一份 而不是每个task副本只有一份
        Broadcast<Map<String,List<String>>> queryParamMapBroadcast=sc.broadcast(queryParamMap);

        // 数据筛选 使用查询参数广播变量,进行筛选
        JavaRDD<String> filterRDD = rawRDD.filter(new Function<String, Boolean>() {
            @Override
            public Boolean call(String lineLog) throws Exception {

               String[] logSplited= lineLog.split("\t");//将每行的日志信息 按照制表符分割
                String city=logSplited[3];
                String platform=logSplited[4];
                String version=logSplited[5];
                // 与查询条件进行比对
                Map<String, List<String>> queryParamMap = queryParamMapBroadcast.value();//获取广播变量的值
                List<String> cities = queryParamMap.get("city");

                if(cities.size()>0 && ! cities.contains(city)){
                 return false;
                }
                List<String> platforms = queryParamMap.get("platform");
                if(platforms.size()>0 && ! platforms.contains(platform)){
                    return false;
                }
                List<String> versions = queryParamMap.get("version");
                if(versions.size()>0 && ! versions.contains(version)){
                    return false;
                }

                return true;
            }
        });

        // 将原始过滤出来的RDD 映射为 (日期_搜索词,用户)格式
        JavaPairRDD<String, String> datekeyWordUserRDD = filterRDD.mapToPair(new PairFunction<String, String, String>() {

            @Override
            public Tuple2<String, String> call(String lineLog) throws Exception {
                String[] splits = lineLog.split("\t");
                String date = splits[0];
                String user = splits[1];
                String keyWord = splits[2];
                return new Tuple2<String, String>(date + "_" + keyWord, user);
            }
        });

        // 进行分组,获取每天每个搜索词,由哪些用户搜索了(没有去重)
        JavaPairRDD<String, Iterable<String>> datekeyWordUsersRDD = datekeyWordUserRDD.groupByKey();
        // 对每天每个搜索词的搜索用户,执行去重操作,获得uv
        JavaPairRDD<String, Long> dateKeyWordUVRDD = datekeyWordUsersRDD.mapToPair(new PairFunction<Tuple2<String, Iterable<String>>, String, Long>() {

            @Override
            public Tuple2<String, Long> call(Tuple2<String, Iterable<String>> dateKeyWordsUsers) throws Exception {

                String dateKeyWord=dateKeyWordsUsers._1;
                Iterator<String> users = dateKeyWordsUsers._2.iterator();
                // 对用户进行去重,并统计去重后的数据
                List<String> distinctUsers=new ArrayList<String>();
                while(users.hasNext()){
                    String user=users.next();
                    if(!distinctUsers.contains(user)){
                        distinctUsers.add(user);
                    }
                }
                // 获取去重后的uv
                long uv=distinctUsers.size();
                return new Tuple2<String,Long>(dateKeyWord,uv);
            }
        });

        // 将每天每个搜索词的uv,转为DataFrame
        SQLContext sqlContext=new SQLContext(sc);
        JavaRDD<Row> madateKeyWordUVRowRDD= dateKeyWordUVRDD.map(new Function<Tuple2<String, Long>, Row>() {

            @Override
            public Row call(Tuple2<String, Long> dataKeyWordUv) throws Exception {
                String date = dataKeyWordUv._1.trim().split("_")[0];
                String keyword = dataKeyWordUv._1.trim().split("_")[1];
                long uv = dataKeyWordUv._2;
                return RowFactory.create(date, keyword, uv);
            }
        });

        List<StructField> structFields=Arrays.asList(
                DataTypes.createStructField("date",DataTypes.StringType,true),
                DataTypes.createStructField("keyword",DataTypes.StringType,true),
                DataTypes.createStructField("uv",DataTypes.LongType,true));
        StructType schema=DataTypes.createStructType(structFields);
        Dataset<Row> datekeyWordUvDF = sqlContext.createDataFrame(madateKeyWordUVRowRDD, schema);

        // 使用sparkSQL的开窗函数 统计每天搜索uv排名前3的热点搜索词
        datekeyWordUvDF.registerTempTable("daily_keyword_uv");
        Dataset<Row> dailyTop3KeyWordDF = sqlContext.sql("select date,keyword,uv " +
                "from (" +
                "select date,keyword,uv,row_number() OVER (PARTITION BY date ORDER BY uv DESC)  rank  " +
                "       from daily_keyword_uv " +
                ") " +
                "tmp  where rank<=3");

        dailyTop3KeyWordDF.show();
        sc.stop();
    }
}