- 一站制造项目中在数据采集时遇到了什么问题,以及如何解决这个问题?
  - 技术选型:Sqoop
  - 问题:发现采集以后生成在HDFS上文件的行数与实际Oracle表中的数据行数不一样,多了
  - 原因:Sqoop默认将数据写入HDFS以普通文本格式存储,一旦遇到数据中如果包含了特殊字符\n,将一行的数据解析为多行
  - 解决
    - 方案一:Sqoop删除特殊字段、替换特殊字符【一般不用】
    - 方案二:更换其他数据文件存储类型:AVRO
      - 数据存储:Hive
      - 数据计算:SparkSQL
- 什么是Avro格式,有什么特点?
  - 二进制文本:读写性能更快
  - 独立的Schema:生成文件每一行所有列的信息
    - 对列的扩展非常友好
  - Spark与Hive都支持的类型
- 如何实现对多张表自动采集到HDFS?
  - 需求
  - 读取表名
  - 执行Sqoop命令
- 效果:将所有增量和全量表的数据采集到HDFS上

- 目标:自动化的ODS层与DWD层构建
- 实现
  - 掌握Hive以及Spark中建表的语法规则
  - 实现项目开发环境的构建
  - 实现所有代码注释
  - ODS层与DWD层整体运行测试成功

黑马一站制造数仓实战3-LMLPHP

- ODS层 :原始数据层
  - 来自于Oracle中数据的采集
  - 数据存储格式:AVRO
  - ODS区分全量和增量
  - 实现
    - 数据已经采集完成

    - step1:创建ODS层数据库:one_make_ods
    - step2:根据表在HDFS上的数据目录来创建分区表
    - step3:申明分区
- DWD层
  - 来自于ODS层数据
  - 数据存储格式:ORC
  - 不区分全量和增量的
  - 实现
- step1:创建DWD层数据库:one_make_dwd
- step2:创建DWD层的每一张表
- step3:从ODS层抽取每一张表的数据写入DWD层对应的表中

Hive建表语法

CREATE [TEMPORARY] [EXTERNAL] TABLE [IF NOT EXISTS] [db_name.]table_name
(
    col1Name col1Type [COMMENT col_comment],
    co21Name col2Type [COMMENT col_comment],
    co31Name col3Type [COMMENT col_comment],
    co41Name col4Type [COMMENT col_comment],
    co51Name col5Type [COMMENT col_comment],
    ……
    coN1Name colNType [COMMENT col_comment]

)
[PARTITIONED BY (col_name data_type ...)]
[CLUSTERED BY (col_name...) [SORTED BY (col_name ...)] INTO N BUCKETS]
[ROW FORMAT row_format]
    row format delimited fields terminated by 
    lines terminated by
[STORED AS file_format]
[LOCATION hdfs_path]
TBLPROPERTIES

Hive中Avro建表方式及语法

指定文件类型

- 方式一:指定类型

stored as avro

- 方式二:指定解析类

 --解析表的文件的时候,用哪个类来解析
  ROW FORMAT SERDE
    'org.apache.hadoop.hive.serde2.avro.AvroSerDe'
  --读取这张表的数据用哪个类来读取
  STORED AS INPUTFORMAT
    'org.apache.hadoop.hive.ql.io.avro.AvroContainerInputFormat'
  --写入这张表的数据用哪个类来写入
OUTPUTFORMAT
    'org.apache.hadoop.hive.ql.io.avro.AvroContainerOutputFormat'

指定Schema

- 方式一:手动定义Schema

CREATE TABLE embedded
COMMENT "这是表的注释"
ROW FORMAT SERDE
  'org.apache.hadoop.hive.serde2.avro.AvroSerDe'
STORED AS INPUTFORMAT
  'org.apache.hadoop.hive.ql.io.avro.AvroContainerInputFormat'
OUTPUTFORMAT
  'org.apache.hadoop.hive.ql.io.avro.AvroContainerOutputFormat'
TBLPROPERTIES (
  'avro.schema.literal'='{
    "namespace": "com.howdy",
    "name": "some_schema",
    "type": "record",
    "fields": [ { "name":"string1","type":"string"}]
  }'
);

- 方式二:加载Schema文件

