SparkSQL-02

一、SparkSQL读写数据的方式

复习:
如何读取json?   df = spark.read.json("json文件")  --> 读取到之后直接就有表结构了
读取csv ?       df = spark.read.csv("csv文件")  --> 表结构只有一列,叫做value

SparkSQL将所有常用的数据源接口都进行了封装,只需要指定读写的类型和地址就可以实现读写。

1)输入Source

  • 类型:text / csv【任意固定分隔符】 / json / orc / parquet / jdbc / table【Hive表】
  • 语法:spark.read.format(格式).load(读取的地址)

方式一:给定读取数据源的类型和地址

spark.read.format("json").load(path)
spark.read.format("csv").load(path)
spark.read.format("parquet").load(path)

方式二:直接调用对应数据源类型的方法

spark.read.json(path)
spark.read.csv(path)
spark.read.parquet(path)

特殊参数:option,用于指定读取时的一些配置选项

spark.read.format("csv").option("sep", "\t").load(path)

jdbcDF = spark.read \
    .format("jdbc") \
    .option("url", "jdbc:postgresql:dbserver") \
    .option("dbtable", "schema.tablename") \
    .option("user", "username") \
    .option("password", "password") \
    .load()

1、代码演示最普通的文件读取方式:

from pyspark.sql import SparkSession

"""
------------------------------------------
  Description : TODO:
  SourceFile : _02_SparkSQLReadDemo
  Author  : 闫哥
  Date  : 2025/3/21 星期五
-------------------------------------------
"""
if __name__ == '__main__':
    with SparkSession.builder.master("local[2]").appName("").config(
        "spark.sql.shuffle.partitions", 2).getOrCreate() as spark:

        # 读取数据,我们以前学的是sparkCore
        spark.read.json("../../datas/sparksql/person.json").show()
        spark.read.format("json").load("../../datas/sparksql/person.json").show()

        spark.read.csv("../../datas/emp/dept.csv").show()
        spark.read.format("csv").option("sep","\t").load("../../datas/emp/emp.tsv").show()

        spark.read.load(
            "../../datas/emp/emp.tsv",
            format="csv",
            sep="\t"
        ).show()

        

2) 通过jdbc读取数据库数据

先在本地数据库或者linux数据库中插入一张表:

CREATE TABLE `emp`  (
  `empno` int(11) NULL DEFAULT NULL,
  `ename` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `job` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `mgr` int(11) NULL DEFAULT NULL,
  `hiredate` date NULL DEFAULT NULL,
  `sal` decimal(7, 2) NULL DEFAULT NULL,
  `comm` decimal(7, 2) NULL DEFAULT NULL,
  `deptno` int(11) NULL DEFAULT NULL
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of emp
-- ----------------------------
INSERT INTO `emp` VALUES (7369, 'SMITH', 'CLERK', 7902, '1980-12-17', 800.00, NULL, 20);
INSERT INTO `emp` VALUES (7499, 'ALLEN', 'SALESMAN', 7698, '1981-02-20', 1600.00, 300.00, 30);
INSERT INTO `emp` VALUES (7521, 'WARD', 'SALESMAN', 7698, '1981-02-22', 1250.00, 500.00, 30);
INSERT INTO `emp` VALUES (7566, 'JONES', 'MANAGER', 7839, '1981-04-02', 2975.00, NULL, 20);
INSERT INTO `emp` VALUES (7654, 'MARTIN', 'SALESMAN', 7698, '1981-09-28', 1250.00, 1400.00, 30);
INSERT INTO `emp` VALUES (7698, 'BLAKE', 'MANAGER', 7839, '1981-05-01', 2850.00, NULL, 30);
INSERT INTO `emp` VALUES (7782, 'CLARK', 'MANAGER', 7839, '1981-06-09', 2450.00, NULL, 10);
INSERT INTO `emp` VALUES (7788, 'SCOTT', 'ANALYST', 7566, '1987-04-19', 3000.00, NULL, 20);
INSERT INTO `emp` VALUES (7839, 'KING', 'PRESIDENT', NULL, '1981-11-17', 5000.00, NULL, 10);
INSERT INTO `emp` VALUES (7844, 'TURNER', 'SALESMAN', 7698, '1981-09-08', 1500.00, 0.00, 30);
INSERT INTO `emp` VALUES (7876, 'ADAMS', 'CLERK', 7788, '1987-05-23', 1100.00, NULL, 20);
INSERT INTO `emp` VALUES (7900, 'JAMES', 'CLERK', 7698, '1981-12-03', 950.00, NULL, 30);
INSERT INTO `emp` VALUES (7902, 'FORD', 'ANALYST', 7566, '1981-12-03', 3000.00, NULL, 20);
INSERT INTO `emp` VALUES (7934, 'MILLER', 'CLERK', 7782, '1982-01-23', 1300.00, NULL, 10);

dept的数据:

CREATE TABLE `dept`  (
  `deptno` int(11) NULL DEFAULT NULL,
  `dname` varchar(14) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `loc` varchar(13) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of dept
-- ----------------------------
INSERT INTO `dept` VALUES (10, 'ACCOUNTING', 'NEW YORK');
INSERT INTO `dept` VALUES (20, 'RESEARCH', 'DALLAS');
INSERT INTO `dept` VALUES (30, 'SALES', 'CHICAGO');
INSERT INTO `dept` VALUES (40, 'OPERATIONS', 'BOSTON');

接着放驱动程序:

py4j.protocol.Py4JJavaError: An error occurred while calling o67.load.
: java.lang.ClassNotFoundException: com.mysql.cj.jdbc.Driver
	at java.net.URLClassLoader.findClass(URLClassLoader.java:382)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:418)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:351)
	at org.apache.spark.sql.execution.datasources.jdbc.DriverRegistry$.register(DriverRegistry.scala:46)
	at org.apache.spark.sql.execution.datasources.jdbc.JDBCOptions.$anonfun$driverClass$1(JDBCOptions.scala:102)
	at org.apache.spark.sql.execution.datasources.jdbc.JDBCOptions.$anonfun$driverClass$1$adapted(JDBCOptions.scala:102)

