简介:本资源是一套面向PowerBuilder初学者与中级开发者的数据库应用实战教程,聚焦数据窗口开发、事务管理、SQL高级操作及客户端/服务器架构实现,帮助开发者快速掌握PB在企业级数据库系统中的典型工程实践。压缩包共172个文件,总大小7.42MB,包含9个PBL/PBD工程库文件(封装可复用业务逻辑)、14个SQL脚本(覆盖建库、建表、存储过程及复杂查询)、8套数据库备份(.bak/.mdf/.ldf)与配套图标(.ico)、位图(.bmp)等资源,支撑完整项目运行与界面定制。已有243人学习下载,内容结构清晰,涵盖从数据源连接、DataWindow设计、事件编程到报表图表生成的全链路开发环节,附带多行业业务模型(如酒店、HRM、MRP、EIS等)的可运行案例,便于对照调试与二次开发。
1. PowerBuilder数据库开发经典案例解析:不是学语法,而是搞懂“PB怎么把SQL变成可交付的.exe”
你手头这个.rar文件,名字叫《PowerBuilder数据库开发经典案例解析》,但它真正值钱的地方,从来不是里面那几十个.pbl或.srw文件——而是它背后一整套面向企业级业务系统的数据交互范式:如何让一个 PB 应用在 Windows 桌面环境里,稳定、可控、可维护地和 SQL Server(尤其是 2008/2012 这类老但仍在产线跑的版本)完成连接、查询、更新、事务回滚、错误捕获,甚至带权限控制的多用户并发操作。这不是写几个SELECT * FROM user_info就完事的事;这是在没有 ORM、没有连接池抽象、没有现代日志框架的年代,靠SQLCA、DataWindow和Transaction Object三件套硬扛出来的工程实践。它适合两类人:一类是还在维护银行柜台系统、电力调度终端、医保结算平台的老工程师,需要快速定位“为什么换台电脑就连不上”;另一类是刚接手遗留系统的新手,得从真实案例里抠出 PB 的“行为逻辑”——比如为什么SQLCA.AutoCommit = False必须在CONNECT之后设,为什么dw_1.SetTransObject(SQLCA)不能放在Open事件最开头。这本案例集,本质是一份PowerBuilder 数据库层的生存手册。
2. 从解压到运行:还原经典案例的最小可执行路径
PowerBuilder 开发环境不是“装完就能跑”,尤其当你面对的是十年前打包的.rar,里面很可能混着 PB 11.5、12.5 或 12.6 的源码,而你的机器装的是 PB 2019 或更高版本。别急着双击xxx.pbt——先做三件事:确认 PB 版本兼容性、重建工作区依赖、验证数据库连接链路。下面是我反复验证过的最小启动路径,不跳步、不省略、不假设你有管理员权限。
2.1 解压与目录结构识别:先看懂这个.rar在说什么
用 7-Zip 或 WinRAR 解压后,典型目录结构如下(以某银行客户管理案例为例):
PB_Case_Bank/ ├── pbwork/ │ ├── bank.pbt ← 主工作区文件(必须存在) │ ├── bank.pbl ← 主程序库(含窗口、函数、结构) │ └── data.pbl ← 数据窗口库(含 dw_customer.srd, dw_order.srd 等) ├── sql/ │ └── init_db.sql ← 创建表、视图、存储过程的脚本(关键!) ├── db/ │ └── bank.mdf ← SQL Server 数据库文件(注意:仅限 SQL Server Express 本地实例) └── readme.txt ← 往往只有一行:“请用 PB 12.5 打开”提示:
readme.txt里的版本号是铁律。PB 12.5 编译的 PBL,用 PB 2019 直接打开会触发自动升级,但升级后可能破坏DataWindow的 SQL 语法兼容性(比如COMPUTE字段在新版本里解析规则变化)。我的做法是:先用对应版本 PB 打开,导出为.srw/.srd文本格式,再导入新版本——这样能保留原始 SQL 和表达式逻辑。
2.2 数据库准备:SQL Server 2008 实例的“静默安装”式配置
案例里init_db.sql通常包含CREATE DATABASE bank ON (FILENAME='...'),但直接sqlcmd -i init_db.sql很可能失败——因为路径权限、SQL Server 身份验证模式、或master数据库是否允许AUTO_CLOSE=OFF。我推荐用 SQL Server Management Studio(SSMS)手动建库,步骤极简:
-- 1. 新建数据库(关键:兼容级别设为 100,对应 SQL Server 2008) CREATE DATABASE bank ON PRIMARY ( NAME = 'bank_data', FILENAME = 'C:\Program Files\Microsoft SQL Server\MSSQL10_50.MSSQLSERVER\MSSQL\DATA\bank.mdf' ) LOG ON ( NAME = 'bank_log', FILENAME = 'C:\Program Files\Microsoft SQL Server\MSSQL10_50.MSSQLSERVER\MSSQL\DATA\bank.ldf' ); GO ALTER DATABASE bank SET COMPATIBILITY_LEVEL = 100; GO -- 2. 启用 TCP/IP 并添加登录用户(PB 默认用 sa 或 Windows 认证) USE master; GO CREATE LOGIN pb_user WITH PASSWORD = 'P@ssw0rd123', CHECK_POLICY = OFF; GO USE bank; GO CREATE USER pb_user FOR LOGIN pb_user; GO EXEC sp_addrolemember 'db_owner', 'pb_user'; GO参数说明:
COMPATIBILITY_LEVEL = 100是血泪经验——PB 12.x 生成的SELECT语句若含TOP n+ORDER BY,在 SQL Server 2012+ 默认兼容级别下会报错“ORDER BY 子句在视图、内联函数、派生表、子查询中需要 TOP、OFFSET 或 FOR XML”。设成 100 就绕过此限制。pb_user是专为 PB 应用创建的账号,避免用sa带来的安全审计风险。
2.3 PowerBuilder 工作区重建:绕过.pbw定位失败的三种实操法
热词里提到“PowerBuilder打开不能定位到当前的pbw”,这其实是 PB 的Library List和Search Path机制在作祟。.pbw(PowerBuilder Workspace)本质是 XML 描述文件,记录了所有.pbl的绝对路径。一旦移动文件夹,PB 就找不到bank.pbl。解决方法有且只有三种,按优先级排序:
重置 Library List(推荐新手)
打开 PB →File→Open→ 选中bank.pbt→ 弹窗提示“找不到关联的 .pbw” → 点No→ 进入空工作区 →File→New→Target→Workspace→ 输入bank→OK→ 右键Libraries→Add Library→ 逐个添加bank.pbl和data.pbl。手动编辑
.pbw(熟手必备)
用记事本打开bank.pbw,找到<library>标签,把path="D:\old\path\bank.pbl"改成当前实际路径,如path="C:\PB_Case_Bank\pbwork\bank.pbl"。保存后双击.pbw即可。用命令行强制指定路径(自动化部署场景)
# 在 PB 安装目录下执行(如 C:\Program Files\Sybase\PowerBuilder 12.5) pb125.exe "C:\PB_Case_Bank\pbwork\bank.pbt" /L"C:\PB_Case_Bank\pbwork"/L参数告诉 PB:所有.pbl都在这个目录下找,忽略.pbw里的旧路径。
3. 数据库连接落地:OLE DB 连接字符串的 4 个必调参数与跨机适配原理
案例里SQLCA.DBMS = 'OLE DB'是主流选择,但SQLCA.Database = 'bank'这种写法在本机能通,换电脑就崩——根本原因在于 OLE DB Provider 对实例名、协议、端口的隐式处理差异。必须把连接字符串(SQLCA.DBParm)拆解到原子级,才能实现“一次配置,多机复用”。
3.1 最小可用连接字符串:去掉所有默认假设
以下是在 SQL Server 2008 上验证通过的SQLCA.DBParm设置(写在Application Open事件里):
// PowerBuilder 代码(非 SQL!这是 PB 的赋值语法) SQLCA.DBMS = "OLE DB" SQLCA.Database = "" SQLCA.LogId = "pb_user" SQLCA.LogPass = "P@ssw0rd123" SQLCA.ServerName = "localhost" // 关键:不要写 "(local)" 或 ".",PB 解析不一致 SQLCA.AutoCommit = False SQLCA.DBParm = "Provider=SQLOLEDB;Data Source=localhost\\SQLEXPRESS;Initial Catalog=bank;Integrated Security=No;Connect Timeout=15;"逻辑说明:
SQLCA.DBParm是 OLE DB 连接字符串的载体,PB 不解析其中内容,直接透传给 Windows OLE DB Provider。Data Source=localhost\\SQLEXPRESS中的\\是转义符,表示实例名SQLEXPRESS;若用默认实例,则写Data Source=localhost。Integrated Security=No强制走 SQL Server 账号认证,避免 Windows 身份在域环境下的不可控。
3.2 跨机部署时的三类连接故障与对应修复项
| 故障现象 | 原因定位点 | 修复操作 |
|---|---|---|
| “Login failed for user 'pb_user'” | SQL Server的SQL Server and Windows Authentication mode未启用 | SSMS → 右键服务器 →Properties→Security→ 选中“SQL Server 和 Windows 身份验证模式” → 重启服务 |
| “Named Pipes Provider: Could not open a connection…” | SQL Server Configuration Manager中TCP/IP协议被禁用 | 展开SQL Server Network Configuration→Protocols for SQLEXPRESS→ 启用TCP/IP→ 右键Properties→IP Addresses标签 → 找到IPAll→ 清空TCP Dynamic Ports,填入TCP Port=1433 |
| “Provider cannot be found” | 目标机缺少SQLOLEDBProvider(常见于 Win10/Win11 新装机) | 下载并安装 Microsoft Data Access Components (MDAC) 2.8 或直接安装SQL Server Native Client |
3.3 连接验证:用SQLCA.SQLCode和SQLCA.SQLErrText写死的排错逻辑
别信 PB 的Connected属性——它只反映上次CONNECT的缓存状态。真验证,必须执行一条轻量 SQL:
// 在 CONNECT 之后立即执行 string ls_test_sql = "SELECT COUNT(*) FROM sysobjects WHERE type='U'" DECLARE test_cursor DYNAMIC CURSOR FOR SQLSA; PREPARE SQLSA FROM :ls_test_sql; OPEN DYNAMIC test_cursor; FETCH test_cursor INTO :ll_count; CLOSE test_cursor; IF SQLCA.SQLCode <> 0 THEN MessageBox("数据库连接失败", "错误:" + SQLCA.SQLErrText + "(SQLCode=" + String(SQLCA.SQLCode) + ")") RETURN END IF参数说明:
sysobjects是 SQL Server 2008 兼容视图,type='U'查用户表数,执行快、权限要求低。SQLCA.SQLCode为 0 表示成功,非 0 则SQLErrText给出具体错误(如SQLCode=-1通常是网络不通,SQLCode=-9是登录失败)。这段代码必须放在CONNECT之后、任何业务逻辑之前,形成“连接即验证”的闭环。
4. DataWindow 数据库交互避坑:SQL 生成、参数绑定与事务控制的三个致命陷阱
PowerBuilder 的DataWindow是双刃剑:它让 CRUD 变得可视化,但也把 SQL 生成、参数传递、事务边界藏在黑匣子里。案例里常见的翻车点,90% 出现在Retrieve()、Update()和AcceptText()这三个动作上。下面列出我在银行项目里踩过的最痛的三条坑,每条都附带可复制的修复代码。
4.1 坑一:Retrieve()时WHERE条件被 PB 自动加引号导致 SQL 报错
现象:dw_1.SetTransObject(SQLCA)正常,dw_1.Retrieve("张三")却报错“Conversion failed when converting varchar to int”。
原因:PB 默认把字符串参数当char处理,生成的 SQL 是WHERE name = '张三',但如果数据库字段是nvarchar且 collation 不匹配,就会触发隐式转换失败。
解决:显式声明参数类型,并在 SQL 里用N'张三':
// 错误写法(PB 自动加单引号) dw_1.Retrieve("张三") // 正确写法:用 Declare + Prepare 绕过 PB 的自动拼接 string ls_sql = "SELECT id, name FROM customer WHERE name = ?" DECLARE cust_cursor DYNAMIC CURSOR FOR SQLSA; PREPARE SQLSA FROM :ls_sql; OPEN DYNAMIC cust_cursor USING :as_name; // as_name 是 string 变量 dw_1.SetTransObject(SQLCA) dw_1.ReSet() dw_1.ImportSQL(SQLSA, "cust_cursor") // 导入结果集到 DW4.2 坑二:Update()失败却不报错,数据静默丢失
现象:dw_1.Update()返回 1(表示成功),但数据库里没更新,SQLCA.SQLCode还是 0。
原因:PB 的Update()默认只提交Modified!状态的行,但如果DataWindow的Update Properties里Key Columns没设对(比如漏了主键字段),PB 就生成UPDATE table SET ... WHERE 1=0这种永假条件。
解决:在DataWindow设计器里右键 →Update Properties→Specify Update Key→ 勾选所有主键字段(如id),并确认WHERE Clause选项卡里Use only key columns被选中。
4.3 坑三:AcceptText()触发两次ItemChanged,导致金额字段重复计算
现象:在金额输入框里输100,ItemChanged事件里做了this.Text = String(Decimal(this.Text) * 100),结果变成10000。
原因:AcceptText()会先触发ItemChanged(此时值还是字符串"100"),再触发ItemFocusChanged(此时值已转为数值100),而 PB 的ItemChanged事件在AcceptText()内部被调用两次。
解决:加状态锁,只让第一次生效:
// 在窗口声明区定义实例变量 boolean ib_accepting = false // 在 ItemChanged 事件里 IF ib_accepting THEN RETURN ib_accepting = true decimal ld_amt ld_amt = Decimal(this.Text) this.Text = String(ld_amt * 100) ib_accepting = false注意:
ib_accepting必须是窗口级实例变量(Instance Variable),不能是局部变量,否则每次事件都是新变量,锁失效。
5. 案例复现后的进阶验证:用 SQL Profiler 抓包 + 日志比对法定位“连接成功但查不到数据”类问题
当你确认 PB 连上了 SQL Server,SQLCA.SQLCode = 0,dw_1.Retrieve()也返回行数,但界面上就是空白——这种问题最折磨人。教科书不会告诉你,PB 的DataWindow在后台干了什么。我的标准排查流程是:用 SQL Server Profiler 抓真实 SQL + 在 PB 里打日志比对 + 检查 DW 的Modify属性。三步缺一不可。
5.1 Step 1:用 SQL Profiler 捕获 PB 发出的真实 SQL
启动 SQL Server Profiler(SSMS →Tools→SQL Server Profiler),新建 Trace:
- Events Selection:勾选
TSQL→SQL:BatchStarting、SQL:StmtStarting、RPC:Completed - Column Filters:
ApplicationName包含PowerBuilder,DatabaseName等于bank - Run,然后在 PB 里点击
Retrieve
你会看到类似这样的语句:
exec sp_executesql N'SELECT id, name, amount FROM customer WHERE name LIKE @P1', N'@P1 nvarchar(10)', N'%张%'注意两点:① PB 把LIKE参数自动转成nvarchar;②sp_executesql是预编译执行,不是直连SELECT。如果 Profiler 里没看到这条 SQL,说明Retrieve()根本没发出去——问题在 DW 的Data Source或Transaction Object绑定。
5.2 Step 2:PB 端日志输出与 Profiler 结果比对表
在dw_1的RetrieveStart事件里加日志:
// RetrieveStart 事件 string ls_sql ls_sql = dw_1.Object.DataWindow.Table.Select // 获取 DW 当前 SQL MessageBox("DW SQL", ls_sql) // 或写入文件:FileWrite(...)然后对比 Profiler 抓到的 SQL 和ls_sql是否一致。常见差异:
| DW 设计器里写的 SQL | Profiler 抓到的 SQL | 原因 |
|---|---|---|
SELECT * FROM customer | SELECT id, name, amount FROM customer | PB 自动生成列名,不认* |
WHERE name = :name | WHERE name = @P1 | PB 把命名参数转成位置参数 |
ORDER BY create_time DESC | ORDER BY create_time DESC OFFSET 0 ROWS | PB 12.5+ 自动加分页语法,但 SQL Server 2008 不支持 |
关键技巧:如果 Profiler 有 SQL,但结果为空,立刻检查
SELECT里的字段是否全在 DW 的Columns列表里——PB 会过滤掉没定义的字段,导致dw_1显示为空白,哪怕数据库返回了 100 行。
5.3 Step 3:检查DataWindow的Modify属性与Protect状态
很多案例里dw_1的Modify属性被设为0(只读),或者某列的Protect表达式写了1。这会导致Retrieve()成功,但数据不显示——因为 PB 认为“你没权限看”。验证方法:
- 在
DataWindow设计器里,右键 →Properties→ 查看Modify值; - 选中任意列 →
Properties→General标签 → 看Protect表达式是否为1或yes; - 临时改成
0或0,再Retrieve,如果数据显示了,就确认是此问题。
我习惯在Open事件里统一放开:
dw_1.Modify("DataWindow.Readonly=0") dw_1.Object.id.Protect = "0" dw_1.Object.name.Protect = "0" dw_1.Object.amount.Protect = "0"最后说一句血泪经验:PB 的调试,永远要相信 Profiler 抓到的 SQL,而不是你脑子里想的 SQL。我见过太多人对着 DW 里写的WHERE status = 1疯狂改代码,结果 Profiler 显示 PB 发的是WHERE status = '1'(字符串比较),而数据库字段是tinyint——类型不匹配,索引失效,查得慢还查不到。希望帮到你。
本文还有配套的精品资源,点击获取