作者:season
文章大綱
0.簡介
想象如下一個場景,一個合作伙伴想讓你分析一下自己的業務數據,比較慷慨的給出了數據全庫。
下面就以Oracle 爲例,使用python 進行全庫數據描述性及探索性逆向分析。
1. cx_Oracle 簡介與數據類型
說到python 鏈接Oracle ,就不得不提到cx_Oracle ,cx_Oracle is a module that enables access to Oracle Database and conforms to the Python database API specification.
Oracle - cx_Oracle - Python 映射爲:
Oracle | cx_Oracle | Python |
VARCHAR2 NVARCHAR2 LONG |
cx_Oracle.STRING |
str |
CHAR |
cx_Oracle.FIXED_CHAR |
|
NUMBER |
cx_Oracle.NUMBER |
int |
FLOAT |
float |
|
DATE |
cx_Oracle.DATETIME |
datetime.datetime |
TIMESTAMP |
cx_Oracle.TIMESTAMP |
|
CLOB |
cx_Oracle.CLOB |
cx_Oracle.LOB |
BLOB |
cx_Oracle.BLOB |
2.Oracle 12c 新特性容器數據庫
一般來說對於Oracle 高版本的數據庫是向下兼容的,所以我們目前使用Oracle 12c 進行本次說明。
Oracle 12C引入了CDB與PDB的新特性,在ORACLE 12C數據庫引入的多租用戶環境(Multitenant Environment)中,允許一個數據庫容器(CDB)承載多個可插拔數據庫(PDB)。
3.Oracle 12c 新建表空間、用戶、表
在windows 下,我們使用PL/sql 以及sqlplus 進行Oracle 的管理工作,sqlplus 是安裝好Oracle 就自帶了。
3.0 設置oracle sid 數據庫實例名
在cmd 命令行窗口使用sqlplus 之前需要進行數據庫實例名 的指定。
set oracle_sid=orcl
3.1 以管理員賬戶登錄
sqlplus sys/sys as sysdba;
3.2 創建表空間
創建用戶之前需要創建表空間。
# -- 2.1 創建臨時空間
create temporary tablespace test
tempfile 'E:\table\test.dbf'
size 5m
autoextend on
next 10m
extent management local;
# -- 2.2 創建數據表空間
create tablespace test_data
logging
datafile 'E:\table\test_data.dbf'
size 10m
autoextend on
next 10m
maxsize unlimited
extent management local;
3.3 創建用戶並指定表空間
有了表空間,我們可以在創建用戶的時候給用戶指定表空間。
# -- 3.創建用戶並指定表空間
-- 剛開始用戶名爲 ,提示錯誤ORA-65096:公用用戶名或角色名無效,網上查資料,說是取名前綴必須爲c##,
--所以用戶名也變成了c##test
--首次創建用戶時提示test_data表空間不存,重啓了服務就創建成功
create user c##test identified by test
default tablespace test_data
temporary tablespace test;
3.4 用戶授權
根據需要設置權限
GRANT CREATE ANY VIEW,DROP ANY VIEW,CONNECT,RESOURCE,CREATE SESSION,DBA TO c##test;
3.5 創建樣例表格
爲了我們後面的分析方便,我們自己創建兩個樣例表格進行舉例,其實真實的情況一般是 參照第6小節數據導入導出,進行原始數據的,導入導出。
注意創建表的時候添加了comment ,這樣方便我們DBA 或者逆向探索時候能夠理解表格的含義。一般的真實情況是,數據庫建表過程中,良好習慣的DBA 會按照一定的命名規範建表,命名字段及編寫註釋。 這就給我們逆向理解合作伙伴的業務提供了便利條件。
--建立表 DEPT和刪除表;
DROP TABLE DEPT cascade constraints;
CREATE TABLE DEPT
(DEPTNO NUMBER(2) CONSTRAINT PK_DEPT PRIMARY KEY,
DNAME VARCHAR2(14) ,
LOC VARCHAR2(13) ) ;
-- 增加表註釋
comment on table C##TEST.DEPT
is '部門表';
comment on column C##TEST.DEPT.deptno
is '部門編號';
comment on column C##TEST.DEPT.dname
is '部門名稱';
comment on column C##TEST.DEPT.loc
is '部門位置';
--建立表 EMP和刪除表;
DROP TABLE EMP;
CREATE TABLE EMP
(EMPNO NUMBER(4) CONSTRAINT PK_EMP PRIMARY KEY,
ENAME VARCHAR2(10),
JOB VARCHAR2(9),
MGR NUMBER(4),
HIREDATE DATE,
SAL NUMBER(7,2),
COMM NUMBER(7,2),
DEPTNO NUMBER(2) CONSTRAINT FK_DEPTNO REFERENCES DEPT);
---插入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');
---插入EMP語句塊;
INSERT INTO EMP VALUES
(7369,'SMITH','CLERK',7902,to_date('17-12-1980','dd-mm-yyyy'),800,NULL,20);
INSERT INTO EMP VALUES
(7499,'ALLEN','SALESMAN',7698,to_date('20-2-1981','dd-mm-yyyy'),1600,300,30);
INSERT INTO EMP VALUES
(7521,'WARD','SALESMAN',7698,to_date('22-2-1981','dd-mm-yyyy'),1250,500,30);
INSERT INTO EMP VALUES
(7566,'JONES','MANAGER',7839,to_date('2-4-1981','dd-mm-yyyy'),2975,NULL,20);
INSERT INTO EMP VALUES
(7654,'MARTIN','SALESMAN',7698,to_date('28-9-1981','dd-mm-yyyy'),1250,1400,30);
INSERT INTO EMP VALUES
(7698,'BLAKE','MANAGER',7839,to_date('1-5-1981','dd-mm-yyyy'),2850,NULL,30);
INSERT INTO EMP VALUES
(7782,'CLARK','MANAGER',7839,to_date('9-6-1981','dd-mm-yyyy'),2450,NULL,10);
INSERT INTO EMP VALUES
(7788,'SCOTT','ANALYST',7566,to_date('12-06-1987','dd-mm-yyyy')-85,3000,NULL,20);
INSERT INTO EMP VALUES
(7839,'KING','PRESIDENT',NULL,to_date('17-11-1981','dd-mm-yyyy'),5000,NULL,10);
INSERT INTO EMP VALUES
(7844,'TURNER','SALESMAN',7698,to_date('8-9-1981','dd-mm-yyyy'),1500,0,30);
INSERT INTO EMP VALUES
(7876,'ADAMS','CLERK',7788,to_date('13-06-1987','dd-mm-yyyy')-51,1100,NULL,20);
INSERT INTO EMP VALUES
(7900,'JAMES','CLERK',7698,to_date('3-12-1981','dd-mm-yyyy'),950,NULL,30);
INSERT INTO EMP VALUES
(7902,'FORD','ANALYST',7566,to_date('3-12-1981','dd-mm-yyyy'),3000,NULL,20);
INSERT INTO EMP VALUES
(7934,'MILLER','CLERK',7782,to_date('23-1-1982','dd-mm-yyyy'),1300,NULL,10);
-- 提交插入
COMMIT;
--查詢部分;
select * from emp;
select * from dept;
3.6 數據導入導出
imp/exp ,impdp/expdp需要成對使用
以下分別給出兩個導入樣例
imp c##test/test@orcl file=D:20190506.DMP full=y log=01.log
impdp c##test/test@orcl directory=dir logfile=p_street_area.log job_name=my_job
4.python 環境準備
使用如下 requirements.txt 初始化環境
conda create --name DATABASE --file requirements.txt
# This file may be used to create an environment using:
# $ conda create --name <env> --file <this file>
# platform: win-64
backcall=0.1.0=py37_0
blas=1.0=mkl
ca-certificates=2019.1.23=0
certifi=2019.3.9=py37_0
colorama=0.4.1=py37_0
cx_oracle=7.0.0=py37h62dcd97_0
decorator=4.4.0=py37_1
icc_rt=2019.0.0=h0cc432a_1
。。。
# 爲了能讓jupyter 使用這個 kernel
#直接 切換到 需要顯示 的 隔離環境
conda install ipykernel
#or
conda install -n python_env ipykernel
5.Oracle SQL 全庫全表字段分析
在Oracle 中進行 全庫全表字段分析需要用的一個非常重要的表:USER_TABLES
什麼是USER_TABLES ?
USER_TABLES describes the relational tables owned by the current user. Its columns (except for OWNER) are the same as those in ALL_TABLES.
USER_TABLES 和ALL_TABLES 字段一樣,我們來看看文檔裏面ALL_TABLES 有哪些有用的字段:
https://docs.oracle.com/en/database/oracle/oracle-database/12.2/refrn/ALL_TABLES.html#GUID-6823CD28-0681-468E-950B-966C6F71325D
對我們有用的字段拿幾個先看看:
SELECT a.num_rows, a.table_name, b.comments
FROM user_tables a, user_tab_comments b
WHERE a.table_name = b.table_name
ORDER BY num_rows DESC
可以看到 寫了註釋的表,都展現出來註釋了。
可以看到剛剛插入完數據,num_rows 沒有更新
隔了一天以後, 數據就有了:
如果想要立即更新USER_TABLES 或者參照下面鏈接
https://stackoverflow.com/questions/16380732/oracle-manually-update-statistics-on-all-tables
運行,手動更新:
exec DBMS_STATS.GATHER_DATABASE_STATS;
一般來說,USER_TABLES不會自動更新,oracle 會在閒時或者定時更新這張表。所以入數據以後不一定 多久會看到USER_TABLES 的更新。
SELECT t_column_comments.table_name,
t_table_comments.comments 表名,
t_table_comments.num_rows 錶行數,
t_table_comments.avg_row_len 表平均長度,
t_column_comments.column_name,
t_column_comments.comments 字段名
FROM (SELECT *
FROM all_col_comments
WHERE table_name IN (SELECT table_name FROM user_tables)) t_column_comments,
(SELECT a.num_rows, a.table_name, b.comments, a.avg_row_len
FROM user_tables a, user_tab_comments b
WHERE a.table_name = b.table_name) t_table_comments
WHERE t_table_comments.table_name = t_column_comments.table_name
ORDER BY t_column_comments.table_name
可以看到如下的導出表基本上符合人的觀察規範,適合進行Oracle 全庫的描述性、探索性數據分析。比如合作伙伴將全庫共享,我們如何第一時間通過數據瞭解合作伙伴的業務情況和設計呢。我想可以通過這樣的手段,首先有一個大致的認識,接下來就是進一步看看樣例數據的樣子了。那麼我們用這個導出表作爲基礎,寫點python代碼進一步進行數據探索性分析。
6.python 鏈接Oracle 全庫數據採樣
本節主要用到了上面的操作類,使用oracle 的user_tables 獲取數據的所有表名稱,之後按照採樣設置進行鏈接及採樣,並根據採樣數據計算數據缺失率,以求初步瞭解數據和業務的緊密關聯。最後用pandas 保存爲excel 方便查看。
其中採樣的功能主要用到了Oracle 中的sample 函數,具體大家可以查看文檔:
https://docs.oracle.com/en/database/oracle/oracle-database/12.2/tgsql/optimizer-access-paths.html#GUID-720EA54F-AB65-4379-99A3-CAE166590127
以下腳本主要有兩大功能:
- 各個表中數據列缺失值統計(採樣缺失值,如採樣10000條)
- 從各個表中獲取數據樣例
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
#-------------------------------------------------------------------------------
'''
@Author : {SEASON}
@License : (C) Copyright 2013-2022, {OLD_IT_WANG}
@Contact : {[email protected]}
@Software: PyCharm
@File : DataBase -- GetSampleData
@Time : 2019/5/22 15:41
@Desc :
'''
#-------------------------------------------------------------------------------
import pandas as pd
# from __future__ import print_function
import cx_Oracle
import OracleBaseTool
import os
#應對出現 illegal multibyte sequence 問題
os.environ['nls_lang'] = 'AMERICAN_AMERICA.AL32UTF8'
#生成數據庫所有表名、表名註釋及行數
sql_string_all_tables = '''
SELECT a.num_rows, a.table_name, b.comments
FROM user_tables a, user_tab_comments b
WHERE a.table_name = b.table_name
ORDER BY num_rows DESC
'''
# sql_string2= '''select a.num_rows, a.TABLE_NAME, b.COMMENTS from user_tables a, user_tab_comments b
# WHERE a.TABLE_NAME = b.TABLE_NAME and a.TABLE_NAME = :content
# order by num_rows desc'''
#named_params = {'content': 'MZ_FYMXB'}
# 傳參的sql語句寫法
# result_list= HIS_oracle_object.selectFromDbTable(sql_string2,named_params)
#生成數據庫所有表名、表名註釋及行數,字段名,字段註釋
sql_string_all_columns = '''
SELECT t_column_comments.table_name,
t_table_comments.comments 表名,
t_table_comments.num_rows 錶行數,
t_table_comments.avg_row_len 表平均長度,
t_column_comments.column_name,
t_column_comments.comments 字段名
FROM (SELECT *
FROM all_col_comments
WHERE table_name IN (SELECT table_name FROM user_tables)) t_column_comments,
(SELECT a.num_rows, a.table_name, b.comments, a.avg_row_len
FROM user_tables a, user_tab_comments b
WHERE a.table_name = b.table_name) t_table_comments
WHERE t_table_comments.table_name = t_column_comments.table_name
ORDER BY t_column_comments.table_name
'''
# 鏈接oracle
test_oracle_obj = OracleBaseTool.OracleBaseTool(
'c##test', 'test', '127.0.0.1', 'orcl')
result_list = test_oracle_obj.selectFromDbTable(sql_string_all_tables)
result_list_schemaDetail = test_oracle_obj.selectFromDbTable_WithTableHead(
sql_string_all_columns)
result_list_schemaDetail_pdf = pd.DataFrame(
result_list_schemaDetail[1:], columns=result_list_schemaDetail[0])
# 設置採樣數據組數,即爲 從表中讀取幾條樣例數據
sample_number = 1000
result_list_schemaDetail_pdf['缺失值比例'] = None
# 採樣5個sample data 作爲column name
for i in range(1, 5+1):
result_list_schemaDetail_pdf['sample_data'+str(i)] = None
# 獲取5條樣例數據,遍歷每一張表
for x in result_list:
table_row_number = x[0]
table_name = x[1]
table_comments = x[2]
if table_row_number > sample_number*10:
#大於10000行的表進行採樣
#select * from table_name sample(10) where rownum<=5
sql_string_forsampledata = '''select * from ''' + table_name + ''' sample(10) where rownum<=1000'''
else:
#小於10000行的表 隨便選
sql_string_forsampledata = '''select * from ''' + table_name
result_list_sampleData = test_oracle_obj.selectFromDbTablefor_SampleData(
sql_string_forsampledata, None, sample_number)
result_list_sampleData_pdf = pd.DataFrame(
result_list_sampleData[1:], columns=result_list_sampleData[0])
# 將 採樣的5個樣例值寫入後面
# 不一定有10000條數據
樣例數據條數 = len(result_list_sampleData)-1
列數量 = len(result_list_sampleData[0])
for column_number in range(0, 列數量):
#獲取到table_name 及string_column_name 對應的行號
string_column_name = result_list_sampleData[0][column_number]
index_number = result_list_schemaDetail_pdf[
(result_list_schemaDetail_pdf['TABLE_NAME'] == table_name) & (
result_list_schemaDetail_pdf['COLUMN_NAME'] == string_column_name)].index
# 計算該column 的缺失值比例
缺失值比例_dict = dict(result_list_sampleData_pdf.isnull().sum() / 樣例數據條數)
result_list_schemaDetail_pdf.loc[index_number,
'缺失值比例'] = 缺失值比例_dict[string_column_name]
if 樣例數據條數 > 5:
int_sample = 5
else:
int_sample = 樣例數據條數
for x in range(1, int_sample+1):
#對該 column 進行數據採樣,並寫入pandas 對應位置
str_sample_data_column_name = 'sample_data' + str(x)
sample_data_column_value = result_list_sampleData[x][column_number]
result_list_schemaDetail_pdf.loc[index_number,
str_sample_data_column_name] = sample_data_column_value
最後一步寫入excel ,結合excel 的一些篩選統計工作,我們可以讓協助的業務部門,架構部門也更好的瞭解整個合作伙伴的數據
代碼如下:使用前記得安裝 conda install openpyxl
# pandas to excel 由於是第三方庫,寫的時候可能報錯,如
# "'utf8' codec can't decode byte 0xe9 in position 1: invalid continuation byte"
# 所以一般強制,字符集寫成 utf-8
writer = pd.ExcelWriter('output_test.xlsx')
result_list_schemaDetail_pdf.to_excel(writer, 'schema')
writer.save()
7.python missingno 缺失值可視化分析
主要用到missingno 對缺失值進行可視化分析,what is missingno
missingno provides a small toolset of flexible and easy-to-use missing data visualizations and utilities that allows you to get a quick visual summary of the completeness (or lack thereof) of your dataset.
github 鏈接
https://github.com/ResidentMario/missingno
對於我們的測試庫, 以下代碼運行在jupyter notebook 中
test_oracle_obj = OracleBaseTool.OracleBaseTool('c##test','test','127.0.0.1','orcl')
sql_string = '''select * from EMP'''
result_list = test_oracle_obj.selectFromDbTable_WithTableHead(sql_string)
%matplotlib inline
import missingno
pdf = pd.DataFrame(result_list[1:], columns = result_list[0] )
missingno.matrix(pdf, labels=True)
柱狀圖分析,該圖按照數據排序,可視化了數據空缺的空間視圖。
條形圖, 該圖展現缺失值數量對比情況。
missingno.bar(pdf)
缺失值的相關性分析,既 一個變量的缺失和另一個變量 的關係,由於我們的樣例數據較少,所以效果不明顯,我們同時看一個官網的例子。
missingno.heatmap(pdf)
缺失值的層次聚類分析,內在邏輯和上面類似,不過是用了不同的算法及展現形式。
missingno.dendrogram(pdf)