Python环境放入MySQL连接驱动

  • 找到工程中pyspark库包所在的环境,将驱动包放入环境所在的jars目录中
  • 如果是Linux上:注意集群模式所有节点都要放。

第一种情况:

假如你是windows环境:

我的最终的路径是在这里:

第二种情况:linux环境下,按照如下方式进行

# 进入目录
cd /opt/installs/anaconda3/lib/python3.8/site-packages/pyspark/jars

# 上传jar包:mysql-connector-java-5.1.32.jar

随堂代码练习:

spark.read.format("jdbc") \
            .option("url", "jdbc:mysql://localhost:3306/edu") \
            .option("dbtable", "emp") \
            .option("user", "root") \
            .option("password", "123456") \
        .load() .show()

3) 读取table中的数据【hive】

假如领导给了一个海量数据,让你处理?两种思路

海量数据:存储在hdfs上

第一种:

使用spark读取hdfs上的数据(可以使用sparkCore读取,也可以使用sparksql读取),将数据变为表【数据+Schema】,然后编写sql或者sparkCore代码。

rdd --> dataFrame

第二种:推荐

将hdfs上的数据映射成hive的表,然后通过sparkSql连接hive, 编写 sql 处理需求。

  • 场景:Hive底层默认是MR引擎,计算性能特别差,一般用Hive作为数据仓库,使用SparkSQL对Hive中的数据进行计算
    • 存储:数据仓库:Hive:将HDFS文件映射成表
    • 计算:计算引擎:SparkSQL、Impala、Presto:对Hive中的数据表进行处理
  • 问题:SparkSQL怎么能访问到Hive中有哪些表,以及如何知道Hive中表对应的HDFS的地址?

Hive中的表存在哪里?元数据--MySQL , 启动metastore服务即可。

本质上:SparkSQL访问了Metastore服务获取了Hive元数据,基于元数据提供的地址进行计算

先退出base环境:conda deactivate
启动服务:
启动hdfs:  start-dfs.sh  因为hive的数据在那里存储着
启动yarn:  start-yarn.sh 因为spark是根据yarn部署的,假如你的spark是standalone模式,不需要启动yarn.
日志服务也需要启动一下:
mapred --daemon start historyserver
# 启动Spark的HistoryServer:18080
/opt/installs/spark/sbin/start-history-server.sh
启动metastore服务: 因为sparkSQL需要知道表结构,和表数据的位置
hive-server-manager.sh start metastore
启动spark服务: 啥服务也没有了,已经启动完了。
查看metastore服务:
hive-server-manager.sh status metastore

修改配置:

是进入到spark目录,不要进hive目录
cd /opt/installs/spark/conf
新增:hive-site.xml
vi hive-site.xml

在这个文件中,编写如下配置:
<?xml version="1.0"?>
<?xml-stylesheet type="text/xsl" href="configuration.xsl"?>
<configuration>
    <property>
        <name>hive.metastore.uris</name>
        <value>thrift://caijing:9083</value>
    </property>
</configuration>

接着将该文件进行分发:
xsync.sh hive-site.xml

操作sparkSQL:

/opt/installs/spark/bin/pyspark --master local[2] --conf spark.sql.shuffle.partitions=2

此处的pyspark更像是一个客户端,里面可以通过python编写spark代码而已。而我们以前安装的pyspark更像是spark的python运行环境。

进入后,通过内置对象spark:

>>> spark.sql("show databases").show()
+---------+
|namespace|
+---------+
|caijing01|
|caijing02|
|  default|
|  finance|
+---------+


>>> spark.sql("select * from caijing01.t_user").show()
+---+----------+      
| id|      name|
+---+----------+
|  1|      宝总|
|  2|      李李|
|  3|      小汪|
|  4|      爷叔|
| 10|  zhangfei|
| 11|   zhaoyun|
| 12|zhugeliang|
|  1|      宝总|
|  2|      李李|
|  3|      小汪|
|  4|      爷叔|
+---+----------+

开发环境如何编写代码,操作hive:

Pycharm工具集成Hive开发SparkSQL,必须申明Metastore的地址和启用Hive的支持