CREATE TABLE embedded
  COMMENT "这是表的注释"
  ROW FORMAT SERDE
    'org.apache.hadoop.hive.serde2.avro.AvroSerDe'
  STORED as INPUTFORMAT
    'org.apache.hadoop.hive.ql.io.avro.AvroContainerInputFormat'
  OUTPUTFORMAT
    'org.apache.hadoop.hive.ql.io.avro.AvroContainerOutputFormat'
  TBLPROPERTIES (
   'avro.schema.url'='file:///path/to/the/schema/embedded.avsc'
  );

建表语法

- 方式一:指定类型和加载Schema文件

create external table one_make_ods_test.ciss_base_areas
comment '行政地理区域表'
PARTITIONED BY (dt string)
stored as avro
location '/data/dw/ods/one_make/full_imp/ciss4.ciss_base_areas'
TBLPROPERTIES ('avro.schema.url'='/data/dw/ods/one_make/avsc/CISS4_CISS_BASE_AREAS.avsc');

- 方式二:指定解析类和加载Schema文件

  create external table one_make_ods_test.ciss_base_areas
  comment '行政地理区域表'
  PARTITIONED BY (dt string)
  ROW FORMAT SERDE
    'org.apache.hadoop.hive.serde2.avro.AvroSerDe'
  STORED AS INPUTFORMAT
    'org.apache.hadoop.hive.ql.io.avro.AvroContainerInputFormat'
  OUTPUTFORMAT
    'org.apache.hadoop.hive.ql.io.avro.AvroContainerOutputFormat'
  location '/data/dw/ods/one_make/full_imp/ciss4.ciss_base_areas'
  TBLPROPERTIES ('avro.schema.url'='/data/dw/ods/one_make/avsc/CISS4_CISS_BASE_AREAS.avsc');

  create external table 数据库名称.表名
  comment '表的注释'
  partitioned by
  ROW FORMAT SERDE
    'org.apache.hadoop.hive.serde2.avro.AvroSerDe'
  STORED AS INPUTFORMAT
    'org.apache.hadoop.hive.ql.io.avro.AvroContainerInputFormat'
  OUTPUTFORMAT
    'org.apache.hadoop.hive.ql.io.avro.AvroContainerOutputFormat'
  location '这张表在HDFS上的路径'
  TBLPROPERTIES ('这张表的Schema文件在HDFS上的路径')

ODS层构建:需求分析

- 目标:将已经采集同步成功的101张表的数据加载到Hive的ODS层数据表中
- 问题
  - 难点1:表太多,如何构建每张表?
    - 101张表的数据已经存储在HDFS上
    - 建表
      - 方法1:手动开发每一张表建表语句,手动运行
      - 方法2:通过程序自动化建表
        - 拼接建表的SQL语句

create external table 数据库名称.表名
comment '表的注释'
partitioned by
ROW FORMAT SERDE
  'org.apache.hadoop.hive.serde2.avro.AvroSerDe'
STORED AS INPUTFORMAT
  'org.apache.hadoop.hive.ql.io.avro.AvroContainerInputFormat'
OUTPUTFORMAT
  'org.apache.hadoop.hive.ql.io.avro.AvroContainerOutputFormat'
location '这张表在HDFS上的路径'
TBLPROPERTIES ('这张表的Schema文件在HDFS上的路径')

- - - - 表名、表的注释、表在HDFS上的路径、Schema文件在HDFS上的路径
    - 将SQL语句提交给Hive或者Spark来执行
- 申明分区

alter table 表名 add partition if not exists partition(key=value)

- 难点2:如果使用自动建表,如何获取每张表的字段信息?

 - Schema文件:每个Avro格式的数据表都对应一个Schema文件

  - 统一存储在HDFS上

- 需求:加载Sqoop生成的Avro的Schema文件,实现自动化建表
- 分析
  - step1:代码中构建一个Hive/SparkSQL的连接
  - step2:创建ODS层数据库

create database if not exists one_make_ods;

- step3:创建ODS层全量表:44张表

create external table one_make_ods_test.ciss_base_areas

  comment '行政地理区域表'

  PARTITIONED BY (dt string)

  ROW FORMAT SERDE 'org.apache.hadoop.hive.serde2.avro.AvroSerDe'
  STORED AS INPUTFORMAT 'org.apache.hadoop.hive.ql.io.avro.AvroContainerInputFormat'
  OUTPUTFORMAT 'org.apache.hadoop.hive.ql.io.avro.AvroContainerOutputFormat'

  location '/data/dw/ods/one_make/full_imp/ciss4.ciss_base_areas'

  TBLPROPERTIES ('avro.schema.url'='hdfs://bigdata.itcast.cn:9000/data/dw/ods/one_make/avsc/CISS4_CISS_BASE_AREAS.avsc');

  - 读取全量表表名

    - 动态获取表名:循环读取文件

  - 获取表的信息:表的注释

    - Oracle:表的信息
    - 从Oracle中获取表的注释

  - 获取表的文件:HDFS上AVRO文件的地址

