[數據庫管理]SQL表定義查詢與數據字典的導出

最後一次更新日期:2019-5-28python

分享一段簡短的python腳本,可用於查詢sql數據庫表定義以及導出數據字典excel文檔。mysql

支持mysql和mssql(SQL Server),因爲本人使用mysql較少,因此mysql的查詢內容相對簡單,若有須要能夠自行增長。sql

效果圖:

完整代碼:

# -*- coding: utf-8 -*-
import pandas as pd
import sqlalchemy as sqla
from openpyxl import load_workbook
from openpyxl.styles import Font,colors,Border,Side,Alignment,PatternFill
   
class SqlDataDictionary:
    '''\n Class: SQL數據字典 Parameters ---------- dbtype: 數據庫類型,僅支持'mysql'和'mssql' username: 用戶名 password: 密碼 server: 服務器地址 database: 數據庫名,不設置該參數時會鏈接默認數據庫, 'mysql'默認'sys','mssql'默認'master' charset: 字符集,默認'utf8' driver: 驅動名,不設置該參數時會使用默認驅動, 'mysql'默認'pymysql','mssql'默認'pymssql' engine: sqlalchemy的鏈接引擎,設置此項能夠代替前面全部的參數 ---------- Attributes ---------- engine_: sqlalchemy的鏈接引擎 excel_col_width_: excel文檔的列寬設置 excel_border_: excel文檔的邊框設置 excel_font_: excel文檔的字體設置 excel_align_: excel文檔的對齊設置 excel_fill_: excel文檔的填充設置 ---------- '''
    def __init__(self,dbtype=None,username=None,password=None,server=None, database=None,charset='utf8',driver=None,engine=None):
        
        #生成engine
        if type(engine)==type(None):
            engine=self._create_engine(dbtype,driver,username,password,
                                       server,database,charset)
        self.engine_=engine
        
        #excel文檔列設置
        self.excel_col_width_={
                'TABLE_CATALOG': 20,
                'TABLE_SCHEMA': 20,
                'TABLE_NAME': 30,
                'TABLE_TYPE': 20,
                'TABLE_COMMENT': 40,
                'TABLE_ROWS': 20,
                'CREATE_TIME': 20,
                'UPDATE_TIME': 20,
                'ORDINAL': 10,
                'COLUMN_NAME': 25,
                'COLUMN_COMMENT': 35,
                'COLUMN_TYPE': 15,
                'COLUMN_DEFAULT': 20,
                'COLUMN_KEY': 15,
                'IS_NULLABLE': 15,
                'IS_IDENTITY': 15,
                'IS_UNIQUEKEY': 15,
                'IS_PRIMARYKEY': 15,
                'IS_COMPUTED': 15,
                'EXPRESSION': 20,
                'INDEX_TYPE': 15,
                'KEY_ORDINAL': 15
                }
        
        #excel文檔邊框線設置
        self.excel_border_={
                'all': Border(
                        left=Side(style='medium',color='FF000000'),
                        right=Side(style='medium',color='FF000000'),
                        top=Side(style='medium',color='FF000000'),
                        bottom=Side(style='medium',color='FF000000'),
                        diagonal=Side(style='medium',color='FF000000'),
                        diagonal_direction=0,
                        outline=Side(style='medium',color='FF000000'),
                        vertical=Side(style='medium',color='FF000000'),
                        horizontal=Side(style='medium',color='FF000000')
                        )
                }
                
        #excel文檔字體設置
        self.excel_font_={
                'link': Font(underline='single',color=colors.BLUE),
                'head': Font(bold=True)
                }
        #excel文檔對齊設置
        self.excel_align_={
                'center':Alignment(horizontal='center')
                }
        #excel文檔填充設置
        self.excel_fill_={
                'link':PatternFill(fill_type='solid',start_color='E6E6E6'),
                'head':PatternFill(fill_type='solid',start_color='CDDCE6')
                }
    
    #建立sqlalchemy鏈接引擎
    def _create_engine(self,dbtype,driver,username,password,server,database,charset):
        #設置默認數據庫和驅動
        if dbtype=='mysql':
            if type(database)==type(None): database='sys'
            if type(driver)==type(None): driver='pymysql'
        elif dbtype=='mssql':
            if type(database)==type(None): database='master'
            if type(driver)==type(None): driver='pymssql'
        #此處可拓展其餘數據庫類型
        else:
            raise Exception('unsupported dbtype')
                
        engine=sqla.create_engine(
                '{}+{}://{}:{}@{}/{}?charset={}'
                .format(dbtype,driver,username,password,server,database,charset)
                )
        return engine
    
    #變動數據庫
    def change_database(self,database):
        
        self.engine_.url.database=database
        self.engine_.dispose()
        self.engine_=sqla.create_engine(self.engine_.url)
        print('database is changed to '+database)
    
    #查詢數據庫名列表(mysql)
    def _query_schema_databases_mysql(self):
        
        databases=pd.read_sql('show databases;',self.engine_)
        databases=databases['Database'].tolist()
        
        return databases
    
    #查詢表定義(mysql)
    def _query_schema_tables_mysql(self):
        
        tables=pd.read_sql(''' select TABLE_NAME,TABLE_COMMENT,TABLE_TYPE, CREATE_TIME,TABLE_ROWS from information_schema.TABLES where TABLE_SCHEMA=database() order by 1; ''',self.engine_)
        
        columns=pd.read_sql(''' select TABLE_NAME,ORDINAL_POSITION ORDINAL,COLUMN_NAME, COLUMN_COMMENT, COLUMN_TYPE,COLUMN_DEFAULT,COLUMN_KEY,IS_NULLABLE from information_schema.COLUMNS where TABLE_SCHEMA=database() order by 1,2; ''',self.engine_)
        
        return tables,columns
    
    #查詢數據庫名列表(sql server)
    def _query_schema_databases_mssql(self):
        
        databases=pd.read_sql('select name from sys.databases',self.engine_)
        databases=databases['name'].tolist()
        
        return databases
    
    #查詢表定義(sql server)
    def _query_schema_tables_mssql(self):

        tables=pd.read_sql(''' SELECT ss.name+'.'+so.name TABLE_NAME,CONVERT(NVARCHAR(4000),sep.value) TABLE_COMMENT, so.type_desc TABLE_TYPE,so.create_date CREATE_TIME,si2.rows TABLE_ROWS FROM sys.objects so JOIN sys.schemas ss ON so.schema_id=ss.schema_id JOIN sysindexes si2 ON so.object_id=si2.id AND si2.indid<2 LEFT JOIN sys.extended_properties sep ON so.object_id=sep.major_id AND sep.minor_id=0 AND sep.name='MS_Description' WHERE so.type IN ('U','V') ORDER BY 1 ''',self.engine_)
        
        columns=pd.read_sql(''' SELECT ss.name+'.'+so.name TABLE_NAME,sc.column_id ORDINAL, sc.name COLUMN_NAME,CONVERT(NVARCHAR(4000),sep.value) COLUMN_COMMENT, case when sc.max_length=-1 then st.name+'(max)' when st.name in ('nchar','nvarchar') then st.name+'('+CAST(sc.max_length/2 as varchar(10))+')' when st.name in ('char','varchar','binary','varbinary') then st.name+'('+CAST(sc.max_length as varchar(10))+')' when st.name in ('numeric','decimal') then st.name+'('+CAST(sc.precision as varchar(5))+','+CAST(sc.scale as varchar(5))+')' else st.name end COLUMN_TYPE, sdc.definition COLUMN_DEFAULT,sc.is_nullable IS_NULLABLE,sc.is_identity IS_IDENTITY, ISNULL(si.is_unique_constraint,0) IS_UNIQUEKEY,ISNULL(si.is_primary_key,0) IS_PRIMARYKEY, sc.is_computed IS_COMPUTED,scc.definition EXPRESSION, si.type_desc INDEX_TYPE,sic.key_ordinal KEY_ORDINAL FROM sys.objects so JOIN sys.schemas ss ON so.schema_id=ss.schema_id JOIN sys.columns sc ON so.object_id=sc.object_id JOIN sys.types st ON sc.user_type_id=st.user_type_id LEFT JOIN sys.default_constraints sdc ON sc.default_object_id=sdc.object_id LEFT JOIN sys.extended_properties sep ON so.object_id=sep.major_id AND sc.column_id=sep.minor_id AND sep.name='MS_Description' LEFT JOIN sys.index_columns sic ON sic.object_id=so.object_id AND sic.column_id=sc.column_id LEFT JOIN sys.indexes si ON sic.object_id=si.object_id AND sic.index_id=si.index_id LEFT JOIN sys.computed_columns scc ON sc.object_id=scc.object_id AND sc.column_id=scc.column_id WHERE so.type in ('U','V') ORDER BY 1,2 ''',self.engine_)
        
        return tables,columns
    
    #查詢數據庫名列表
    def query_schema_databases(self):
        '''\n Method: 查詢數據庫名列表 '''
        if self.engine_.name=='mysql':
            databases=self._query_schema_databases_mysql()
        elif self.engine_.name=='mssql':
            databases=self._query_schema_databases_mssql()
        #此處可拓展其餘數據庫類型
        else:
            raise Exception('unsupported dbtype')
            
        return databases
    
    #查詢表定義
    def query_schema_tables(self,database=None):
        '''\n Method: 查詢表定義 Parameters ---------- database: 須要查詢全部表定義的數據庫,str類型, None表示使用engine中的設置 ---------- Returns ---------- tables: 表信息,DataFrame類型 columns: 列信息,DataFrame類型 ---------- '''
        if type(database)!=type(None):
            self.change_database(database)
        
        if self.engine_.name=='mysql':
            tables,columns=self._query_schema_tables_mysql()
        elif self.engine_.name=='mssql':
            tables,columns=self._query_schema_tables_mssql()
        #此處可拓展其餘數據庫類型
        else:
            raise Exception('unsupported dbtype')
            
        if (not tables.columns.contains('TABLE_NAME')) or \
            (not columns.columns.contains('TABLE_NAME')):
            raise Exception("missing column 'TABLE_NAME'")
            
        return tables,columns

    #Excel列索引轉標籤
    def _excel_col_label(self,idx):
        
        label=''
        while True:
            label+=chr(idx%26+65)
            idx=idx//26
            if idx==0:
                break;
                
        return label[::-1]
    
    #導出Excel文檔
    def to_excel(self,output_folder,databases=None,name_prefix=''):
        '''\n Method: 導出Excel文檔 Parameters ---------- output_folder: 輸出文件夾的路徑,str類型 databases: 須要查詢全部表定義的數據庫, None表示使用engine中的設置, str類型指定一系列逗號分隔的數據庫名或'*'表示導出全部, list of str類型指定數據庫名列表 name_prefix: 導出文件名的前綴(數據庫名會做爲後綴) ---------- '''
        if name_prefix!='': name_prefix+='_'
        
        if type(databases)==type(None):
            databases=self.engine_.url.database
        
        if type(databases)==str:
            if databases=='*':
                databases=self.query_schema_databases()
            else:
                databases=[db for db in databases.split(',') if db!='']
        
        if type(databases)==list:
            for database in databases:
                tables,columns=self.query_schema_tables(database)
                file_path=output_folder+'\\'+name_prefix+database+'.xlsx'
                self.schema_to_excel(tables,columns,file_path)
        else:
            raise Exception('databases should be None(use engine setting),'+ 
                            'str(database names or ''*''), '+
                            'or list of str(database names)')
    
    #將架構信息導出至單個Excel文檔
    def schema_to_excel(self,tables,columns,file_path):
        '''\n Method: 將架構信息導出至單個Excel文檔 Parameters ---------- tables: 表信息,DataFrame類型 columns: 列信息,DataFrame類型 file_path: excel文件路徑 ---------- '''
        columns_0=columns['TABLE_NAME']
        columns_1=columns.drop('TABLE_NAME',axis=1)
    
        #導出數據至Excel
        writer=pd.ExcelWriter(file_path)
        tables.to_excel(writer,'Index',index=False)
        for i in range(tables.shape[0]):
            table_name=tables['TABLE_NAME'].iloc[i]
            columns_=columns_1[columns_0==table_name]
            columns_.to_excel(writer,'Table'+str(i+1),index=False)
        writer.save()
        
        #調整索引頁格式
        wb=load_workbook(file_path) 
        ws=wb["Index"]
        
        #調整列寬
        for j in range(tables.shape[1]):
            label=self._excel_col_label(j)
            width=self.excel_col_width_[tables.columns[j]]
            ws.column_dimensions[label].width=width
            ws[label+'1'].fill=self.excel_fill_['head']
            
        #增長邊框線
        for i in range(1,tables.shape[0]+2):
            for j in range(tables.shape[1]):
                label=self._excel_col_label(j)
                ws[label+str(i)].border=self.excel_border_['all']
                
        #處理各表的列定義頁
        for i in range(len(tables)):
            
            table_name=tables['TABLE_NAME'].iloc[i]
            sheet_name='Table'+str(i+1)
            
            #索引頁增長調轉指定表頁的連接
            jump_link_colidx=tables.columns.tolist().index('TABLE_NAME')
            jump_link_cell=self._excel_col_label(jump_link_colidx)+str(i+2)
            back_link_colidx=columns.shape[1]-1
            back_link_collab=self._excel_col_label(back_link_colidx)
            back_link_cell=back_link_collab+'3'
            ws[jump_link_cell].hyperlink = "#"+sheet_name+"!A1"
            ws[jump_link_cell].font = self.excel_font_['link']

            #指定表頁增長返回索引頁的連接
            ws2=wb[sheet_name]
            ws2[back_link_cell]='back'
            ws2[back_link_cell].hyperlink = "#Index!"+jump_link_cell
            ws2[back_link_cell].font=self.excel_font_['link']
            ws2[back_link_cell].border=self.excel_border_['all']
            ws2[back_link_cell].alignment =self.excel_align_['center']
            ws2[back_link_cell].fill=self.excel_fill_['link']
            
            ws2.column_dimensions[back_link_collab].width=40
            
            #添加表名信息
            tname_head_cell=back_link_collab+'1'
            tname_value_cell=back_link_collab+'2'
            ws2[tname_head_cell]='TABLE_NAME'
            ws2[tname_head_cell].font =self.excel_font_['head']
            ws2[tname_head_cell].alignment =self.excel_align_['center']
            ws2[tname_head_cell].border=self.excel_border_['all']
            ws2[tname_head_cell].fill=self.excel_fill_['link']
            ws2[tname_value_cell]=table_name
            ws2[tname_value_cell].alignment =self.excel_align_['center']
            ws2[tname_value_cell].border=self.excel_border_['all']
            ws2[tname_value_cell].fill=self.excel_fill_['link']
            
            #篩選指定表的列定義
            columns_=columns_1[columns_0==table_name]
            
            #調整列寬
            for j in range(columns_.shape[1]):
                label=self._excel_col_label(j)
                width=self.excel_col_width_[columns_.columns[j]]
                ws2.column_dimensions[label].width=width
                ws2[label+'1'].fill=self.excel_fill_['head']
            
            #增長邊框線
            for i in range(1,columns_.shape[0]+2):
                for j in range(columns_.shape[1]):
                    label=self._excel_col_label(j)
                    ws2[label+str(i)].border=self.excel_border_['all']
        
        #保存文件
        wb.save(file_path)
        wb.close()
        
    #讀取Excel文檔
    def read_excel(self,file_path):
        '''\n Method: 讀取Excel文檔 Parameters ---------- file_path: excel文件路徑 ---------- Returns ---------- tables: 表信息,DataFrame類型 columns: 列信息,DataFrame類型 ---------- '''
        data=pd.read_excel(file_path, None)
        
        columns=[]
        for sheetname in data.keys():
            if sheetname=='Index':
                tables=data[sheetname]
            else:
                columns.append(data[sheetname])
        
        columns=pd.concat(columns)
        columns.insert(0,'TABLE_NAME',columns.pop('TABLE_NAME'))
        columns.index=range(columns.shape[0])
        columns.loc[columns['TABLE_NAME']=='back','TABLE_NAME']=None
        columns['TABLE_NAME']=columns['TABLE_NAME'].fillna(method='ffill')
                
        return tables,columns
  
