1、需求描述
数据库详细设计文档是软件项目众多交付文档中非常重要的一份文档,在代码开发后期,数据库表结构已稳定,为了保证数据库表结构与数据库详细设计文档信息保持一致,往往需要投入很大的人力和时间去维护,且还存在人为操作偏差。这时候,一款可以随时能同步数据库表结构与数据库详细设计文档的工具,显得非常重要。
2、工具概述
首先使用PowerDesigner(简称“PD”)反向工程构建一份pdm文档,然后使用PD中内置的VBA脚本编辑器,自定义VBA脚本,导出数据表结构成excel文档
3、版本要求
PowerDesigner16.5、jdk7
4、操作步骤
在PD的安装目录中,自定义脚本startup.bat
set JAVA_HOME=D:\software\Java\jdk1.7.0_79 set CLASSPATH=D:\Sybase\PowerDesigner 16\ojdbc6.jar pdshell16.exe
在VB Scripts目录中新建extends目录,存放自定义脚本Export2Excel.vba
Option Explicit Dim savePath,curdate,tabList,codeList,colCodeList,codeStyle,codeGroup,titleColor curdate = Date savePath = "数据库表结构-"&curdate&"-V1.0.xlsx" tabList = "tablist.txt" codeList = "codelist.xlsx" colCodeList = "colcodelist.xlsx" codeStyle = "s01" 's01,s02 codeGroup = False '码值sheet是否分组 titleColor = "15" Output savePath '----------------------------------------------------------------------------- ' Main function '----------------------------------------------------------------------------- ' Get the current active model Dim Model Set Model = ActiveModel If (Model Is Nothing) Or (Not Model.IsKindOf(PdPDM.cls_Model)) Then MsgBox "The current model is not an PDM model." Else ' Get the tables collection ' 创建EXCEL APP Dim EXCEL, BOOK, sheetList,vcnt vcnt = 0 Dim varray (1000) '表清单 Dim carray (1000) '字段-码值映射 Dim rarray (500) '字段-码值映射索引范围 Dim larray (1000) '码值-单元格位置映射 Set EXCEL = CreateObject("Excel.Application") EXCEL.Visible = True Set BOOK = EXCEL.Workbooks.Add(-4167) '新建工作簿 BOOK.Sheets.Add , BOOK.Sheets(BOOK.Sheets.count) '添加sheet BOOK.Sheets(1).Name = "码表" BOOK.Sheets.Add , BOOK.Sheets(BOOK.Sheets.count) '添加sheet BOOK.Sheets(2).name ="目录" '添加目录sheet页 Set sheetList = BOOK.sheets("目录") '目录sheet对象 '读取表清单 ReadCsvFile '读取字段-代码映射 ReadTabCodeFile '生成码表sheet页 ReadCodeFile '初始化目录Sheet标题 InitsheetListHeader Model,sheetList '输出数据库表元信息 ShowProperties Model,sheetList '显示网格线 EXCEL.ActiveWindow.DisplayGridlines = True 'BOOK.SaveAs savePath 'EXCEL.Close 'EXCEL.Quit 'Set BOOK = Nothing 'Set EXCEL = Nothing End If '----------------------------------------------------------------------------- ' Init Sheet List Header '----------------------------------------------------------------------------- Sub InitsheetListHeader(mdl, sheetList) Dim rowsNo rowsNo=1 output "初始化目录Sheet标题..." '设置标题内容 sheetList.cells(1, 1) = "序号" sheetList.cells(1, 2) = "表名" sheetList.cells(1, 3) = "备注" '设置标题列宽 sheetList.Columns(3).ColumnWidth = 20 'sheetList.Columns(1).WrapText =true '设置边框 sheetList.Range(sheetList.Cells(1, 1),sheetList.Cells(1, 3)).Borders.LineStyle = "1" '设置背景颜色 sheetList.Range(sheetList.Cells(1, 1),sheetList.Cells(1, 3)).Interior.ColorIndex = titleColor sheetList.Columns("A:B").EntireColumn.AutoFit '列宽自适应 sheetList.Activate EXCEL.ActiveWindow.SplitRow = 1 EXCEL.ActiveWindow.SplitColumn = 0 EXCEL.ActiveWindow.FreezePanes = True output "初始化目录Sheet标题完毕!" End Sub '----------------------------------------------------------------------------- ' Show properties of tables '----------------------------------------------------------------------------- Sub ShowProperties(mdl, sheetList) Dim tabIndex tabIndex = 2 Output "输出表结构元信息..." Dim tab,bln,i,j,cnt bln = False j = 0 cnt = 10 For i = LBound(varray) To UBound(varray) '表清单 If varray(i) = "" Then j = j + 1 bln = False Else bln = False j = 0 For Each tab In mdl.tables If varray(i) = tab.name Then bln = True Exit For End If Next End If If j > cnt Then Exit For End If If bln = True Then ShowTable tab,tabIndex,sheetList tabIndex = tabIndex + 1 End If Next Output "输出表结构元信息完成!" End Sub '----------------------------------------------------------------------------- ' read csv file '----------------------------------------------------------------------------- Sub ReadCsvFile() Dim system, file Set system = CreateObject("Scripting.FileSystemObject") ' Open mode constants... Dim ForReading, ForWriting, ForAppending ForReading = 1 ' Open a file for reading only. You can't write to this file. ForWriting = 2 ' Open a file for writing. ForAppending = 8 ' Open a file and write to the end of the file. Output "读取文件"&tabList&"..." Set file = system.OpenTextFile(tabList, ForReading) vcnt = 0 Do While file.AtEndOfStream <> True varray(vcnt) = Trim(file.ReadLine) vcnt = vcnt + 1 Loop file.Close Output "共读取文件有效行数:"&vcnt End Sub '----------------------------------------------------------------------------- ' read code file '----------------------------------------------------------------------------- Sub ReadCodeFile() Dim cExcel,cWorkbook,cSheet,ctSheet,startRow,startCol,totalRow,totalCol,i,j,rNum,gNum startRow = 2 startCol = 2 rNum = 1 gNum = 1 j=0 Output "读取代码配置文件"&codeList&"..." Set cExcel = CreateObject("Excel.Application") Set cWorkbook = cExcel.Workbooks.Open(codeList) Set cSheet = cWorkbook.Worksheets(1) totalRow = cSheet.UsedRange.Rows.Count totalCol = cSheet.UsedRange.Columns.Count Output "获取sheet1有效行"&totalRow&",有效列"&totalCol Dim shtn,codeNo,codeName,itemNo,itemName,isInuse,isStandard Set shtn = BOOK.Sheets("码表") codeNo = "" If codeStyle = "s01" Then shtn.cells(rNum, 1) = "代码编号" '代码编号-标题 shtn.cells(rNum, 2) = "代码名称" '代码名称-标题 shtn.cells(rNum, 3) = "代码项编号" '代码项编号-标题 shtn.cells(rNum, 4) = "代码项名称" '代码项名称-标题 shtn.cells(rNum, 5) = "是否使用" '是否使用-标题 shtn.cells(rNum, 6) = "是否落标" '是否落标-标题 '设置背景颜色 shtn.Range(shtn.cells(rNum, 1),shtn.cells(rNum, 6)).Interior.ColorIndex = titleColor '设置字体 shtn.Range(shtn.cells(rNum, 1),shtn.cells(rNum, 6)).Font.Bold = True rNum = rNum + 1 For i = startRow To totalRow If codeNo <> cSheet.Cells(i,startCol) Then shtn.cells(rNum, 1) = cSheet.Cells(i,startCol) '代码编号-取值 shtn.cells(rNum, 2) = cSheet.Cells(i,startCol+1) '代码名称-取值 shtn.cells(rNum, 6) = cSheet.Cells(i,startCol+5) '是否落标-取值 If codeNo <> "" Then If codeGroup Then shtn.Range("A"&(rNum-gNum)&":A"&(rNum-2)).Rows.Group End If larray(j) = shtn.cells(rNum-gNum, 1)&",A"&(rNum-gNum) j = j+1 shtn.Range(shtn.cells(rNum-gNum, 1),shtn.cells(rNum-1, 1)).Merge shtn.Range(shtn.cells(rNum-gNum, 2),shtn.cells(rNum-1, 2)).Merge shtn.Range(shtn.cells(rNum-gNum, 6),shtn.cells(rNum-1, 6)).Merge End If codeNo = cSheet.Cells(i,startCol) '代码编号 gNum = 1 Else gNum = gNum + 1 End If shtn.cells(rNum, 3) = cSheet.Cells(i,startCol+2) '代码项编号-取值 shtn.cells(rNum, 4) = cSheet.Cells(i,startCol+3) '代码项名称-取值 shtn.cells(rNum, 5) = cSheet.Cells(i,startCol+4) '是否使用-取值 shtn.Range(shtn.cells(rNum, 1),shtn.cells(rNum, 6)).Borders.LineStyle = "1" rNum = rNum + 1 Next If codeGroup Then shtn.Range("A"&(rNum-gNum)&":A"&(rNum-2)).Rows.Group End If larray(j) = shtn.cells(rNum-gNum, 1)&",A"&(rNum-gNum) j = j+1 shtn.Range(shtn.cells(rNum-gNum, 1),shtn.cells(rNum-1, 1)).Merge shtn.Range(shtn.cells(rNum-gNum, 2),shtn.cells(rNum-1, 2)).Merge shtn.Range(shtn.cells(rNum-gNum, 6),shtn.cells(rNum-1, 6)).Merge ElseIf codeStyle = "s02" Then For i = startRow To totalRow If codeNo <> cSheet.Cells(i,startCol) Then If codeNo <> "" and codeGroup Then shtn.Range("A"&(rNum-gNum-1)&":A"&(rNum-1)).Rows.Group End If codeNo = cSheet.Cells(i,startCol) '代码编号 shtn.cells(rNum, 1) = "代码编号" '代码编号-标题 larray(j) = shtn.cells(rNum, 1)&",A"&rNum j = j+1 shtn.cells(rNum, 2) = codeNo '代码编号-取值 shtn.cells(rNum, 3) = "代码名称" '代码名称-标题 shtn.cells(rNum, 4) = cSheet.Cells(i,startCol+1) '代码名称-取值 shtn.cells(rNum, 5) = "是否落标" '是否落标-标题 shtn.cells(rNum, 6) = cSheet.Cells(i,startCol+5) '是否落标-取值 '设置背景颜色 'shtn.Range(shtn.cells(rNum, 1),shtn.cells(rNum, 6)).Interior.ColorIndex = "15" '设置字体 shtn.cells(rNum, 1).Font.Bold = True shtn.cells(rNum, 3).Font.Bold = True shtn.cells(rNum, 5).Font.Bold = True rNum = rNum + 1 shtn.cells(rNum, 1) = "代码项编号" '代码项编号-标题 shtn.cells(rNum, 2) = "代码项名称" '代码项名称-标题 shtn.cells(rNum, 3) = "是否使用" '是否使用-标题 rNum = rNum + 1 gNum = 0 End If shtn.cells(rNum, 1) = cSheet.Cells(i,startCol+2) '代码项编号-取值 shtn.cells(rNum, 2) = cSheet.Cells(i,startCol+3) '代码项名称-取值 shtn.cells(rNum, 3) = cSheet.Cells(i,startCol+4) '是否使用-取值 rNum = rNum + 1 gNum = gNum + 1 Next shtn.Range("A"&(rNum-gNum-1)&":A"&(rNum-1)).Rows.Group Else Output "不支持的代码表样式"&codeStyle End If shtn.Columns("A:F").EntireColumn.AutoFit '列宽自适应 shtn.Columns(6).ColumnWidth = 10 shtn.Columns("A:F").NumberFormatLocal = "@" '单元格内容为文本格式 shtn.Activate EXCEL.ActiveWindow.SplitRow = 1 EXCEL.ActiveWindow.SplitColumn = 0 EXCEL.ActiveWindow.FreezePanes = True cWorkbook.Close End Sub '----------------------------------------------------------------------------- ' read table's column code mapper file '----------------------------------------------------------------------------- Sub ReadTabCodeFile() Dim cExcel,cWorkbook,cSheet,ctSheet,startRow,startCol,totalRow,totalCol,tabName,colName,codeNo,cnt,cnt2,tmp,tmp2,i,j startRow = 2 startCol = 2 cnt = 0 cnt2 = 0 tmp = "" Output "读取字段-代码映射配置文件"&colCodeList&"..." Set cExcel = CreateObject("Excel.Application") Set cWorkbook = cExcel.Workbooks.Open(colCodeList) Set cSheet = cWorkbook.Worksheets(1) totalRow = cSheet.UsedRange.Rows.Count totalCol = cSheet.UsedRange.Columns.Count Output "获取sheet1有效行"&totalRow&",有效列"&totalCol For i = startRow To totalRow tabName = cSheet.Cells(i,startCol) '表名 colName = cSheet.Cells(i,startCol+1) '字段名 codeNo = cSheet.Cells(i,startCol+2) '代码编号 If tmp <> tabName&","&colName Then carray(cnt) = tabName&","&colName&","&codeNo tmp = tabName&","&colName cnt = cnt + 1 Else carray(cnt-1) = carray(cnt-1)&","&codeNo End If Next cWorkbook.Close tmp = "" For i = 0 To cnt-1 tabName = Split(carray(i),",")(0) If tmp <> tabName Then rarray(cnt2) = tabName&","&i&","&i tmp = tabName cnt2 = cnt2 +1 Else tmp2 = Split(rarray(cnt2-1),",") rarray(cnt2-1) = tmp2(0)&","&tmp2(1)&","&i End If Next End Sub '----------------------------------------------------------------------------- ' Show table properties '----------------------------------------------------------------------------- Sub ShowTable(tab, tabIndex, sheetList) Dim tabDesp If IsObject(tab) Then If tab.comment <> "" Then tabDesp = tab.code + "(" + tab.comment + ")" Else tabDesp = tab.code End If Output "输出"&tabDesp&"..." sheetList.Cells(tabIndex, 1) = tabIndex-1 sheetList.Cells(tabIndex, 2) = tabDesp BOOK.Sheets.Add , BOOK.Sheets(BOOK.Sheets.count) '添加sheet BOOK.Sheets(tabIndex+1).Name = tab.code Dim shtn Set shtn = BOOK.Sheets(tab.code) '表名标题 shtn.Range(shtn.cells(1, 1),shtn.cells(1, 7)).Merge shtn.Cells(1, 1) = tabDesp shtn.Cells(1, 1).Font.Size = 12 shtn.Cells(1, 1).Font.Bold = True shtn.Cells(1, 1).HorizontalAlignment = 3 '设置边框 'shtn.Range(shtn.cells(1, 1),shtn.cells(1, 7)).Borders.LineStyle = "1" '返回链接 shtn.Cells(1, 8) = "<<目录" '设置列标题 shtn.cells(2, 1) = "字段中文名" shtn.cells(2, 2) = "字段英文名" shtn.cells(2, 3) = "字段类型" shtn.cells(2, 4) = "是否主键" shtn.cells(2, 5) = "是否非空" shtn.cells(2, 6) = "默认值" shtn.cells(2, 7) = "备注" '设置列宽和换行 shtn.Columns(1).ColumnWidth = 30 shtn.Columns(2).ColumnWidth = 20 shtn.Columns(3).ColumnWidth = 20 shtn.Columns(4).ColumnWidth = 10 shtn.Columns(5).ColumnWidth = 10 shtn.Columns(6).ColumnWidth = 10 shtn.Columns(7).ColumnWidth = 30 shtn.Columns(1).WrapText =true shtn.Columns(2).WrapText =true shtn.Columns(3).WrapText =true shtn.Columns(4).WrapText =true shtn.Columns(5).WrapText =true shtn.Columns(6).WrapText =true shtn.Columns(7).WrapText =true '设置边框 shtn.Range(shtn.cells(2, 1),shtn.cells(2, 7)).Borders.LineStyle = "1" '设置背景颜色 shtn.Range(shtn.cells(2, 1),shtn.cells(2, 7)).Interior.ColorIndex = titleColor '输出字段信息 Dim col Dim rNum,i,j,cnt,bln,tarray,m,n,k,tmp,codeNo,location,back,left,right rNum = 1 j = 0 cnt = 5 back = 0 left = 0 right = 0 For i = LBound(rarray) To UBound(rarray) If rarray(i) = "" Then Exit For End If tmp = Split(rarray(i),",") If tab.code = tmp(0) Then left = tmp(1) right = tmp(2) Exit For End If Next For Each col in tab.columns rNum = rNum + 1 shtn.cells(rNum+1, 1) = col.comment '字段中文名 shtn.cells(rNum+1, 2) = col.code '字段英文名 shtn.cells(rNum+1, 3) = col.datatype '字段类型 If col.Primary = true Then shtn.cells(rNum+1, 4) = "Y" '是否主键 Else shtn.cells(rNum+1, 4) = " " End If If col.Mandatory = true Then '是否非空 shtn.cells(rNum+1, 5) = "Y" Else shtn.cells(rNum+1, 5) = " " End If shtn.cells(rNum+1, 6) = col.defaultvalue '默认值 shtn.cells(rNum+1, 7) = col.comment '备注 For i = left To right '字段-码值映射 k = 0 tarray = Split(carray(i),",") If UBound(tarray) < 2 Then Output "字段-代码配置项["&carray(i)&"]格式有误" back = 1 Exit For Else If col.code = tarray(1) Then If UBound(tarray) > 1 Then For m = 2 To UBound(tarray) For n = LBound(larray) To UBound(larray) '码值-单元格位置映射 If larray(n) = "" Then j = j + 1 Else j = 0 tmp = Split(larray(n),",") codeNo = tmp(0) location = tmp(1) If tarray(m) = codeNo Then Output col.code&":"&codeNo shtn.cells(rNum+1+k, 7) = "关联码值("&codeNo&")" shtn.Hyperlinks.Add shtn.cells(rNum+1+k, 7), "","码表!"&location Output shtn.cells(rNum+1+k, 7)&"添加链接码表!"&location k = k + 1 Exit For End If End If If j > cnt Then Exit For End If Next Next If k = 0 Then Output "字段-代码配置项["&carray(i)&"]对应码值丢失,请检查码表sheet页" Elseif k > 1 Then shtn.Range(shtn.cells(rNum+1, 1),shtn.cells(rNum+k, 1)).Merge shtn.Range(shtn.cells(rNum+1, 2),shtn.cells(rNum+k, 2)).Merge shtn.Range(shtn.cells(rNum+1, 3),shtn.cells(rNum+k, 3)).Merge shtn.Range(shtn.cells(rNum+1, 4),shtn.cells(rNum+k, 4)).Merge shtn.Range(shtn.cells(rNum+1, 5),shtn.cells(rNum+k, 5)).Merge shtn.Range(shtn.cells(rNum+1, 6),shtn.cells(rNum+k, 6)).Merge shtn.Range(shtn.cells(rNum+1, 1),shtn.cells(rNum+k, 7)).Borders.LineStyle = "1" rNum = rNum+k-1 End If End If End If End If Next If back = 1 Then Exit For End If shtn.Range(shtn.cells(rNum+1, 1),shtn.cells(rNum+1, 7)).Borders.LineStyle = "1" Next '设置超链接,从目录点击表名去查看表结构 sheetList.Hyperlinks.Add sheetList.Cells(tabIndex,2), "",tab.code&"!A1" shtn.Hyperlinks.Add shtn.Cells(1, 8), "","目录!B"&tabIndex 'shtn.Columns("A:G").EntireColumn.AutoFit '列宽自适应 'shtn.Columns(6).ColumnWidth = 10 shtn.Columns("A:G").NumberFormatLocal = "@" '单元格内容为文本格式 shtn.Activate EXCEL.ActiveWindow.SplitRow = 2 EXCEL.ActiveWindow.SplitColumn = 0 EXCEL.ActiveWindow.FreezePanes = True Output "输出"&tabDesp&"完成!" End If End Sub
为了丰富表字段与码值对应的映射,还需一段辅助sql脚本
--create table mytab (tablename varchar2(100)); --表清单 --drop table column_code_mapper purge; --select * from column_code_mapper order by tablename,columnname; create table column_code_mapper as select distinct upper(colactualtablename) as tablename, upper(colactualname) as columnname, replace(replace(coleditsource5,'''',''),' ','') as coleditsource from ( select t.*, case when instr(coleditsource4,'=',1) >0 then trim(substr(coleditsource4,instr(coleditsource4,'=',1)+1)) when instr(coleditsource4,'(',1) >0 and instr(coleditsource4,')',1) >0 then trim(substr(coleditsource4,instr(coleditsource4,'(',1)+1,instr(coleditsource4,')',1)-instr(coleditsource4,'(',1)-1)) else trim(coleditsource4) end as coleditsource5 from ( select t.*, case when instr(lower(coleditsource3),' order ',1)>0 then substr(coleditsource3,1,instr(lower(coleditsource3),' order ',1)) else coleditsource3 end as coleditsource4 from ( select t.*, case when instr(lower(coleditsource2),' and ',1)>0 then substr(coleditsource2,1,instr(lower(coleditsource2),' and ',1)) else coleditsource2 end as coleditsource3 from ( select adc.dono, adc.jboclass, adc.jbofrom, adc.jbowhere, adl.colindex, case when lower(adl.coltablename) = 'o' or adl.coltablename is null or trim(adc.jboclass) = trim(adc.jbofrom) then substr(jboclass,instr(jboclass,'.',-1)+1) else substr(substr(adc.jbofrom,1,instr(upper(adc.jbofrom),upper(' '||trim(adl.coltablename)))), instr(substr(adc.jbofrom,1,instr(upper(adc.jbofrom),upper(' '||trim(adl.coltablename)))),'.',-1)+1) end as colactualtablename, adl.coltablename, adl.colactualname, adl.colheader, adl.coleditsourcetype, coleditsource, case when lower(adl.coleditsourcetype) = 'jbo' then substr(adl.coleditsource,instr(adl.coleditsource,',',1,3)+1) else adl.coleditsource end as coleditsource2 from awe_do_catalog adc,awe_do_library adl where adc.dono = adl.dono and adl.coleditsource is not null and instr(adl.colactualname,'NameManager.',1) = 0 and ((lower(adl.coleditsourcetype) = 'jbo' and instr(adl.coleditsource,'jbo.ui.system.CODE_LIBRARY')>0) or lower(adl.coleditsourcetype) = 'code') ) t )t)t)t,user_tab_columns uc where uc.table_name = upper(t.colactualtablename) and uc.column_name = upper(t.colactualname) and exists (select 1 from mytab where uc.table_name = tablename) --检查字段中文乱码 select t1.table_name,t1.column_name,t4.comments,'comment on column '|| t3.table_name||'.'||t3.column_name||' is '''';' from user_col_comments t1,mytab t2,user_tab_columns t3,user_tab_comments t4 where t1.table_name = t2.tablename and (t1.comments is null or t1.comments like '%?%') and t1.table_name = t3.table_name and t1.column_name = t3.column_name and t3.table_name = t4.table_name order by t1.table_name,t1.column_name --导出成codelist.xlsx select distinct cl.codeno as "代码编号", nvl(cc.codename,cc.codetypetwo) as "代码名称", cl.itemno as "代码项编号", cl.itemname as "代码项名称", decode(cl.isinuse,'1','Y') as "是否使用", decode(cc.codetypeone,'数据标准化','Y') as "是否落标" from code_library cl left join code_catalog cc on cl.codeno = cc.codeno inner join column_code_mapper t on cl.codeno = t.coleditsource or t.coleditsource like cl.codeno||',%' or t.coleditsource like '%,'||cl.codeno||',%' or t.coleditsource like '%,'||cl.codeno order by cl.codeno,cl.itemno; --导出成colcodelist.xlsx select distinct t.tablename as "表名", t.columnname as "字段名", cl.codeno as "代码编号" from code_library cl left join code_catalog cc on cl.codeno = cc.codeno inner join column_code_mapper t on cl.codeno = t.coleditsource or t.coleditsource like cl.codeno||',%' or t.coleditsource like '%,'||cl.codeno||',%' or t.coleditsource like '%,'||cl.codeno order by t.tablename,t.columnname,cl.codeno;
反向生成pdm
File/New Model/Physical Program
Database/Update Model From Database/,选择Connection Type 为JDBC
Tools/Execute Commands/Run Scripts,选择上面的Export2Excel.vba