- 获取表的Schema:HDFS上的Avro文件的Schema文件地址

 - 拼接建表字符串

    - 方式一:直接相加:简单

      str1 = "I "
      str2 = "like China"
      str3 = str1 + str2

- 方式二:通过列表拼接:复杂

  - 执行建表SQL语句

- step4:创建ODS层增量表:57张表

  - 读取增量表表名

    - 动态获取表名:循环读取文件

  - 获取表的信息:表的注释

    - Oracle:表的信息
    - 从Oracle中获取表的注释

  - 获取表的文件:HDFS上AVRO文件的地址

 - 获取表的Schema:HDFS上的Avro文件的Schema文件地址

  - 拼接建表字符串

  - 执行建表SQL语句

工程代码结构

黑马一站制造数仓实战3-LMLPHP

代码模块功能**

- `auto_create_hive_table`:用于实现ODS层与DWD层的建库建表的代码
  - `cn.itcast`
    - `datatohive`
      - CHiveTableFromOracleTable.py:用于创建Hive数据库、以及获取Oracle表的信息创建Hive表等
      - CreateMetaCommon.py:定义了建表时固定的一些字符串数据,数据库名称、分层名称、文件类型属性等
      - CreateHiveTablePartition.py:用于手动申明ODS层表的分区元数据
      - LoadData2DWD.py:用于实现将ODS层的数据insert到DWD层表中
      - `fileformat`
      - AvroTableProperties.py- - - Avro文件格式对象,用于封装Avro建表时的字符串
      - OrcTableProperties.py:Orc文件格式对象,用于封装Orc建表时的字符串
    - OrcSnappyTableProperties.py:Orc文件格式加Snappy压缩的对象
      - TableProperties.py:用于获取表的属性的类
- `entity`
  - TableMeta.py:Oracle表的信息对象:用于将表的名称、列的信息、表的注释进行封装
    - ColumnMeta.py:Oracle列的信息对象:用于将列的名称、类型、注释进行封装
- `utils`
  - OracleHiveUtil.py:用于获取Oracle连接、Hive连接
- FileUtil.py:用于读写文件,获取所有Oracle表的名称
  - TableNameUtil.py:用于将全量表和增量表的名称放入不同的列表中
- ConfigLoader.py:用于加载配置文件,获取配置文件信息
  - OracleMetaUtil.py:用于获取Oracle中表的信息:表名、字段名、类型、注释等
- EntranceApp.py:程序运行入口,核心调度运行的程序

# todo:1-获取Oracle、Hive连接,获取所有表名
# todo:2-创建ODS层数据库
# todo:3-创建ODS层数据表
# todo:4-手动申明ODS层分区数据
# todo:5-创建DWD层数据库以及数据表
# todo:6-加载ODS层数据到DWD层
# todo:7-关闭连接,释放资源

- `config`
  - common.py:用于获取日志的类
    - settings.py:用于配置日志记录方式的类
  - `log`
    - itcast.log:日志文件
- `dw`:用于存储每一层构建的核心配置文件等

  - 重点关注:dw.ods.meta_data.tablenames.txt:存储了整个ODS层的表的名称

代码配置修改

- 修改1:auto_create_hive_table.cn.itcast.EntranceApp.py

  # 51行:修改为你实际的项目路径对应的表名文件
tableList = FileUtil.readFileContent("D:\\PythonProject\\OneMake_Spark\\dw\\ods\\meta_data\\tablenames.txt")

- 修改2:auto_create_hive_table.cn.itcast.utils.ConfigLoader

config.read('D:\\PythonProject\\OneMake_Spark\\auto_create_hive_table\\resources\\config.txt')

连接代码及测试

- 为什么要获取连接?
  - Python连接Oracle:获取表的元数据
  - 表的信息:TableMeta
    - 表名
      - 表的注释
    - list:[列的信息]
  - 列的信息:ColumnMeta
    - 列名
      - 列的注释
    - 列的类型
      - 类型长度
    - 类型精度