#示例 
if __name__=='__main__':
    
    #mysql
    dbtype='mysql'
    username='root'
    password='123456'
    server='localhost:3306'
    ''' #mssql dbtype='mssql' username='sa' password='123456' server='localhost:1433' #mssql使用windows帳戶鏈接 engine=sqla.create_engine("mssql+pyodbc://localhost/master"+ "?driver=SQL Server Native Client 11.0") data_dict=SqlDataDictionary(engine=engine) '''
    #print(data_dict.query_schema_databases())
    #print(data_dict.query_schema_tables())
    
    data_dict=SqlDataDictionary(dbtype,username,password,server)
    
    data_dict.to_excel(output_folder='C:\\Users\\hp\\Desktop', 
                       databases='world', name_prefix='local')
    
    #tables,columns=data_dict.read_excel('C:\\Users\\hp\\Desktop\\local_world.xlsx')
複製代碼

說明:

該小工具主要有三部分:數據庫

  1. 從數據庫系統視圖中查詢表和字段定義;
  2. 經過sqlalchemypandas執行查詢sql並將結果以DataFrame返回;
  3. 經過pandas導出數據至Excel文件,再使用openpyxl調整Excel的格式。

查詢表和字段定義

mysql的查詢使用了系統數據庫information_schema下的系統視圖,該數據庫包含全部數據庫的定義信息,查詢時注意限制當前鏈接數據庫。windows