spark = SparkSession \
        .builder \
        .appName("HiveAPP") \
        .master("local[2]") \
        .config("spark.sql.warehouse.dir", 'hdfs://bigdata01:9820/user/hive/warehouse') \
        .config('hive.metastore.uris', 'thrift://bigdata01:9083') \
        .config("spark.sql.shuffle.partitions", 2) \
        .enableHiveSupport()\
        .getOrCreate()

代码实战:

from pyspark.sql import SparkSession

"""
------------------------------------------
  Description : TODO:
  SourceFile : _03_sparksql操作hive
  Author  : 闫哥
  Date  : 2025/3/21 星期五
-------------------------------------------
"""
if __name__ == '__main__':
    spark = SparkSession \
        .builder \
        .appName("sparksql操作hive") \
        .master("local[2]") \
        .config("spark.sql.warehouse.dir", 'hdfs://caijing:9820/user/hive/warehouse') \
        .config('hive.metastore.uris', 'thrift://caijing:9083') \
        .config("spark.sql.shuffle.partitions", 2) \
        .enableHiveSupport() \
        .getOrCreate()

    spark.sql("""
        select * from caijing01.t_user
    """).show()

    spark.stop()

代码还可以这样写:

方式二:加载Hive表的数据变成DF,可以调用DSL或者SQL的方式来实现计算

# 读取Hive表构建DataFrame

hiveData = spark.read.table("yhdb.student")

hiveData.printSchema()

hiveData.show()

# 读取hive表中的数据
	spark2 = SparkSession \
		.builder \
		.appName("HiveAPP") \
		.master("local[2]") \
		.config("spark.sql.warehouse.dir", 'hdfs://192.168.233.128:9820/user/hive/warehouse') \
		.config('hive.metastore.uris', 'thrift://192.168.233.128:9083') \
		.config("spark.sql.shuffle.partitions", 2) \
		.enableHiveSupport() \
		.getOrCreate()

	#spark2.sql("show databases").show()
	#spark2.sql("show  tables").show()

	#spark2.sql("select * from yhdb.t_user").show()

	spark2.read.table("t_user2").show()

不要在一个python 文件中,创建两个不同的sparkSession对象,否则对于sparksql获取hive的元数据,有影响。另外,记得添加一个权限校验的语句:

# 防止在本地操作hdfs的时候,出现权限问题
os.environ['HADOOP_USER_NAME'] = 'root'

为什么有些平台不支持,不兼容 sqoop flume datax 这些工具呢?

spark 可以读取日志数据

spark 可以读取数据库数据

spark 可以读取 hdfs 数据

spark 可以读取 hive 数据

------------------------------------

spark 可以读取日志数据,形成一个 A 表,读取 mysql 数据,形成一个 B 表

A 表和 B 表还可以相互关联,此时也就不需要 sqoop、flume、datax 去导入导出了。

spark 还可以将统计出来的结果直接放入 mysql 或者直接放入 hive

--------------------

我们后面学习的内容还是沿用 将日志数据,数据库数据等所有数据抽取到 hive ,然后呢,使用 spark 去统计,统计完之后还是放入 hive ,使用 datax 等工具将结果导出 mysql。

2)输出Sink

sink --> 下沉 --> 落盘 --> 保存起来

如果输出路径或者表已经存在了怎么办

  • 类型:text /csv【所有具有固定分隔符的文件】/ json/ orc/ parquet / jdbc / table【Hive表】
  • 语法:DataFrame.write.format(保存的类型).save(保存到哪)
    • 方法:save-保存到文件save(path)或者数据库表save()中,saveAsTable-用于保存到Hive表

方式一:给定输出数据源的类型和地址

df.write.format("json").save(path)
df.write.format("csv").save(path)
df.write.format("parquet").save(path)

方式二:直接调用对应数据源类型的方法

df.write.json(path)
df.write.csv(path)
df.write.parquet(path)

特殊参数:option,用于指定输出时的一些配置选项

df.write \
.format("jdbc") \
.option("url", "jdbc:postgresql:dbserver") \
.option("dbtable", "schema.tablename") \
.option("user", "username") \
.option("password", "password") \
.save()

输出模式:Save Mode

append: 追加模式,当数据存在时,继续追加
overwrite: 覆写模式,当数据存在时,覆写以前数据,存储当前最新数据;
error/errorifexists: 如果目标存在就报错,默认的模式
ignore: 忽略,数据存在时不做任何操作

代码如何编写:

df.write.mode(saveMode="append").format("csv").save(path)

实战一:保存普通格式:

from pyspark.sql import SparkSession

"""
------------------------------------------
  Description : TODO:
  SourceFile : _04_sparksql_sink
  Author  : 闫哥
  Date  : 2025/3/21 星期五
-------------------------------------------
"""
if __name__ == '__main__':
    spark = SparkSession.builder.master("local[2]").appName("").config(
        "spark.sql.shuffle.partitions", 2).getOrCreate()

    df = spark.read.json("../../datas/sparksql/person.json")
    df.show()
    df.createOrReplaceTempView("person")
    # # 获取年龄最大的人的名字
    maxAgeDf = spark.sql("""
      select * from person where age = (select max(age) from person)
    """)
    maxAgeDf.show()
    # 将数据写入文件中
    maxAgeDf.write.csv("../../datas/sparksql/maxAge",mode="overwrite")
    maxAgeDf.write.mode("overwrite").format("csv").save("../../datas/sparksql/maxAge1")

    # text 保存路径为hdfs 直接报错,不支持,需要将结果拼接为一个长的字符串,text格式,只支持一列,并且这一列必须是string类型的
    #maxAgeDf.write.mode(saveMode='overwrite').text("hdfs://caijing:9820/result")
    maxAgeDf.write.orc("hdfs://caijing:9820/result",mode="overwrite")
    # maxAgeDf.write.parquet("hdfs://bigdata01:9820/result", mode="overwrite")


    spark.stop()