- Python连接HiveServer或者Spark的ThriftServer:提交SQL语句
- 连接代码讲解
  - step1:怎么获取连接?
    - Oracle:安装Python操作Oracle库包:cx_Oracle

cx_Oracle.connect(ORACLE_USER, ORACLE_PASSWORD, dsn)

- Hive/SparkSQL:安装Python操作Hive库包:PyHive

hive.Connection(host=SPARK_HIVE_HOST, port=SPARK_HIVE_PORT, username=SPARK_HIVE_UNAME, auth='CUSTOM', password=SPARK_HIVE_PASSWORD)

- step2:连接时需要哪些参数?

  - Oracle:主机名、端口、用户名、密码、SID
  - Hive:主机名、端口、用户名、密码

- step3:如果有100个代码都需要构建Hive连接,怎么解决呢?

  - 将所有连接参数写入一个配置文件:resource/config.txt
  - 通过配置文件的工具类获取配置:ConfigLoader

- step4:在ODS层建101张表,表名怎么动态获取呢?

  - 读取表名文件:将每张表的名称都存储在一个列表中

- step5:ODS层的表分为全量表与增量表,怎么区分呢?

  - 通过对@符号的分割,将全量表和增量表的表名存储在不同的列表中

连接代码测试

- 启动虚拟运行环境

黑马一站制造数仓实战3-LMLPHP

运行测试代码

def getOracleConn():
    """
    用户获取Oracle的连接对象:cx_Oracle.connect(host='', port='', username='', password='', param='')
    :return:
    """
    oracleConn = None   #构建Oracle连接对象
    try:
        ORACLE_HOST = ConfigLoader.getOracleConfig('oracleHost')                # 获取Oracle连接的主机地址
        ORACLE_PORT = ConfigLoader.getOracleConfig('oraclePort')                # 获取Oracle连接的端口
        ORACLE_SID = ConfigLoader.getOracleConfig('oracleSID')                  # 获取Oracle连接的SID
        ORACLE_USER = ConfigLoader.getOracleConfig('oracleUName')               # 获取Oracle连接的用户名
        ORACLE_PASSWORD = ConfigLoader.getOracleConfig('oraclePassWord')        # 获取Oracle连接的密码
        # 构建DSN
        dsn = cx_Oracle.makedsn(ORACLE_HOST, ORACLE_PORT, ORACLE_SID)
        # 获取真正的Oracle连接
        oracleConn = cx_Oracle.connect(ORACLE_USER, ORACLE_PASSWORD, dsn)
    # 异常处理
    except cx_Oracle.Error as error:
        print(error)
    # 返回Oracle连接
    return oracleConn


def getSparkHiveConn():
    """
    用户获取SparkSQL的连接对象
    :return:
    """
    # 构建SparkSQL的连接对象
    sparkHiveConn = None
    try:
        SPARK_HIVE_HOST = ConfigLoader.getSparkConnHiveConfig('sparkHiveHost')              # 获取Spark连接的主机地址
        SPARK_HIVE_PORT = ConfigLoader.getSparkConnHiveConfig('sparkHivePort')              # 获取Spark连接的端口
        SPARK_HIVE_UNAME = ConfigLoader.getSparkConnHiveConfig('sparkHiveUName')            # 获取Spark连接的用户名
        SPARK_HIVE_PASSWORD = ConfigLoader.getSparkConnHiveConfig('sparkHivePassWord')      # 获取Spark连接的密码
        # 获取一个Spark TriftServer连接对象
        sparkHiveConn = hive.Connection(host=SPARK_HIVE_HOST, port=SPARK_HIVE_PORT, username=SPARK_HIVE_UNAME, auth='CUSTOM', password=SPARK_HIVE_PASSWORD)
    # 异常处理
    except Exception as error:
        print(error)
    # 返回连接对象
    return sparkHiveConn


def readFileContent(fileName):
    """
    加载表名所在的文件
    :param fileName:存有表名的文件路径
    :return:存有所有表名的列表集合
    """
    # 定义一个空的列表,用于存放表名,最后返回
    tableNameList = []
    # 打开一个文件
    fr = open(fileName)
    # 遍历每一行
    for line in fr.readlines():
        # 将每一行尾部的换行删掉
        curLine = line.rstrip('\n')
        # 把表名放入列表
        tableNameList.append(curLine)
    # 返回所有表名的列表
    return tableNameList