show databases;

use db_name
select * from information_schema.TABLES where TABLE_SCHEMA=database();
select * from information_schema.COLUMNS where TABLE_SCHEMA=database();
複製代碼

mssql的查詢使用了sys架構下的系統視圖,包含當前鏈接數據庫的定義信息。mssql也有information_schema架構的系統視圖,大部分字段和mysql中的是同樣的,但此處沒有使用。服務器

--數據庫
select * from sys.databases
--數據庫對象(表、視圖、函數、存儲過程、約束等)
--經過限制type屬性查詢表(U)和視圖(V)
select * from sys.objects where type in ('U','V')
--列
select * from sys.columns
--類型
select * from sys.types
--全部者架構
select * from sys.schemas
--拓展屬性
--經過限制name='MS_Description'查詢表和列的描述
--major_id對應object_id,minor_id對應column_id,
--minor_id=0表示是表的描述
select * from sys.extended_properties
--索引
select * from sys.indexes
--索引列
select * from sys.index_columns
--計算列
select * from sys.computed_columns
--默認值約束
select * from sys.default_constraints
複製代碼

有一點要注意,mssqlmysql的組織結構不太同樣:mssql的數據庫對應的是CATELOG,全部者對應着SCHEMA;而mysql的數據庫對應的是SCHEMACATELOG全爲def架構