假如:
spark.sql("select concat(name,' ',age) from person").write.text("hdfs://caijing:9820/spark/result")
直接报错:假如你的输出类型是text类型,直接报错
pyspark.sql.utils.AnalysisException: Text data source does not support bigint data type.
假如修改为parquet等类型,是可以直接保存的:
rsDf.write.parquet("hdfs://caijing:9820/result")  

假如你将上面的sql语句,变为如下格式,就可以保存啦:
spark.sql("select concat(name,' ',cast(age as string)) from person").write.text("hdfs://caijing:9820/spark/result")         

实战二:保存到数据库中:

代码演示:

from pyspark.sql import SparkSession
from pyspark.sql.types import StructType, StructField, IntegerType, StringType, DoubleType

"""
------------------------------------------
  Description : TODO:
  SourceFile : _04_sparksql_sink
  Author  : 闫哥
  Date  : 2025/3/21 星期五
-------------------------------------------
"""
if __name__ == '__main__':
    spark = SparkSession.builder.master("local[2]").appName("").config(
        "spark.sql.shuffle.partitions", 2).getOrCreate()

    emp_schema = StructType([
        StructField("empno", IntegerType(), True),
        StructField("ename", StringType(), True),
        StructField("salary", DoubleType(), True),
        StructField("jiangjin", DoubleType(), True),
        StructField("deptno", IntegerType(), True),
    ])
    df = spark.read.format("csv").option("sep","\t").load("../../datas/emp/emp.tsv",schema=emp_schema)


    df.write.format("jdbc") \
        .option("url", "jdbc:mysql://localhost:3306/edu") \
        .option("dbtable", "emp2") \
        .option("user", "root") \
        .option("password", "123456") \
        .mode("overwrite") \
        .save()


    spark.stop()

CSDN:Spark中给读取到的数据 的列 重命名的几种方式!_spark withcolumnrenamed-CSDN博客

实战三:将结果保存在hive表中

from pyspark.sql import SparkSession
from pyspark.sql.types import StructType, StructField, IntegerType, StringType, DoubleType

"""
------------------------------------------
  Description : TODO:
  SourceFile : _03_sparksql操作hive
  Author  : 闫哥
  Date  : 2025/3/21 星期五
-------------------------------------------
"""
if __name__ == '__main__':
    spark = SparkSession \
        .builder \
        .appName("sparksql操作hive") \
        .master("local[2]") \
        .config("spark.sql.warehouse.dir", 'hdfs://caijing:9820/user/hive/warehouse') \
        .config('hive.metastore.uris', 'thrift://caijing:9083') \
        .config("spark.sql.shuffle.partitions", 2) \
        .enableHiveSupport() \
        .getOrCreate()

    emp_schema = StructType([
        StructField("empno", IntegerType(), True),
        StructField("ename", StringType(), True),
        StructField("salary", DoubleType(), True),
        StructField("jiangjin", DoubleType(), True),
        StructField("deptno", IntegerType(), True),
    ])
    df = spark.read.format("csv").option("sep", "\t").load("../../datas/emp/emp.tsv", schema=emp_schema)

    # 将奖金为null数据过滤掉,留下奖金不为null的数据
    df.createOrReplaceTempView("t_emp")
    rsDf = spark.sql("""
        select * from t_emp where cast(jiangjin as int) !=0 
    """)

    rsDf.write.saveAsTable("caijing01.emp2",mode="overwrite")

    spark.stop()

二:SparkSQL的UDF

- UDF:一对一的函数【User Defined Functions】
  - substr、split、concat、instr、length、from_unixtime
- UDAF:多对一的函数【User Defined Aggregation Functions】 聚合函数
  - count、sum、max、min、avg、collect_set/list
- UDTF:一对多的函数【User Defined Tabular Functions】
  - explode、json_tuple【解析JSON格式】、parse_url_tuple【解析URL函数】

Spark中支持UDF和UDAF两种,支持直接使用Hive中的UDF、UDAF、UDTF.

pyspark中自定义函数的三种写法:

假如别人问:你都自定义过哪些函数?

- 解析IP地址获取用户所在的国家、省份、城市
  def transIp2Region(ip: str) -> [国家,省份,城市]
- 解析GPS地址获取用户所在的国家、省份、城市
  def transGPS2Region(lng, lat) -> [国家,省份,城市]
- 对原始数据进行解密:原始数据是加密的
  def fieldDecode(col) -> decode_col
- 根据身份证号判断年龄以及性别

如何在sparkSQL中自定义函数呢?