def getODSTableNameList(fileNameList):
    """
    基于传递的所有表名,将增量表与全量表进行划分到不同的列表中
    :param fileNameList: 所有表名的列表
    :return: 增量与全量列表
    """
    # 定义全量空列表
    full_list = []
    # 定义增量空列表
    incr_list = []
    # 用于返回的结果列表
    result_list = []
    # 定义一个bool值,默认为true
    isFull = True
    # 取出集合中的每一个表名
    for line in fileNameList:
        # 如果isFull = True
        if isFull:
            # 如果当前取到的表名为@
            if "@".__eq__(line):
                # 将 isFull = False
                isFull = False
                # 跳过本次循环
                continue
            # 将表名放入全量表的列表中
            full_list.append(line)
        # 如果isFull = False
        else:
            # 将表名放入增量列表
            incr_list.append(line)
    # 将全量列表和增量列表放入一个结果列表中
    result_list.append(full_list)
    result_list.append(incr_list)
    # 返回结果列表
    return result_list
if __name__ == '__main__':

    # =================================todo: 1-初始化Oracle、Hive连接,读取表的名称=========================#
    # 输出信息
    recordLog('ODS&DWD Building AND Load Data')
    # 定义了一个分区变量:指定当前要操作的Hive分区的值为20210101
    partitionVal = '20210101'
    # 调用了获取连接的工具类,构建一个Oracle连接
    oracleConn = OracleHiveUtil.getOracleConn()
    # 调用了获取连接的工具类,构建一个SparkSQL连接
    hiveConn = OracleHiveUtil.getSparkHiveConn()
    # 调用了文件工具类读取表名所在的文件:将所有表的名称放入一个列表:List[102个String类型的表名]
    tableList = FileUtil.readFileContent("D:\\PythonProject\\OneMake30\\dw\\ods\\meta_data\\tablenames.txt")
    # 调用工具类,将全量表的表名存入一个列表,将增量表的表名存入另外一个列表中,再将这两个列表放入一个列表中:List[2个List元素:List1[44张全量表的表名],List2[57张增量表的表名]]
    tableNameList = TableNameUtil.getODSTableNameList(tableList)
    # ------------------测试:输出获取到的连接以及所有表名
    print(oracleConn)
    print(hiveConn)
    for tbnames in tableNameList:
        print("---------------------")
        for tbname in tbnames:
            print(tbname)

- 执行代码观察结果

黑马一站制造数仓实战3-LMLPHP

ODS建库代码及实现测试

step1:ODS层的数据库名称叫什么?

- step2:如何使用PyHive创建数据库?

  - 第一步:先获取连接
  - 第二步:拼接SQL语句,从连接对象中获取一个游标
  - 第三步:使用游标执行SQL语句
  - 第四步:释放资源

class CHiveTableFromOracleTable:

    # 构建当前类的对象时,初始化Oracle和Hive的连接
    def __init__(self, oracleConn, hiveConn):
        self.oracleConn = oracleConn
        self.hiveConn = hiveConn

    # 创建数据库方法
    def executeCreateDbHQL(self, dbName):
        """
        根据传递的数据库名称,在Hive中创建数据库
        :param dbName: 数据库名称
        :return: None
        """
        # 拼接建库的SQL语句
        createDbHQL = 'create database if not exists ' + dbName
        # 从SparkSQL连接获取一个游标:理解为执行SQL语句的对象
        cursor = self.hiveConn.cursor()
        try:
            # 使用游标对象执行SQL语句
            cursor.execute(createDbHQL)
        # 异常处理
        except hive.Error as error:
            print(error)
        # 执行结束,最后释放游标
        finally:
            if cursor:
                cursor.close()
    # =================================todo: 2-ODS层建库=============================================#
    # 构建了一个建库建表的类的对象:实例化的时候给连接赋值
    cHiveTableFromOracleTable = CHiveTableFromOracleTable(oracleConn, hiveConn)
    # 打印日志
    recordLog('ODS层创建数据库')
    # 调用这个类的创建数据库的方法:传递ODS层数据库的名称
    cHiveTableFromOracleTable.executeCreateDbHQL(CreateMetaCommon.ODS_NAME)

运行代码,查看结果

黑马一站制造数仓实战3-LMLPHP

ODS建表代码及实现测试

表名怎么获取?

step2:建表的语句是什么,哪些是动态变化的?

