PowerDesigner PDM 生成器
从业务需求或表结构清单,直接生成可在 PowerDesigner 中打开的 .pdm 文件,并附带 DDL 与字段清单。
何时使用
- 用户要求「生成 PDM / PowerDesigner 模型 / 物理数据模型」且指定了版本或数据库
- 需要从文字业务描述、Excel 字段清单、已有 DDL 反推建模
- 需要把模型交付给只认 PowerDesigner 的团队
关键事实(不要凭记忆猜,已用真实 PowerDesigner 16.6 COM 打开验证)
- PDM 就是 XML。文件头为处理指令:
<?PowerDesigner AppLocale="UTF16" ID="{GUID}" Label="" LastModificationDate="UNIX_TS" Name="模型名" Objects="N" Symbols="N" Target="DBMS名" Type="{CDE44E21-9669-11D1-9914-006097355D9B}" signature="PDM_DATA_MODEL_XML" version="16.6.0.4882"?>
- PDM 模型类 GUID 恒为CDE44E21-9669-11D1-9914-006097355D9B
- DBMS 对象类 GUID 恒为4BA9F647-DAB1-11D1-9944-006097355D9B
- 根结构:Model > o:RootObject > c:Children > o:Model(表不是直接挂在 RootObject 下)
-o:RootObject必须带<a:SessionID>00000000-0000-0000-0000-000000000000</a:SessionID>,缺失会导致 PD 打开时弹模态对话框卡死(COM 表现为 OpenModel 长时间无返回 / "Invalid file")。 - 真正决定模型 DBMS 的是
c:TargetModels里的<o:TargetModel>(其TargetModelID指向.xdb顶层 ObjectID),不是c:DBMS快捷方式本身。 两者 TargetID 必须一致,否则 PD 报 "Invalid file"。c:DBMS快捷方式的TargetID与c:TargetModels中 o:TargetModel 的TargetModelID要填同一个 .xdb ObjectID。 - 元素层级(与真实 PD 15/16 文件一致):
o:Model ├ c:DBMS → o:Shortcut(TargetID 指向已安装的 .xdb 资源) ├ c:PhysicalDiagrams → o:PhysicalDiagram → c:Symbols → o:TableSymbol / o:ReferenceSymbol ├ c:DefaultDiagram → o:PhysicalDiagram Ref ├ c:Tables → o:Table → c:Columns / c:Keys / c:Indexes / c:PrimaryKey ├ c:References → o:Reference → c:ParentTable / c:ChildTable / c:ParentKey / c:Joins └ c:TargetModels → o:TargetModel(TargetModelID 指向 .xdb,决定实际 DBMS) - 命名空间:
xmlns:a="attribute" xmlns:c="collection" xmlns:o="object";跨引用用Id="oN"定义 +Ref="oN"引用,允许前向引用(图形符号可以先引用后面才定义的表)。 - 版本兼容是单向的:高版本 PD 能开低版本文件,反之不行。因此若要一份文件兼容多个 PD 版本,把
version标成目标集合中最低的那个。默认16.6(写 16.6.0.4882 等更低版本亦可被高版本打开)。 - ReferenceJoin 方向:
c:Object1= 父表列,c:Object2= 子表(外键)列。 - 外键索引命名:与引用同名(PD 惯例),多外键时靠引用名保证唯一。
- PD 反序列化对元素顺序敏感:每个对象内属性/子元素顺序必须是
... → Modifier → Comment → History → 其余集合(即 Comment 必须在 History 之前)。顺序错误时 PD 可能弹窗卡死或报 Invalid file。 - 必须保留的非空块(空白会导致打开失败/弹窗):
a:PackageOptionsText、a:ModelOptionsText、c:DefaultGroups不能空白。a:ExtendedAttributesText/c:GeneratedModels/c:GenerationOrigins可空白,但若c:TargetModels中保留了指向它们的SessionShortcuts/Ref(如 o3/o4),则对应块不能空白,否则出现悬空 Ref → Invalid file。 o:IndexColumn必须同时含<c:Column>与<c:Elements>两个子集合(各放一个<o:Column Ref="..."/>),只写<c:Column>会导致打开失败。- 物理图符号尺寸必须与 PD 原生渲染尺寸一致,否则表会重叠。PD 打开模型时会按内容把符号撑大,写得偏小就会被撑开而压住邻居。~~旧的"宽 10000 / 每列 520 高"是错的经验值(比真实值小约 37%)~~。
- 原生高度公式(已标定):
h = max(4000, 1422 + 825 × 字段数)。从一份人工维护的真实 .pdm(36 张表 / 7 个包)反推,36/36 张表误差 ≤5 单位。 - 宽度由内容自适应:取"最长一行文本"的渲染宽度 + 图标列/内边距。半角字符按 170、全角(中文)按 330(单位 1/100 mm),再乘安全系数 1.15,下限 11000、上限 26000。
- 间距:
gap_x=12000、gap_y=10000,留出空白便于查看外键连线。 - 列宽/行高逐列逐行算(该列最宽的表、该行最高的表),而不是全局统一,既不浪费也不重叠。
- 生成器
LAYOUT已内置上述参数;_grid_rects()生成后会调用_warn_overlap()自检并在有重叠时打告警。 Name才是画在符号上的文本,Comment不参与渲染(PD 的TABL显示配置里表头取DisplayName、列集合取DisplayName,且DataType No—— 数据类型不显示)。所以符号宽度只由Name决定,往Name里塞整段中文描述会把符号撑到 20000~26000 单位。- 职责约定:
Name= 简短标题(显示用,如客户订单表);Comment= 完整描述(设计说明用,可含章节引用、枚举说明)。 - 生成器行为:仅当
name == comment(说明作者只是复制了一份描述)才自动把Name压缩成标题短语——去成对括号及内容、去残留§章节号、取第一个:,;分隔符之前的标题段、限长(表 24 / 列 18 字,软截断到标点处)。若作者已给出与Comment不同的短名,则原样保留,尊重人工意图。 - 实测收益:把
name与comment解耦后,7 表规模的模型最大表宽降 46%、总宽降 28%;90 表规模的模型最大表宽降 39%、总宽降 8%。 - 关系感知排序:
order_tables()先按外键关系切连通分量,分量内从"关联最多的枢纽表"广度优先展开,使父表与子表在网格里相邻,缩短连线、减少交叉。 - 自引用关系要在表右侧画一个小回环符号(
Rect+ListOfPoints),不能退化成零尺寸点。 - 复核方法:
python pdm_validate.py out.pdm会按"每张物理图"分别检查符号矩形是否重叠(注意主图与各包子图共用同一坐标系,跨图比较会产生假重叠)。
- 原生高度公式(已标定):
-
Package(分包)的正确机制:
- 分包不是用表内的
<c:Owner><o:Package Ref/></c:Owner>(那是错的,PD 会忽略,包里 0 张表)。 - 分包也不是把所有表写在模型级
c:Tables里仅靠c:Owner链接。 - 正确做法:属于某包的表定义必须写在包自己的
<o:Package><c:Tables>…</c:Tables>内;模型级c:Tables只放未分包的表(通常为空)。 - 文档顺序(在
o:Model内):c:PhysicalDiagrams → c:Packages → c:DefaultDiagram → c:Tables → c:References → … → c:TargetModels。c:Packages必须位于c:PhysicalDiagrams之后、c:DefaultDiagram/c:Tables之前,否则 PD 报 Invalid file。 - 每个包需自带一张物理图(
<c:PhysicalDiagrams><o:PhysicalDiagram>…</o:PhysicalDiagram></c:PhysicalDiagrams>),否则该包在浏览器里不显示子图。图中符号用<c:Symbols>放本包的o:TableSymbol/o:ReferenceSymbol,只画两端都在本包内的关系。生成器用_ref_blocks.py的c:PackageDiagram块(从真实 PD 生成的包图抽取)作模板,并把内部符号注入。 - 经 PowerDesigner 16.6 COM 实测验证:90 张表分到 8 个包,每个包正确显示对应表数与子图符号数。
- 分包不是用表内的
-
CHECK 约束(表级)用
<c:Checks><o:Check>表达,属性顺序固定:
Name → Code → CreationDate → Creator → ModificationDate → Modifier → ConstraintName → ConstraintExpression → ConstraintType("R")。
<a:ConstraintName>是必需项:缺它时 PD 能打开文件,但会把整块c:Checks静默丢弃(重新保存后即消失)。
判定方法:让 PD 打开并Save()同一文件,回读是否仍有<o:Check>——保留即格式正确,消失即被丢弃。
规格写法:表级"checks": [{"name":"ck_x","code":"ck_x","expression":"col IN ('a','b')"}]。 - 从 SQL DDL 解析时的三个必踩坑(
sql2spec.py已修复,勿回退):- 行尾
--注释必须先剥离:DDL 常见「列定义后跟--注释」,按逗号切分后注释会并进下一个分片,
使列正则匹配失败而整列丢失(表现为表只剩首尾几列)。用strip_line_comments()(引号感知)。 - 命名约束以
CONSTRAINT开头:CONSTRAINT pk_x PRIMARY KEY (id)/CONSTRAINT uk_x UNIQUE (c)/
CONSTRAINT ck_x CHECK (...)都不会命中startswith("PRIMARY KEY"/"UNIQUE"/"CHECK")分支,
必须单独解析,否则主键/唯一键/检查约束全部丢失(主键丢失是最严重的静默错误)。 - 类型别名要规范化:
int8→bigint、varchar→character varying、timestamp→timestamp without time zone,
否则 PD 只会当成未知类型(能打开,但不作为原生类型参与 DDL 生成)。见TYPE_ALIAS。
- 行尾
- 复合唯一键:规格里写
"composite_uniques": [["role_id","command_type"]],生成器会建多列o:Key+
关联的 uniqueo:Index(如uk_rc_role_command)。单列唯一仍用列的"unique": true。 - 校验策略:静态校验优先,PD 只做最终验收(重要,避免把"弹窗阻塞"误判成"模型损坏")。
- 默认用
python pdm_validate.py out.pdm:秒级、无界面、零副作用。它把"PD 会静默丢弃/拒绝加载"的规则全部前置:UTF-16/声明一致性、XML 良构、占位符残留、Model 子块顺序、Ref 悬空、DBMS 三处一致、CHECK 缺ConstraintName、表无主键、符号矩形重叠、无SessionID、包缺c:PhysicalDiagrams。 - PD 的 COM 只驱动图形界面,没有 headless/批处理命令行(
PdShell16.exe本身就是 GUI 程序,其外壳里搜不到任何静默开关)。所以用 COM 就一定会有界面、也可能弹模态框。 - 用
scripts/verify_pd.ps1跑真实验收,它已处理四个坑:① 先杀残留PdShell16进程(上次异常退出会残留,导致新会话直接"灾难性故障",实测每次启动都能清掉 1~2 个残留);② 只读不保存——Save()既不必要又会触发确认框;③ 逐步追加写日志,被弹窗卡住时日志停在某一步,可据此区分"弹窗阻塞"与"文件真有问题";④OpenModel失败自动重试(首次调用偶发失败是已知现象)。 - PD 保存会把文件重新编码成 UTF-8(并在文件头写
encoding="UTF-8"),同时保留我们写入的坐标。所以校验器不能一律要求 UTF-16,真正致命的是"XML 声明的 encoding 与实际字节不一致"。
- 默认用
PostgreSQL 特别说明(已验证)
- PowerDesigner 16.6 自带的 DBMS 资源最高到 PostgreSQL 9.x(
pgsql9.xdb,位于SAP\PowerDesigner 16\Resource Files\DBMS\)。没有 PG14。 pgsql9.xdb顶层<o:DBMS>的 ObjectID =83063C86-4E0A-4954-BF00-620C0D0F02D8,即 PDM 中 DBMS Shortcut 与 TargetModel 应填的TargetID;xdb 文件名pgsql9.xdb,URL 写作file:///%[XDB]%/pgsql9.xdb(%[XDB]%是 PD 内置变量)。- 想换其它 DBMS:到本机 PD 的
Resource Files\DBMS\下找到对应.xdb,取其顶层 ObjectID 作为target_id,文件名作为xdb字段。
达梦 / Dameng 特别说明(已验证)
- 达梦 DM 没有原生
.xdb,PowerDesigner 无法直接识别。正确做法:用通用ODBC 3.0驱动承载达梦("可以用ODBC类型处理"即指此)。 odbc3.xdb顶层<o:DBMS>的 ObjectID =A8334AA1-B463-4BAB-9885-05DC861515BE,文件名odbc3.xdb,URLfile:///%[XDB]%/odbc3.xdb。- 生成器已内置预设:规格里写
"dbms": {"code": "DM8"}(或DAMENG/ODBC)即可自动解析出上述target_id与xdb,无需手填。列物理类型仍写达梦原生写法(VARCHAR2(n)、NUMBER(p,s)、DATE、TIMESTAMP、CLOB、INTEGER、BIGINT),PD 在 ODBC 目标下会原样保留这些类型串。 - 达梦常用 type 写法:
VARCHAR2(n)、NUMBER(p,s)、INTEGER、BIGINT、DATE、TIMESTAMP、CLOB、CHAR(n)。
工作流
- 收集需求:模型类型(PDM/LDM/CDM)、目标 PD 版本、目标 DBMS、需求来源(文字描述/DDL/Excel)。
- 写规格 JSON(见
specs/order_demo.json完整示例),字段说明见下。若来源是 SQL DDL,先用scripts/sql2spec.py反推规格(见下)。 - 生成:
bash python scripts/pdm_builder.py spec.json -o out.pdm --pd-version 16.6 --ddl out.sql --csv out.csv
- 用用户自己的空 PDM 模板抽取 DBMS(保证 100% 可解析):python scripts/pdm_builder.py spec.json -o out.pdm --dbms-from 用户模板.pdm - 自检 / 验收(必做,分两层):
- 第一层(默认,每次都要跑)—— 静态校验,不启动 PD:
bash python scripts/pdm_validate.py out.pdm # 输出 PASS / FAIL 与逐条问题
- 第二层(可选)—— 真实 PD 验收,只在新增了 PD 不认识的元素/属性时才需要:
powershell & scripts/verify_pd.ps1 -Pdm out.pdm # 结果见同目录 .verify.log
注意它会真的打开 PowerDesigner 界面, 也可能弹窗; 日志逐步记录, 若卡在某步就是
"弹窗阻塞"而不是"模型损坏"——此时应以pdm_validate.py的结论为准, 不要据此判定文件有问题。
COM 读数要点:$m.Tables.Item($i)必须用数字下标(用表名会抛异常, 不是模型问题);
Tables.Checks集合在 16.6 未暴露, CHECK 完整性以静态校验为准。
从 SQL DDL 生成(含按类型分包)
当来源是 PostgreSQL DDL 脚本时,用 scripts/sql2spec.py 一条命令解析为规格并直接产出 PDM(不要再手写临时转换脚本,那是过去反复出错的环节):
python scripts/sql2spec.py 路径/xxx-DDL.sql -o specs/from_sql.json \
--model-name "我的模型" --model-code "MY_MODEL" \
--pdm out.pdm --ddl out.sql --csv out_fields.csv
--pdm/--ddl/--csv可选,给了就直接产出,省掉中间手工步骤。- 外键指向但本 DDL 未定义的表会自动补"最小桩表"(只含被引用的列作主键,PB 注释标注"外部表"),避免悬空引用导致模型打不开;桩表不带
package,因此不会污染分包结构。不需要时加--no-stub。 -
只有真正属于本模块的表才会按前缀分包。
-
解析内容:
CREATE TABLE(含IF NOT EXISTS、schema 限定)、列定义(类型/长度/精度/NOT NULL/DEFAULT/自增/主键/唯一)、PRIMARY KEY/UNIQUE约束、ALTER TABLE … ADD CONSTRAINT … FOREIGN KEY … REFERENCES …、CREATE [UNIQUE] INDEX、COMMENT ON TABLE/COLUMN。 - 按前缀自动分包:脚本内置
DEFAULT_PKG_MAP(前缀→(包code, 包名, 说明)),按"最长前缀优先"匹配表名第一段: crm_→客户域(CRM)、inv_→库存域(INV)、ord_→订单域(ORD)、sys_→系统域(SYS)、log_→日志域(LOG)、pay_→支付域(PAY)、msg_→消息域(MSG)、cfg_→配置域(CFG)- 生成的 JSON 里每个表带
"package": "WMS"字段;pdm_builder.py据此把表写进对应<o:Package><c:Tables>(见事实 #12)。无匹配前缀的表不分包(落在模型级c:Tables)。 - 想换前缀/包名:编辑
sql2spec.py顶部的DEFAULT_PKG_MAP(或后续可加--package-map参数)。 - 之后照常
pdm_builder.py生成 PDM 即可(--ddl/--csv同样适用)。
> 注意:sql2spec.py 不能 100% 覆盖所有方言写法(如复合类型、分区表、自定义域、函数体里的 DDL、表内 FOREIGN KEY),生成后务必在 PD 里 Tools > Check Model 并人工抽查几张表。外键目前只解析 ALTER TABLE … ADD CONSTRAINT … FOREIGN KEY 形式。
规格 JSON 格式
{
"model": {"name": "订单中心", "code": "ORDER_CENTER", "comment": "..."},
"schema": "public",
"diagram_name": "主图", "diagram_code": "MAIN_DIAGRAM",
"dbms": {"name": "PostgreSQL 9.x", "code": "PGSQL9",
"target_id": "83063C86-4E0A-4954-BF00-620C0D0F02D8",
"target_class_id": "4BA9F647-DAB1-11D1-9944-006097355D9B",
"xdb": "pgsql9.xdb"},
"tables": [{
"name": "客户", "code": "CUSTOMER", "comment": "表说明",
"columns": [
{"name": "客户ID", "code": "id", "type": "bigint", "primary_key": true, "identity": true, "comment": "主键"},
{"name": "客户编号", "code": "customer_no", "type": "character varying", "length": 32,
"mandatory": true, "unique": true, "comment": "唯一编号"},
{"name": "单价", "code": "price", "type": "numeric", "length": 12, "precision": 2, "comment": ""},
{"name": "创建时间", "code": "created_at", "type": "timestamp without time zone", "default": "now()"}
],
"indexes": [{"name": "IDX_CUSTOMER_PHONE", "columns": ["phone"], "unique": false}]
}],
"references": [{
"name": "订单归属客户", "code": "FK_ORDERS_CUSTOMER",
"parent": "CUSTOMER", "child": "ORDERS",
"parent_columns": ["id"], "child_columns": ["customer_id"],
"cardinality": "0..*", "parent_role": "客户", "child_role": "订单"
}]
}
> 分包写法:顶层用 packages 声明包(每个含 code/name/comment),表中用 "package": "<包code>" 归入对应包。若省略顶层 packages,生成器会自动从表的 package 字段聚合出包定义(包名取 code)。完整示例见 specs/demo_packages.json。
列字段:name(中文名) code(物理列名) type length precision mandatory primary_key identity unique default comment
> name 与 comment 的分工(影响图上表宽,务必遵守):
> - name 是画在符号上的短标题,列宽/表宽由它决定 → 写短,例如 订单状态、是否启用、客户订单表。
> - comment 是完整描述,不参与渲染 → 可以写长,例如 订单状态:待付款/已付款/已发货/已完成(枚举值见数据字典)。
> - 不要把同一段长描述同时写进 name 和 comment:那样表会被撑得极宽。生成器虽会在两者完全相同时自动压缩 name,但直接从源头写短更可控。
> - 列名建议不超过 ~10 个全角字,表名不超过 ~16 个全角字。
常见 DBMS 的 type 写法
- PostgreSQL:
bigintintegercharacter varying(64)numeric(12,2)textbooleandatetimestamp without time zone - MySQL:
bigintintvarchar(64)decimal(12,2)textdatetimetinyint - Oracle:
NUMBER(19)VARCHAR2(64)NUMBER(12,2)CLOBDATETIMESTAMP - SQL Server:
bigintintnvarchar(64)decimal(12,2)datetime2bit
已知限制
- 已实现:Package 分组(按表名前缀自动划包,见上)、自引用与 M:N 中间表、复合唯一索引、外键索引、物理图平铺布局。
- 未实现:视图、存储过程、序列(Sequence)、域(Domain)、CDM/LDM、扩展属性。有需要时在
pdm_builder.py里按同样的 Id/Ref 模式扩展。 - 物理图布局按 PD 原生符号尺寸(高
max(4000, 1422+825×列数)、宽按Name内容自适应,见"关键事实"第 11 条)排成网格,间距gap_x=12000/gap_y=10000,打开后表已平铺展开、互不重叠;自引用关系画成表旁的回环符号。 - 生成 DDL 前建议先在 PD 里跑
Tools > Check Model。