使用最常用的regeister方式自定义函数

最常用的方式,这种方式编写的函数,既能用于SQL中,也能用于DSL中

语法:

UDF变量名 = spark.udf.register(UDF函数名, 函数的处理逻辑)

定义:spark.udf.register()
UDF变量名:DSL中调用UDF使用的
UDF函数名:SQL中调用UDF使用

案例:

查看以下数据:

id	name	msg
01	周杰伦	150/175
02	周杰	130/185
03	周华健	148/178
04	周星驰	130/175
05	闫妮	110/180

将以上数据,通过自定义函数,变为如下数据:
01	周杰伦	 150斤/175cm
02	周杰	  130斤/185cm
03	周华健  148斤/178cm

第一步:自定义函数

# 编写一个普通的函数,用于写逻辑
def get_data(str1):
	list1 = str1.split("/")
	return list1[0] + "斤/" + list1[1] + "cm"

第二步:注册函数

# 定义一个UDF:变量名-dsl = spark.udf.register(函数名-sql, 处理逻辑, 返回值)
	# get_new_info 用于 sql 中
	# get_info 用于DSL
	get_info = spark.udf.register(name="get_new_info", f=lambda oldinfo: get_data(oldinfo))

第三步:使用函数

#使用sql的方式调用
	spark.sql("select id,name,get_new_info(msg) from star").show()

	# 使用dsl的方式调用
	# DSL:用变量名
	import pyspark.sql.functions as F

	new_df.select(F.col("id"), F.col("name"), F.col("msg"), get_info(F.col("msg")).alias("newinfo")).show()

代码演示:

import os

from pyspark.sql import SparkSession

"""
------------------------------------------
  Description : TODO:
  SourceFile : _07_sparksql_udf
  Author  : 闫哥
  Date  : 2025/3/21 星期五
-------------------------------------------
"""

def get_data(str1):
    arr1 = str1.split("/")
    return arr1[0]+"斤/"+arr1[1]+"cm"

if __name__ == '__main__':
    os.environ['JAVA_HOME'] = 'C:\Program Files\Java\jdk1.8.0_241'
    # 配置Hadoop的路径,就是前面解压的那个路径
    os.environ['HADOOP_HOME'] = 'D:\hadoop-3.3.1'
    # 配置base环境Python解析器的路径
    os.environ['PYSPARK_PYTHON'] = 'C:/ProgramData/Miniconda3/python.exe'  # 配置base环境Python解析器的路径
    os.environ['PYSPARK_DRIVER_PYTHON'] = 'C:/ProgramData/Miniconda3/python.exe'
    spark = SparkSession.builder.master("local[2]").appName("自定义函数").config(
        "spark.sql.shuffle.partitions", 2).getOrCreate()

    # getmsg 用于 sql 中
    # get_info 用于DSL

    #get_info = spark.udf.register("getmsg", get_data)
    spark.udf.register("getmsg", get_data)

    spark.read.format("csv").option("sep","\t").option("header","true").load("../../datas/testudf.txt").createOrReplaceTempView("stars")

    spark.sql("select name,getmsg(msg) from stars").show()


    spark.stop()

三:SparkSQL使用方式

1、平常做SQL测试,能不能像Hive中一样,直接启动一个命令行做测试?

我们可以使用一个命令,这个命令叫做spark-sql

# 启动HDFS服务:NameNode和DataNodes
start-dfs.sh

# 启动HiveMetaStore 服务
hive-server-manager.sh start metastore

# 设置Spark日志级别
cd /opt/installs/spark/conf/
mv log4j.properties.template log4j.properties
vi log4j.properties 
#修改19行INFO为WARN
log4j.rootCategory=WARN, console

接着开始启动客户端:

spark-sql --master yarn --conf spark.sql.shuffle.partitions=2

2、问题:Hive中可以使用Beeline或者DataGrip连接HiveServer2进行测试,那SparkSQL中如何实现?

ThriftServer

  • 功能:类似于HiveServer2,负责解析客户端提交的SQL语句,转换成Spark的任务进行执行
  • 本质:Spark中的一个特殊的程序,利用程序的资源运行所有SQL,该程序除非手动关闭,否则一直运行

/opt/installs/spark/sbin/start-thriftserver.sh \
--hiveconf hive.server2.thrift.port=10001 \
--hiveconf hive.server2.thrift.bind.host=caijing \
--master yarn \
--conf spark.sql.shuffle.partitions=2

启动服务,该服务不会停止,一直在后台启动,假如启动不了,记得查看日志。

停止thiftServer:

/opt/installs/spark/sbin/stop-thriftserver.sh 

使用beeline也可以连接:

/opt/installs/spark/bin/beeline -u jdbc:hive2://caijing:10001 -n root -p 123456

遇到的问题:hive中的数据库和sparksql 操作的数据库不是同一个

解决方案:在hive 下修改hive-site.xml

<property>
		<name>hive.metastore.schema.verification</name>
		<value>false</value>
</property>

将hive-site.xml 复制到 spark的conf 下

cp /opt/installs/hive/conf/hive-site.xml /opt/installs/spark/conf

修改spark下的hive-site.xml