create external table 数据库名称.表名
comment '表的注释'
partitioned by
ROW FORMAT SERDE
  'org.apache.hadoop.hive.serde2.avro.AvroSerDe'
STORED AS INPUTFORMAT
  'org.apache.hadoop.hive.ql.io.avro.AvroContainerInputFormat'
OUTPUTFORMAT
  'org.apache.hadoop.hive.ql.io.avro.AvroContainerOutputFormat'
location '这张表在HDFS上的路径'
TBLPROPERTIES ('这张表的Schema文件在HDFS上的路径')

- - 表名
  - 表的注释
  - 表的HDFS地址
  - Schema文件的HDFS地址
- step3:怎么获取表的注释?
  - 从Oracle中获取:从系统表中获取某张表的信息和列的信息

select
       columnName, dataType, dataScale, dataPercision, columnComment, tableComment
from
(
    select
           column_name columnName,
           data_type dataType,
           DATA_SCALE dataScale,
           DATA_PRECISION dataPercision,
           TABLE_NAME
    from all_tab_cols where 'CISS_CSP_WORKORDER' = table_name) t1
    left join (
        select
               comments tableComment,TABLE_NAME
        from all_tab_comments WHERE 'CISS_CSP_WORKORDER' = TABLE_NAME) t2
        on t1.TABLE_NAME = t2.TABLE_NAME
    left join (
        select comments columnComment, COLUMN_NAME
        from all_col_comments WHERE TABLE_NAME='CISS_CSP_WORKORDER') t3
        on t1.columnName = t3.COLUMN_NAME;

黑马一站制造数仓实战3-LMLPHP

- step4:全量表与增量表有什么区别?

  - 区别1:表名不一样
    - full_table_list
    - incr_table_list
  - 区别2:路径不一样
    - `/data  /dw  /ods /one_make /full  /Oracle库名.表名`
    - `/data  /dw  /ods /one_make /incr  /Oracle库名.表名`

- step5:如何实现自动化建表?

  - 自动化创建全量表
    - 获取全量表名
    - 调用建表方法:数据库名称、表名、全量标记
    - 通过Oracle工具类获取表的信息【表的名称、表的注释、字段信息等】
    - 拼接建表语句
    - 执行SQL语句
  - 自动化创建增量表
    - 获取增量表名
    - 调用建表方法:数据库名称、表名、增量标记
    - 通过Oracle工具类获取表的信息【表的名称、表的注释、字段信息等】
    - 拼接建表语句
    - 执行SQL语句

运行代码,查看结果

    # 执行Hive建表
    def executeCreateTableHQL(self, dbName, tableName, dynamicDir):
        """
        用于根据传递的数据库名称、表名在Hive中创建对应的表,self为当前类的实例对象
        :param dbName: 数据库名称【ODS、DWD】
        :param tableName: 表名
        :param dynamicDir: 全量或者增量【full_imp、incr_imp】
        :return: None
        """
        # 构建一个空的列表:用于拼接字符串:SQL语句
        buffer = []
        # 构建一个游标对象
        cursor = None
        try:
            # 调用工具类,从Oracle中获取这张表的元数据【表的信息 = 表名 + 表的注释 + list[列的信息]】
            tableMeta = OracleMetaUtil.getTableMeta(self.oracleConn, tableName.upper())
            # 拼接SQL:create external table if not exists one_make_ods.
            buffer.append("create external table if not exists " + dbName + ".")
            # 拼接SQL:CISS_CSP_WORKORDER
            buffer.append(tableName.lower())
            # 拼接SQL:列的信息,【ODS层不执行】,DWD层用于拼接每一列信息
            buffer = getODSStringBuffer(buffer, dbName, tableMeta)
            # 拼接SQL:如果表有注释,就将表的注释拼接到建表语句中
            if tableMeta.tableComment:
                buffer.append(" comment '" + tableMeta.tableComment + "' \n")
            # 拼接SQL:指定分区
            buffer.append(' partitioned by (dt string) ')
            # 拼接SQL:Schema的路径以及文件的存储格式
            buffer.append(CreateMetaCommon.getTableProperties(dbName, tableName))
            # ODS => ods,DWD => dwd
            dbFolderName = CreateMetaCommon.getDBFolderName(dbName)
            # ODS => ciss4. ,DWD => 空
            userName = CreateMetaCommon.getUserNameByDBName(dbName)
            # 拼接SQL:location
            buffer.append(" location '/data/dw/" + dbFolderName + "/one_make/" + CreateMetaCommon.getDynamicDir(dbName,dynamicDir) + "/" + userName + tableName + "'")
            # 获取SparkSQL的游标
            cursor = self.hiveConn.cursor()
            # 执行SQL语句
            cursor.execute(''.join(buffer))
            logging.warning(f'oracle表转换{dbFolderName}后的Hive DDL语句为:\n{"".join(buffer)}')
        # 异常处理
        except Exception as exception:
            print(exception)
        # 释放游标
        finally:
            if cursor:
                cursor.close()
