• spark教程(11)-sparkSQL 数据抽象


    数据抽象

    sparkSQL 的数据抽象是 DataFrame,df 相当于表格,它的每一行是一条信息,形成了一个 Row

    Row

    它是 sparkSQL 的一个抽象,用于表示一行数据,从表现形式上看,相当于一个 tuple 或者 表中的一行;

    from pyspark.sql import Row
    
    ##### 创建 Row
    #### method 1
    row = Row(name="Alice", age=11)
    print row     # Row(age=11, name='Alice')
    print row['name'], row['age']     # ('Alice', 11)
    print row.name, row.age         # ('Alice', 11)
    print 'name' in row         # True
    print 'wrong_key' in row         # False
    
    #### method 2
    Person = Row("name", "age")
    print Person            # <Row(name, age)>
    print 'name' in Person      # True
    print 'wrong_key' in Person     # False
    print Person("Alice", 11)       # Row(name='Alice', age=11)

    DataFrame (DF)

    与 RDD 类似,df 也是分布式的数据容器,不同的是,df 更像一个 二维数据表,除了数据本身外,还包含了数据的结构信息,即 schema;

    df 的 API 提供了更高层的关系操作,比函数式的 RDD API 更加友好;

    df 的底层仍是 RDD,所以 df 也是惰性执行的,但值得注意的是,它比 RDD 性能更高

    问题来了:为什么底层实现是 RDD,却比 RDD 更快,不合常理啊

    其实是这样的,因为 df 是由 spark 自己转换成 RDD 的,那么 spark 自然会用最合适的、最优化的方式转换成 RDD,因为它比任何人都清楚怎么才能更高效,

    对比我们自己操作 RDD 去实现各种功能,大部分情况下我们的作法可能不是最优,自己玩不如作者玩,所以说 df 性能高于 RDD

    举个简单例子:

    data1 = sc.parallelize([('1','a'), ('2', 'b'), ('3', 'c')])
    data2 = sc.parallelize([('1','1'), ('2', '2'), ('3', '3')])
    ### 找到两个list中 key 为 1 的对应值的集合
    
    ## 自己写可能这么写
    data1.join(data2).collect() # [('1', ('a', '1')), ('3', ('c', '3')), ('2', ('b', '2'))]
    data1.join(data2).filter(lambda x: x[0] == '1').collect()   # [('1', ('a', '1'))]
    
    ## spark 可能这么写
    data1.filter(lambda x: x[0] == '1').join(data2.filter(lambda x: x[0] == '1')).collect() # [('1', ('a', '1'))]

    为什么 spark 这么写快呢?这里简单解释下

    join 是把 两个元素做 笛卡尔內积,生成了 3x3=9 个元素,然后 shuffle,每个分区分别比较 key 是否相同,如果相同,合并,然后合并分区结果;

    我们自己写的就是这样,shuffle 了 9 个元素;

    而 spark 是先 filter,每个 list 变成了 一个元素,然后 join,join 的结果直接就是所需,不用 shuffle;

    shuffle 本身是耗时的,而 filter 无需 shuffle,所以效率高      【join 是个 低效方法的原因】

    小结

    1. df 也是一个查询优化的手段

    2. df 允许我们像操作数据库一样操作它

    DataSet

    DataSet 是 DataFrame 的扩展,是 spark 最新的数据抽象;

    dataSet 像个对象,允许我们像操作类一样操作它,通过属性查看数据;

    实际上 DataSet 是在 df 的基础上增加了数据类型;

    df 只指定了字段名,而没有指定字段类型,sparkSQL 需要自动推断数据集的格式,这也是一种消耗,而 dataSet 直接指定了字段名和字段属性,效率更高

    python 目前不支持 dataSet,所以后续支持了再说 

    SparkSession

    在老版本中,sparkSQL 提供了两种 SQL 查询的起始点:

    SQLContext,用于 spark 自己提供的 SQL 查询;

    HiveContext,用于连接 hive 的查询

    sparkSession 是新版的 SQL 查询起始点,实质上是组合了 SQLContext 和 HiveContext;

    sparkSession 只是封装了 sparkContext,sparkContext 包含 SQLContext 和 HiveContext;

    所以 sparkSession 实际上还是 依靠 sparkContext 实现了 SQLContext 和 HiveContext,故老版本用法也适用新版本。

    DataFrame 的创建

    sparkSession 直接生成 df

    df 的创建有 3 种方式

    从 spark 的数据源创建:读取 spark 支持的文件

    从内部 RDD 创建:RDD 转换成 df

    从 hive 创建:hive 查询

    spark 数据源创建 DF

    spark 支持的文件格式都有统一的入口

    >>> dir(spark.read)
    [ 'csv', 'format', 'jdbc', 'json', 'load', 'option', 'options', 'orc', 'parquet', 'schema', 'table', 'text']

    sparkSQL 定义了一个 DataFrameReader 的类,在这个类中定义了所有数据源的接口,spark.read 是这个类的入口 

    DataFrameReader

    创建 df 的方法也是惰性的

    json

    json 文件必须每行是一个 json 对象

    ##  json 文件如下
    # {'age': '10','name': 'zhangsan'}
    # {'age': '20','name': 'lisi'}
    
    #### method 1
    df1 = spark.read.json('data.json')    # 相对路径
    # >>> df1     # DataFrame[age: string, name: string] 可以看到 df 具备了字段名和字段属性
    
    df1.show()
    # +---+--------+
    # |age|    name|
    # +---+--------+
    # | 10|zhangsan|
    # | 20|    lisi|
    # +---+--------+
    
    df2 = spark.read.json('file:///usr/lib/spark/data.json')  # 绝对路径
    
    #### method 2
    spark.read.format('json').load('/data.json').show()

    其他文件读取方式与 json 完全相同

    jdbc

    spark.read.jdbc('jdbc:postgresql://172.16.89.80:5432/postgres', 'subtable', 'max_lng', 5, 10, 3, properties={'user':'postgres', 'password':'postgres'}).show()

    注意 

    sparkSQL 会自动推断数据集的格式,以 json 为例,sparkSQL 会扫描数据集的每一项从而推断数据格式,

    如果我们已经知道数据格式,可以在创建 df 时指定数据格式,从而加速创建过程且避免扫描数据集

    from pyspark.sql.types import StructType, StructField, StringType, IntegerType
    schema = StructType([StructField("age", StringType(), True),
                         StructField("name", StringType(), True)])
    spark.read.schema(schema).json('/data.json').show()

    RDD 创建 DF

    见下面的格式互转

    hive 创建 DF

    有两种方式从 hive 创建 df

    1. 使用 DataFrameReader 中定义的 table 方法

    注意这种方式不只适用于 hive,也用于其他表

    spark.read.table('hive1101.person').show()

    2. 使用 HiveContext 或者 SparkSession 中的 sql 方法,直接运行 hql

    DF 操作

    sparkSQL 对 DF 的操作有两种风格,一种是类 sql 的方式,一种是 领域专属语言 DSL

    SQL 风格操作 DF

    df 并不是一张数据表,而 sql 风格需要一张表;

    如果有 hive 环境,可以直接用 hive 中的表,

    如果没有,需要把 df 当成一个临时表注册到应用上,而且只有注册到的应用正在运行,这个临时表才可以使用

    注册方法不止一种,比如 createTempView、registerTempTable、

    ### 创建临时视图
    df1.createTempView('student')
    # df1.createOrReplaceTempView('student')    # ok
    spark.sql('select * from student').show()
    # +---+--------+
    # |age|    name|
    # +---+--------+
    # | 10|zhangsan|
    # | 20|    lisi|
    # +---+--------+
    
    spark.sql('select age from student').show()
    spark.sql('select avg(age) from student').show()
    # +------------------------+
    # |avg(CAST(age AS DOUBLE))|
    # +------------------------+
    # |                    15.0|
    # +------------------------+

    关闭 SparkSession 后这张表无法使用

    session

    这里穿插讲下 session 的概念;

    session 的本意是会话,我们在多个场合都见过 session,如 web,如 tensorflow,但是在 web 中貌似不是 会话啊;

    其实是这样的,session 有广义和狭义之分

    广义 session:就是我们说的会话

    狭义 session:它是一个存储位置,和 cookie 相对,cookie 是把某个信息存在客户度,session 是把 某个信息存在服务器上

    全局表 

    临时表是在 session 范围内的,session 关闭后,临时表失效,如果想应用范围内有效,可以使用全局表,

    全局表需要全路径访问

    ### 为了在应用范围内使用数据表,创建全局表
    df1.createGlobalTempView('people')
    ## 查询
    spark.sql('select * from global_temp.people').show()    # global_temp.people 全路径访问表
    
    
    ## 在另一个 session 中查询该表
    spark.newSession().sql('select * from global_temp.people').show()

    DSL 风格操作 DF

    df 知道每列的名字和数据类型,可以提供用于数据处理的领域专属语言DSL  【这种方式不常用】

    df1.printSchema() # 打印表结构
    # root
    #  |-- age: string (nullable = true)
    #  |-- name: string (nullable = true)
    
    df1.select('name').show()   # 查询name字段
    df1.select("name", df1.age + 1).show() # age 字段的值都 加1,scala 中是用 $'age' 代替 df.age
    # +--------+---------+
    # |    name|(age + 1)|
    # +--------+---------+
    # |zhangsan|     11.0|
    # |    lisi|     21.0|
    # +--------+---------+
    
    df1.filter(df1.age > 15).show()  # 查看 age 大于 15
    # +---+----+
    # |age|name|
    # +---+----+
    # | 20|lisi|
    # +---+----+
    
    # 多列和多个条件
    df.select("userID", "rating").filter("userID = 2 and rating > 3").show()

    RDD-DF-dataSet 

    rdd、dataFrame、dataSet 相当于 spark 中三种数据类型,简单总结几点:

    1. rdd 是 df、ds 的底层实现

    2. df 在 rdd 的基础上添加了结构,可以像数据表一个进行字段操作,易用,且高效

    3. ds 在 df 的基础上添加了数据类型,并且可以像操作类一样进行属性操作,目前 python 不支持

    4. 三者可互相转换

    5. df、ds 是 sparkSQL 中的数据类型,准确的说叫数据抽象,在 sparkSQL 中他们被转换成 table,进行 sql 操作

    6. 三者的计算逻辑并无差异,也就是说相同的数据,结果是相同的

    7. 三者的计算效率和执行方式不同

    8. 在未来 spark 演进过程中, ds 会逐步取代 df、rdd

    发展历程

    RDD(spark1.0)  ===>  DataFrame(spark1.3)  ===> DataSet(spark1.6)

    转换逻辑

    rdd + 表结构 = df

    rdd + 表结构 + 数据类型 = ds

    df + 数据类型 = ds

    ds - 数据类型 - 表结构 = rdd

    ds - 数据类型 = df

    df - 表结构 = rdd

    转换方法

    RDD to DF  之 toDF

    dataFrame 类似于数据表,数据表有行的概念,df 也有 Row 的概念,也就是说 df 必须是有行有列,二维的概念

    如果 RDD 不是二维,或者说没有 Row 的概念,需要显示的构建 Row 的格式;

    ## 手动构建 Row 的概念
    rdd1 = sc.parallelize(range(5))
    df1_1 = rdd1.map(lambda x: Row(id = x)).toDF()    #  先加入结构,即字段,或者说 key,然后调用 toDF
    # >>> df1
    # DataFrame[id: bigint]

    RDD to DF  之 spark.createDataFrame

    该方法有两个输入:一个由行构成的RDD,一个数据格式;

    数据格式可以是一个 StructType 类实例;

    一个 StructType 对象包含一个 StructField 对象序列;

    一个StructField 对象用于指定一列的名字、数据类型,并可选择的指定这一列是否包含空值及其元数据

    ### 方法2
    rdd1 = sc.parallelize([range(5)])   # 注意必须是 二维 的,sc.parallelize(range(5)) 是不行的
    df2_1 = spark.createDataFrame(rdd1).collect()                   # 没有显示地添加字段,以 默认值为 字段名
    # [Row(_1=0, _2=1, _3=2, _4=3, _5=4)]
    
    rdd2 = sc.parallelize([('a', 1), ('b', 2)])     # 二维数据
    df2_2 = spark.createDataFrame(rdd2, ['label', 'num']).collect() # 显示地添加字段
    # [Row(label=u'a', num=1), Row(label=u'b', num=2)]
    
    ### 方法3
    rdd3 = sc.parallelize([('zhangsan', 20), ('lisi', 30)])
    Person = Row('name', 'age')     # 格式化 Row,每行代表一个 Person
    person = rdd3.map(lambda x: Person(*x))     # 把 RDD 格式化成 新的 RDD,并加入 Row 的概念
    df3_1 = spark.createDataFrame(person).show()
    # +--------+---+
    # |    name|age|
    # +--------+---+
    # |zhangsan| 20|
    # |    lisi| 30|
    # +--------+---+
    
    ### 方法4
    from pyspark.sql.types import StructType, StructField, StringType, IntegerType
    schema = StructType([StructField("name", StringType(), True),
                         StructField("age", IntegerType(), True)])
    df3 = spark.createDataFrame(rdd3, schema).collect()
    # [Row(name=u'zhangsan', age=20), Row(name=u'lisi', age=30)]

    toDF() vs createDataFrame()

    1. 前者需要自己推断数据集的数据格式,因为并没有指定,后者则需要指定数据格式;

    2. 前者易用;

    3. 后者更加灵活,可以根据需要对同一数据设定多个数据格式,满足不同需求

    DF to RDD

    只需调用 rdd 属性即可

    rdd = sc.parallelize([('a', 1), ('b', 2)])     # 二维数据
    df = spark.createDataFrame(rdd2, ['label', 'num'])
    df.rdd.collect()
    # [Row(label=u'a', num=1), Row(label=u'b', num=2)]

    参考资料:

    https://spark.apache.org/docs/latest/api/python/pyspark.sql.html#pyspark.sql.SQLContext  官网 rdd to df

  • 相关阅读:
    Codeforces 1439B. Graph Subset Problem (思维,复杂度分析)
    石子游戏(nim游戏+按位考虑)
    Codeforces 1437F Emotional Fishermen(思维,dp)
    Codeforces Round #671 (Div. 2) (A~E)
    Java就业企业面试问题ssh框架
    DUBBO初探搭建DUBBO开发环境
    Spring容器组建注解@Component和Resouces实现完全注解配置
    jUnit 4 在 s2sh 中的应用
    4.5、常量、作用域、内置全局变量
    Java 诗词纵向转换字符流输出
  • 原文地址:https://www.cnblogs.com/yanshw/p/11689947.html
Copyright © 2020-2023  润新知