<property>
		<name>hive.server2.thrift.port</name>
		<value>10001</value>
		<description>Port number of HiveServer2 Thrift interface when hive.server2.transport.mode is 'binary'.</description>
	  </property>

接着分发一下:

xsync.sh /opt/installs/spark/conf/hive-site.xml

重启thrift服务:

/opt/installs/spark/sbin/stop-thriftserver.sh

/opt/installs/spark/sbin/start-thriftserver.sh --hiveconf hive.server2.thrift.port=10001 --hiveconf hive.server2.thrift.bind.host=bigdata01 --master yarn --conf spark.sql.shuffle.partitions=2

使用datagrip 重新连接一下,记得刷新一下连接。

3、在工作过程中如何使用sparkSQL

1) 使用python编写sparkSQL

使用Python代码在本地运行或者远程提交,测试SQL或者DSL的开发

- 场景:数据源多样化:Hive表、MySQL、结构化文件,可以写SQL或者DSL、RDD
- 产出:Python文件
- 运行:spark-submit

比如:
/opt/installs/spark-yarn/bin/spark-submit \
--master yarn \
--deploy-mode client \
--conf "spark.pyspark.driver.python=/opt/installs/anaconda3/bin/python3" \
--conf "spark.pyspark.python=/opt/installs/anaconda3/bin/python3" \
--driver-memory 512m \
--executor-memory 512m \
--executor-cores 1 \
--num-executors 2 \
--queue default \
开发的Python程序.py \
参数……

2) 不会写python,只会sql

启动ThriftServer,使用Beeline或者DataGrip连接ThriftServer,测试SQL语句的开发

- 场景:数据源主要用于对Hive表做处理,只写SQL
- 产生:SQL语句/SQL文件
- 运行:spark-sql -f

spark-sql -f   /opt/installs/a.sql

实战:
vi a.sql

代码如下:
show databases;
use db_hive;
show tables;
select * from emp;
create table test_rs03
select
    d.dname,
    round(avg(e.sal), 2) as avg_sal
from db_hive.emp e
join db_hive.dept d
on e.deptno = d.deptno
group by d.dname
order by avg_sal desc;

接着运行该sql:

# 运行SQL文件
/opt/installs/spark/bin/spark-sql --master local[2] -f /opt/data/spark-app.sql

四、综合案例

数据如下:📎retail.json

收银系统中,每个订单会产生一条JSON数据,传递到后台的数据存储系统中

{
  "discountRate":1,
  "dayOrderSeq":8,
  "storeDistrict":"雨花区",
  "isSigned":0,
  "storeProvince":"湖南省",
  "origin":0,
  "storeGPSLongitude":"113.01567856440359",
  "discount":0,
  "storeID":4064,
  "productCount":4,
  "operatorName":"OperatorName",
  "operator":"NameStr",
  "storeStatus":"open",
  "storeOwnUserTel":12345678910,
  "corporator":"hnzy",
  "serverSaved":true,
  "payType":"alipay",
  "discountType":2,
  "storeName":"杨光峰南食店",
  "storeOwnUserName":"OwnUserNameStr",
  "dateTS":1563758583000,
  "smallChange":0,
  "storeGPSName":"",
  "erase":0,
  "product":[
    {
      "count":1,
      "name":"百事可乐可乐型汽水",
      "unitID":0,
      "barcode":"6940159410029",
      "pricePer":3,
      "retailPrice":3,
      "tradePrice":0,
      "categoryID":1
    },
    {
      "count":1,
      "name":"馋大嘴盐焗鸡筋110g",
      "unitID":0,
      "barcode":"6951027300076",
      "pricePer":2.5,
      "retailPrice":2.5,
      "tradePrice":0,
      "categoryID":1
    },
    {
      "count":2,
      "name":"糯米锅巴",
      "unitID":0,
      "barcode":"6970362690000",
      "pricePer":2.5,
      "retailPrice":2.5,
      "tradePrice":0,
      "categoryID":1
    },
    {
      "count":1,
      "name":"南京包装",
      "unitID":0,
      "barcode":"6901028300056",
      "pricePer":12,
      "retailPrice":12,
      "tradePrice":0,
      "categoryID":1
    }
  ],
  "storeGPSAddress":"",
  "orderID":"156375858240940641230",
  "moneyBeforeWholeDiscount":22.5,
  "storeCategory":"normal",
  "receivable":22.5,
  "faceID":"",
  "storeOwnUserId":4082,
  "paymentChannel":0,
  "paymentScenarios":"PASV",
  "storeAddress":"StoreAddress",
  "totalNoDiscount":22.5,
  "payedTotal":22.5,
  "storeGPSLatitude":"28.121213726311993",
  "storeCreateDateTS":1557733046000,
  "payStatus":-1,
  "storeCity":"长沙市",
  "memberID":"0"
}

字段解析:

- 核心字段
- "storeProvince":订单所在的省份信息,例如:湖南省
- "storeID":订单所产生的店铺ID,例如:4064
- "receivable":订单收款金额:例如:22.5
- "payType":订单支付方式,例如:alipay
- "dateTS":订单产生时间,例如:1563758583000

1-统计查询每个省份的总销售额【订单金额要小于1万】:省份、订单金额