def getTableMeta(oracleConn, tableName) -> TableMeta:
    """
    用于读取Oracle中表的信息【表名、列的信息、表的注释】封装成TableMeta
    :param oracleConn: Oracle连接对象
    :param tableName: 表的名称
    :return:
    """
    # 从连接中获取一个游标【SQL对象】
    cursor = oracleConn.cursor()
    try:
        # 定义Oracle查询表信息的SQL语句
        oracleSql = f"""select columnName, dataType, dataScale, dataPercision, columnComment, tableComment from
(select column_name columnName,data_type dataType, DATA_SCALE dataScale,DATA_PRECISION dataPercision, TABLE_NAME
from all_tab_cols where '{tableName}' = table_name) t1
left join (select comments tableComment,TABLE_NAME from all_tab_comments WHERE '{tableName}' = TABLE_NAME) t2 on t1.TABLE_NAME = t2.TABLE_NAME
left join (select comments columnComment, COLUMN_NAME from all_col_comments WHERE TABLE_NAME='{tableName}') t3 on t1.columnName = t3.COLUMN_NAME
"""
        # 记录运行的SQL语句
        logging.warning(f'query oracle table {tableName} metadata sql:\n{oracleSql}')
        # 执行SQL语句
        cursor.execute(oracleSql)
        # 获取执行的结果
        resultSet = cursor.fetchall()
        # 构建返回的表的信息对象:表名 + 列的信息 + 表的注释
        tableMeta = TableMeta(f'{tableName}', '')
        # 表信息 = 表名 + 表的注释 + list[列的信息]
        for line in resultSet:
            # 获取每一列的信息
            columnName = line[0]        # 获取列的名称
            dataType = line[1]          # 获取列的类型
            dataScale = line[2]         # 获取列值长度
            dataScope = line[3]         # 获取列值精度
            columnComment = line[4]     # 获取列的注释
            tableComment = line[5]      # 获取表的注释
            if dataScale is None:       # 如果列值的长度为空,则设置为0
                dataScale = 0
            if dataScope is None:       # 如果列值的精度为空,则设置为0
                dataScope = 0
            # 将每条数据封装成一个列的信息对象【列名 + 类型 + 长度 + 精度 + 注释】
            columnMeta = ColumnMeta(columnName, dataType, columnComment, dataScope, dataScale)
            # 将列的信息添加到表的对象中
            tableMeta.addColumnMeta(columnMeta)
            # 将表的注释添加到表的对象中
            tableMeta.tableComment = tableComment
        # 返回当前表的所有信息【表名 + 所有列的信息 + 表的注释】
        return tableMeta
    # 异常处理
    except cx_Oracle.Error as error:
        print(error)
    # 关闭游标
    finally:
        if cursor:
            cursor.close()
    # =================================todo: 3-ODS层建表=============================================#
    # 打印日志
    recordLog('ODS层创建全量表...')
    # 从表名的列表中取出第一个元素:全量表名的列表
    fullTableList = tableNameList[0]
    # 取出每张全量表的表名
    for tblName in fullTableList:
    #     # 创建全量表:ODS层数据库名称,全量表的表名,full_imp
        cHiveTableFromOracleTable.executeCreateTableHQL(CreateMetaCommon.ODS_NAME, tblName, CreateMetaCommon.FULL_IMP)
    # 打印日志
    recordLog('ODS层创建增量表...')
    # # 从表名的列表中取出第二个元素:增量表名的列表
    incrTableList = tableNameList[1]
    # 取出每张增量表的表名
    for tblName in incrTableList:
    #     # Hive中创建这张增量表:ODS层数据库名称,增量表的表名,incr_imp
        cHiveTableFromOracleTable.executeCreateTableHQL(CreateMetaCommon.ODS_NAME, tblName, CreateMetaCommon.INCR_IMP)

