最後一次更新日期: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')
複製代碼
該小工具主要有三部分:數據庫
sqlalchemy
和pandas
執行查詢sql
並將結果以DataFrame
返回;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
複製代碼
有一點要注意,mssql
和mysql
的組織結構不太同樣:mssql
的數據庫對應的是CATELOG
,全部者對應着SCHEMA
;而mysql
的數據庫對應的是SCHEMA
,CATELOG
全爲def
。架構
sqlalchemy
+pandas
執行sql
查詢pandas
的read_sql
方法用於從sql
查詢讀取數據生成DataFrame
:
第一個參數sql
設置要執行的sql
;
第二個參數con
設置數據庫鏈接,主要支持類型爲sqlalchemy
的engine
或connection
。app
import pandas as pd
result=pd.read_sql('select * from test',engine)
複製代碼
sqlalchemy
的create_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")
複製代碼
建立基於pymysql
的engine
後首次執行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.DataFrame
的to_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]
索引器能夠根據名稱獲取Worksheet
,Worksheet
的名稱就是打開Excel
文件時顯示在下方的標籤頁名稱,
Worksheet[cell_name]
索引器能夠根據名稱獲取Cell
,Cell
的名稱是列標籤與行號的組合,形如'AB25'
,使用過Excel
的人應該都比較熟悉。
Worksheet
還可經過rows
或columns
獲取以行或列組織的Cell
集合的迭代器,可用於行或列的遍歷;row_dimensions[index]
或column_dimensions[index]
獲取用於設置基於行或列的屬性的RowDimension
或ColumnDimension
對象,例如設置行列寬。
Cell
的屬性:
value
屬性存放單元格的值;
font
屬性設置字體,Font
類型;
border
屬性設置邊框線,Border
類型;
alignment
屬性設置對齊方式,Alignment
類型;
fill
屬性設置填充,PatternFill
類型;
hyperlink
屬性設置超連接,str
類型,格式形如'#sheet!A1'
。
RowDimension
或ColumnDimension
的共通屬性:
height
或width
屬性設置行列寬;
hidden
屬性設置是否隱藏;
font
、border
、alignment
、fill
等屬性的設置和Cell
是同樣。
color
的設置能夠使用十六進制表示的RGB
或RGBA
字符串,例如'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
方法。