2-统计查询销售额最高的前3个省份中,统计各省份单日销售额超过1000的各省份的店铺个数

  • 只对销售额最高的前3个省份做统计:将这三个省份的数据过滤出来
  • 统计每个省份每个店铺每天的销售额超过1000的店铺个数
  • 省份、店铺id【去重】、销售额、天

3-统计查询销售额最高的前3个省份中,每个省份的平均订单金额

  • 只对销售额最高的前3个省份做统计:将这三个省份的数据过滤出来
  • 按照省份分组,求订单金额平均值

4-统计查询销售额最高的前3个省份中,每个省份的每种支付类型的占比

  • 只对销售额最高的前3个省份做统计:将这三个省份的数据过滤出来
  • 支付类型:微信、刷卡、支付宝、现金
  • 支付类型的占比 = 类型支付个数 / 总个数
  • 分组:每个省份每种类型支付的个数 / 每个省份总支付个数
  • 省份、支付类型

第一步:数据清洗

字段说明:

- 核心字段
- "storeProvince":订单所在的省份信息,例如:湖南省
- "storeID":订单所产生的店铺ID,例如:4064
- "receivable":订单收款金额:例如:22.5
- "payType":订单支付方式,例如:alipay
- "dateTS":订单产生时间,例如:1563758583000
读取数据变成DataFrame,并对不合法的数据进行清洗【过滤、转换】

- 订单金额超过10000的订单不参与统计
- storeProvince不为空:None, 也不为 ‘null’值
- 只保留需要用到的字段,将字段名称转换成Python规范:a_b_c
- 并对时间戳进行转换成日期,获取天
- 对订单金额转换为decimal类型

第二步:获取销售额前三的数据,放入缓存中

第三步:编写各种指标。

最后的代码:

import os

from pyspark.sql import SparkSession

"""
------------------------------------------
  Description : TODO:
  SourceFile : _06综合案例
  Author  : 老闫
  Date  : 2024/11/6 星期三
-------------------------------------------
"""
if __name__ == '__main__':
    # 配置环境
    os.environ['JAVA_HOME'] = 'C:/Program Files/Java/jdk1.8.0_241'
    # 配置Hadoop的路径,就是前面解压的那个路径
    os.environ['HADOOP_HOME'] = 'D:/hadoop-3.3.1'
    # 配置base环境Python解析器的路径
    os.environ['PYSPARK_PYTHON'] = 'C:/ProgramData/Miniconda3/python.exe'  # 配置base环境Python解析器的路径
    os.environ['PYSPARK_DRIVER_PYTHON'] = 'C:/ProgramData/Miniconda3/python.exe'

    spark = SparkSession.builder.master("local[2]").appName("").config(
        "spark.sql.shuffle.partitions", 2).getOrCreate()

    df = spark.read.json("../../datas/function/retail.json")
    df.createOrReplaceTempView("orders")
    """
    	    - 订单金额超过10000的订单不参与统计
    		- storeProvince不为空:None, 也不为 ‘null’值
    		- 只保留需要用到的字段,将字段名称转换成sql规范:a_b_c
    		- 并对时间戳进行转换成日期,获取天
    		- 对订单金额转换为decimal类型
    """
    """
    	- 核心字段
    	- "storeProvince":订单所在的省份信息,例如:湖南省
    	- "storeID":订单所产生的店铺ID,例如:4064
    	- "receivable":订单收款金额:例如:22.5
    	- "payType":订单支付方式,例如:alipay
    	- "dateTS":订单产生时间,例如:1563758583000
    """
    cleared_df = spark.sql("""
        select 
          storeProvince store_province,
          storeID store_id,
          cast(receivable as decimal(10,2)) receivable,
          payType pay_type,
          from_unixtime(dateTS/1000,"yyyy-MM-dd") date_ts
         from orders where receivable <10000 and storeProvince is not null and storeProvince!= 'null'
    """)

    cleared_df.createOrReplaceTempView("user_orders")
    # 统计查询每个省份的总销售额
    df1 = spark.sql("""
       select store_province,sum(receivable) total_money from user_orders group by store_province
    """)
    df1.createOrReplaceTempView("every_province_total_money")
    df1.show()
    # 统计查询销售额最高的前3个省份中,统计各省份单日销售额超过1000的各省份的店铺个数
    qsDf = spark.sql("""
       select * from user_orders where store_province in (
          select store_province from every_province_total_money order by total_money desc limit 3
       )
    """)
    qsDf.createOrReplaceTempView("qs_details")

    # 统计查询销售额最高的前3个省份中,统计各省份单日销售额超过1000的各省份的店铺个数
    spark.sql("""
       with t as(
          select date_ts,store_id,store_province from qs_details group by date_ts,store_id,store_province having sum(receivable) > 1000
       )
       select store_province,count(distinct store_id) store_num from t group by store_province
    """).show()

    # 每个省份的平均订单金额
    spark.sql("""
      select store_province,round(avg(receivable),2) avg_receivable from qs_details group by store_province
    """).show()

    # 每个省份的每种支付类型的占比
    spark.sql("""
       with t as(
         select store_province,pay_type,count(1) total_order from  qs_details group by store_province,pay_type
       )
       select store_province,pay_type,round(total_order/(sum(total_order) over(partition by store_province )),2) rate from t 
    """).show()

    spark.stop()