黑马一站制造数仓实战3-LMLPHP

ODS申明分区的代码及实现测试

  • step1:为什么要申明分区?
    • 表的分区数据由Sqoop采集到HDFS生成AVRO文件

    • HiveSQL基于表的目录实现了分区表的创建

create external table if not exists one_make_ods.ciss_base_areas 
partitioned by (dt string) 
ROW FORMAT SERDE 'org.apache.hadoop.hive.serde2.avro.AvroSerDe'
STORED AS INPUTFORMAT 'org.apache.hadoop.hive.ql.io.avro.AvroContainerInputFormat'
OUTPUTFORMAT 'org.apache.hadoop.hive.ql.io.avro.AvroContainerOutputFormat'
tblproperties ('avro.schema.url'='hdfs:///data/dw/ods/one_make/avsc/CISS4_CISS_BASE_AREAS.avsc')
location '/data/dw/ods/one_make/full_imp/ciss4.ciss_base_areas'
    • 但是Hive中没有对应分区的元数据,无法查询到数据

- step2:怎么申明分区?

  - Alter Table

    alter table 表名 add if not exists partition (dt='值')
    location 'HDFS上的分区路径'

  - 例如

    alter table one_make_ods.ciss_base_areas add if not exists partition (dt='20210101')
    location '/data/dw/ods/one_make/full_imp/ciss4.ciss_base_areas/20210101'

- step3:如何自动化实现每个表的分区的申明?

  - 获取分区工具类实例
  - 调用申明分区的方法
    - 对所有全量表调用申明分区的方法:数据库名称、表名、全量标记、分区值
    - 对所有增量表调用申明分区的方法:数据库名称、表名、增量标记、分区值
  - 拼接SQL
  - 执行SQL

代码测试

    # =================================todo: 4-ODS层申明分区=============================================#
    recordLog('创建ods层全量表分区...')
    # 构建专门用于申明分区的类的对象
    createHiveTablePartition = CreateHiveTablePartition(hiveConn)
    # 全量表执行44次创建分区操作
    for tblName in fullTableList:
    #     # 调用申明分区的方法申明全量表的分区:ods层数据库名称、表名、full_imp,20210101
        createHiveTablePartition.executeCPartition(CreateMetaCommon.ODS_NAME, tblName, CreateMetaCommon.FULL_IMP, partitionVal)

    recordLog('创建ods层增量表分区...')
    # 增量表执行57次创建分区操作
    for tblName in incrTableList:
        createHiveTablePartition.executeCPartition(CreateMetaCommon.ODS_NAME, tblName, CreateMetaCommon.INCR_IMP, partitionVal)
    def executeCPartition(self, dbName, hiveTName, dynamicDir, partitionDT):
        """
        用于实现给Hive表的数据手动申明分区
        :param dbName: 数据库名称
        :param hiveTName: 表名称
        :param dynamicDir: 全量或者增量
        :param partitionDT: 分区值
        :return: None
        """
        # 构建空的列表,拼接SQL语句
        buffer = []
        # 定义一个游标
        cursor = None
        try:
            # SQL拼接:alter table one_make_ods.
            buffer.append("alter table " + dbName + ".")
            # SQL拼接:表名
            buffer.append(hiveTName)
            # SQL拼接:add if not exists partition (dt='
            buffer.append(" add if not exists partition (dt='")
            # SQL拼接:20210101
            buffer.append(partitionDT)
            # SQL拼接:') location 'data/dw/ods/one_make/full_imp/ciss4.'
            buffer.append("') location '/data/dw/" + CreateMetaCommon.getDBFolderName(dbName) +
                          "/one_make/" + CreateMetaCommon.getDynamicDir(dbName, dynamicDir) + "/ciss4.")
            # SQL拼接:表名
            buffer.append(hiveTName)
            # SQL拼接:/
            buffer.append("/")
            # SQL拼接:分区目录
            buffer.append(partitionDT)
            buffer.append("'")
            # 实例化SparkSQL游标
            cursor = self.hiveConn.cursor()
            # 执行SQL语句
            cursor.execute(''.join(buffer))
            # 输出日志
            logging.warning(f'执行创建hive\t{hiveTName}表的分区:{partitionDT},\t分区sql:\n{"".join(buffer)}')
        # 异常处理
        except Exception as e:
            print(e)
        # 释放游标
        finally:
            if cursor:
                cursor.close()
06-01 11:30