使用sqlalchemy+pandas執行sql查詢

pandasread_sql方法用於從sql查詢讀取數據生成DataFrame
第一個參數sql設置要執行的sql
第二個參數con設置數據庫鏈接,主要支持類型爲sqlalchemyengineconnectionapp

import pandas as pd
result=pd.read_sql('select * from test',engine)
複製代碼

sqlalchemycreate_engine方法用於建立engine對象,鏈接url遵循RFC-1738,常見形式以下:ide

import sqlalchemy as sqla
engine=sqla.create_engine('{dbtype}+{driver}://{username}:{password}@{server}/{database}'+
                          '?charset={charset}'')
複製代碼

使用windows authentication鏈接mssql須要使用支持該認證方式的驅動,好比pyodbc,示例以下:函數

engine=sqla.create_engine("mssql+pyodbc://localhost/master"+
                          "?driver=SQL Server Native Client 11.0")
複製代碼

建立基於pymysqlengine後首次執行sql查詢可能會出現以下警告,但查詢結果正常。
經查找資料肯定這是mysql的一個bug,聽說不影響使用(尚不能確定)。

C:\ProgramData\Anaconda3\lib\site-packages\pymysql\cursors.py:170: Warning: (1366, "Incorrect string value: '\\xD6\\xD0\\xB9\\xFA\\xB1\\xEA...' for column 'VARIABLE_VALUE' at row 518")
  result = self._query(query)
複製代碼

pandas+openpyxl導出Excel

pd.DataFrameto_excel方法能夠導出數據到Excel指定sheet,但沒法進一步調整格式:
第一個參數excel_writer設置Excel文件路徑或已存在的pd.ExcelWriter
第二個參數sheet_name設置sheet名稱,默認'Sheet1'
第三個參數na_rep設置缺失值填充,默認''
第六個參數header設置是否寫入列標籤,默認True
第七個參數index設置是否寫入行標籤,默認True

df.to_excel('D:\\test.xlsx','data',index=False)
複製代碼

openpyxl中的Excel對象主要有三個層次:
最頂層容器是Workbook,而後是Worksheet,最後是Cell

openpyxl.load_workbook(file_path)方法根據Excel文件路徑加載Workbook對象, Workbook[sheet_name]索引器能夠根據名稱獲取WorksheetWorksheet的名稱就是打開Excel文件時顯示在下方的標籤頁名稱,
Worksheet[cell_name]索引器能夠根據名稱獲取CellCell的名稱是列標籤與行號的組合,形如'AB25',使用過Excel的人應該都比較熟悉。

Worksheet還可經過rowscolumns獲取以行或列組織的Cell集合的迭代器,可用於行或列的遍歷;row_dimensions[index]column_dimensions[index]獲取用於設置基於行或列的屬性的RowDimensionColumnDimension對象,例如設置行列寬。

Cell的屬性:
value屬性存放單元格的值;
font屬性設置字體,Font類型;
border屬性設置邊框線,Border類型;
alignment屬性設置對齊方式,Alignment類型;
fill屬性設置填充,PatternFill類型;
hyperlink屬性設置超連接,str類型,格式形如'#sheet!A1'

RowDimensionColumnDimension的共通屬性:
heightwidth屬性設置行列寬;
hidden屬性設置是否隱藏;
fontborderalignmentfill等屬性的設置和Cell是同樣。

color的設置能夠使用十六進制表示的RGBRGBA字符串,例如'FF000000',也能夠使用openpyxl.styles.colors下預設的一些顏色配置。

from openpyxl import load_workbook
from openpyxl.styles import Font,Border,Side,Alignment,PatternFill,colors

wb=load_workbook('C:\\Users\\hp\\Desktop\\local_world.xlsx') 

ws=wb['Index']

print(ws['A1'].value)

ws['A1'].border=Border(
        left=Side(style='medium',color='FF000000'),
        right=Side(style='medium',color='FF000000'),
        top=Side(style='medium',color='FF000000'),
        bottom=Side(style='medium',color='FF000000')
        )

ws['A1'].font=Font(underline='single',color=colors.BLUE)

ws['A1'].alignment=Alignment(horizontal='center')

ws['A1'].fill=PatternFill(fill_type='solid',start_color='E6E6E6')

ws['A1'].hyperlink='#Table1!B2'

ws.column_dimensions['A'].width=40.0
複製代碼

關於拓展

(1). 表和列信息查詢內容的增長只須要修改相應的sql就好了,但原有的TABLE_NAME列不能刪除; (2). 增長新的數據庫類型支持須要拓展三處:
_create_engine方法中增長新類型的默認數據庫和默認驅動設置;
query_schema_databases方法中增長新類型的數據庫列表查詢支持,返回數據庫名列表;
query_schema_tables方法中增長新類型的表和列定義查詢支持,返回兩個DataFrame,注意,兩個查詢必需要包含可做爲惟一標識的表名字段TABLE_NAME,另外同義的列保持列名統一,這樣能夠共用Excel文檔的列寬設置;
(3). 預約義的一些Excel文檔樣式可在構造函數中修改,不想動默認設置的話也能夠建立對象後單獨從屬性中修改,這些樣式的用處都已經固定,更復雜的樣式調整須要修改schema_to_excel方法。

相關文章
相關標籤/搜索