DSL语法如下:

# !/usr/bin/env python
# -*- coding: utf-8 -*-
from pyspark import StorageLevel
from pyspark.sql import SparkSession
import pyspark.sql.functions as F
import os

from pyspark.sql.types import DecimalType

"""
  -------------------------------------------------
     Description :	TODO:基于SparkSQL实现零售分析案例
     SourceFile  :	07.pyspark_sql_sale_case
     Author      :	Wang
     Date	     :	2023/1/2
  -------------------------------------------------
  """

if __name__ == '__main__':
    # todo:0-设置系统环境变量:全部换成Linux地址
    os.environ['JAVA_HOME'] = '/opt/installs/jdk'
    os.environ['HADOOP_HOME'] = '/opt/installs/hadoop'
    os.environ['PYSPARK_PYTHON'] = '/opt/installs/anaconda3/bin/python3'
    os.environ['PYSPARK_DRIVER_PYTHON'] = '/opt/installs/anaconda3/bin/python3'

    # todo:1-构建SparkSession
    spark = SparkSession \
        .builder \
        .master("local[2]") \
        .appName("SparkSQL APP") \
        .config("spark.sql.shuffle.partitions", 2) \
        .getOrCreate()

    # todo:2-数据处理:读取、转换、保存
    # step1: 读取数据
    input_df = spark.read.json(path="../datas/order/retail.json")
    # input_df.show()

    # step2: 处理数据
    """ETL:数据清洗"""
    etl_df = ( input_df
               # 行的过滤:where
               .where( (F.col("receivable") < 10000) & (F.col("storeProvince").isNotNull()) & (F.col("storeProvince") != 'null') )
               # 列的过滤和转换:select
               .select(
                    F.col("storeProvince").alias("store_province"),
                    F.col("storeID").alias("store_id"),
                    F.col("payType").alias("pay_type"),
                    # 将订单时间转换为订单日期:yyyy-MM-dd,时间戳转成日期:from_unixtime , 日期转换成时间戳:unix_timestamp
                    F.from_unixtime(F.substring(F.col("dateTS"), 1, 10), 'yyyy-MM-dd').alias("daystr"),
                    # 类型转换
                    F.col("receivable").cast(DecimalType(10, 2)).alias("receivable_money")
                )
    )
    # etl_df.show()

    """需求1:统计查询每个省份的订单金额"""
    # 将ETL的数据进行持久化
    etl_df.persist(StorageLevel.MEMORY_AND_DISK)

    rs1 = ( etl_df
            # 先按照省份分组
            .groupBy(F.col("store_province"))
            # 聚合
            .agg(F.sum(F.col("receivable_money")).alias("total_money"))
    )
    # rs1.show()

    """过滤:将销售额最高的前3个省份的数据先过滤出来"""
    # 获取前三省份的名称的列表
    top3_province = rs1.orderBy(F.col("total_money").desc()).limit(3).rdd.map(lambda row: row.store_province).collect()
    # print(top3_province)
    # 实现过滤
    top3_province_data = ( etl_df
                           # 过滤出前三个省份的数据
                           .where(F.col("store_province").isin(top3_province))
                       )
    # top3_province_data.show()

    # 释放缓存
    etl_df.unpersist()

    """需求2:对销售额最高的前3个省份的数据进行统计,统计单日销售额超过1000的各省份的店铺个数"""
    # 先将DF注册为临时的视图
    top3_province_data.createOrReplaceTempView("tmp_top3_order")
    # 如果一个视图被使用多次,可以进行缓存
    spark.catalog.cacheTable("tmp_top3_order")
    # 使用SQL进行处理
    rs2 = spark.sql("""
        with tmp as (
            select
                store_province, store_id, daystr,
                sum(receivable_money) as day_money
            from tmp_top3_order
            group by store_province, store_id, daystr
            having day_money > 1000
        )
        select
            store_province,
            count(distinct store_id) as store_cnt
        from tmp
        group by store_province
    """)
    # rs2.show()

    """需求3:统计销售额最高的前3个省份的每个省份的平均订单金额"""
    rs3 = spark.sql("""
        select
            store_province,
            round(avg(receivable_money), 2) as avg_money
        from tmp_top3_order
        group by store_province
    """)
    # rs3.show()

    """需求4:统计销售额最高的前3个省份的数据,查询每个省份的每种支付类型的订单个数占比"""
    rs4 = spark.sql("""
        with t1 as (
            select
                store_province,
                pay_type,
                count(1) as pay_type_cnt
            from tmp_top3_order
            group by store_province, pay_type
        ), t2 as (
            select
                *,
                sum(pay_type_cnt) over (partition by store_province) as total_cnt
            from t1
        )
        select
            store_province,
            pay_type,
            round((pay_type_cnt / total_cnt), 2) as rate
        from t2
    """)
    rs4.show()


    # 视图不再被使用,需要释放
    spark.catalog.uncacheTable("tmp_top3_order")
    # step3: 保存结果

    # todo:3-关闭SparkSession
    spark.stop()

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值