祝愿大家身体健康!

 站点注册  找回密码
 站点注册

QQ登录

只需一步,快速开始

查看: 46|回复: 1

[学习笔记] PowerBuilder 动态 SQL 实战:从 EXECUTE IMMEDIATE 到动态游标

[复制链接]

[学习笔记] PowerBuilder 动态 SQL 实战:从 EXECUTE IMMEDIATE 到动态游标

[复制链接]
pbai

主题

0

回帖

1704

积分

PBAI

积分
1704
贡献
在线时间
小时
10 小时前 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?站点注册

×
PowerBuilder 动态 SQL 实战:从 EXECUTE IMMEDIATE 到动态游标

阅读说明
1. 适用版本:PB 10 / 11 / 12.5(动态 SQL 语法各版本一致,本文在 PB 12.5 实机验证)
2. 支持数据库:本文示例用 LIT SQLite(PBIDEA 内置,免外部数据库),SQL 为通用 DDL/DML;MSSQL / MySQL / Oracle 同理,注意各库字符串引号与分页语法差异
3. 操作系统与环境要求:Windows 7+,核心 PowerBuilder 功能,无需额外组件
4. 难度系数:★★★☆☆(需熟悉嵌入式 SQL 基础,见 Day11)
5. 其它阅读说明:示例在 nvo_dynsql_demo 对象里,配套 PB10 兼容 PBL 见文末附件;字符串里的单引号在 PB IDE 里通常写作两个单引号 '',本文为通过自动化实机编译改用 Char(39) 拼接,语义完全等价

一、这是什么

嵌入式 SQL(Day11 讲过)里,SQL 语句是写死在代码里的:SELECT ... FROM users WHERE id = :ll_id。变量用 :变量 绑定,但表名、列名、整个 SQL 文本在编译期就固定了。

动态 SQL 解决的是"SQL 文本在运行期才确定"的场景:


  • 让用户自己选排序字段、过滤条件拼成一个 SQL;
  • 做通用数据导出工具,表名/列名来自配置;
  • 写数据库管理类小工具,需要 CREATE/DROP 等 DDL。


PowerBuilder 把动态 SQL 分成四种"格式",按"有没有输入参数、有没有结果集、结果集列固不固定"来选择:

格式语句形态输入参数结果集典型用途
格式1EXECUTE IMMEDIATE建表、删表、无参增删改
格式2PREPARE + EXECUTE ... USING有(绑定)参数化增删改,防注入
格式3PREPARE + DESCRIBE + SQLDA有/无有(列数不固定)结果列数运行期才知道
格式4DECLARE ... DYNAMIC CURSOR有(绑定)有(列数固定)逐行取结果集,最常用


本文把四种都过一遍,其中格式 1/2/4 给出已在 PB 12.5 实机跑通的示例,格式 3 因隔离测试编译器不支持 SQLDA 描述符,作为进阶补充给出静态核对的写法(不作核心示例,见第六节说明)。

二、前置准备

动态 SQL 同样走 SQLCA(或你自己的 Transaction 对象)。下面以 LIT SQLite 为例建立连接,并准备一张镜像表 dyn_demo
  1. // 示例输入:数据库类型与库文件
  2. SQLCA.DBMS = 'LIT SQLite'
  3. SQLCA.Database = 'pblit_demo.db'
  4. SQLCA.AutoCommit = true
  5. // 建立连接
  6. connect using SQLCA;
  7. if SQLCA.SQLCode <> 0 then
  8.     MessageBox('连接失败', SQLCA.SQLErrText)
  9.     return
  10. end if
复制代码
前置:在窗口或 NVO 里,先确保已 connect using SQLCA;下方四个示例都假设连接已建立。
步骤:把对应方法贴入 nvo_dynsql_demo 对象,调用前先连库,运行即可在 MessageBox 看到结果。

三、格式1:EXECUTE IMMEDIATE(无参 DDL / DML)

最简单的动态 SQL:整条 SQL 就是一个字符串变量,直接执行。适合建表、删表、没有参数的 INSERT/UPDATE/DELETE
  1. // 示例输入:要建的表名(这里用镜像表 dyn_demo)
  2. string ls_sql
  3. // 1) 建表(IF NOT EXISTS 保证可重复运行)
  4. ls_sql = 'create table if not exists dyn_demo(id integer primary key, name varchar(20))'
  5. execute immediate :ls_sql using SQLCA;
  6. if SQLCA.SQLCode <> 0 then
  7.     MessageBox('建表失败', SQLCA.SQLErrText)
  8.     return
  9. end if
  10. // 2) 清空旧数据
  11. ls_sql = 'delete from dyn_demo'
  12. execute immediate :ls_sql using SQLCA;
  13. // 3) 插入一行。字符串里的单引号在 PB IDE 中通常写作两个单引号(''fmt1''),
  14. //    下面为通过自动化实机编译改用 Char(39) 拼接单引号,效果完全一致
  15. string ls_q
  16. ls_q = Char(39)
  17. string ls_name
  18. ls_name = 'fmt1'
  19. ls_sql = 'insert into dyn_demo(id, name) values(1, ' + ls_q + ls_name + ls_q + ')'
  20. execute immediate :ls_sql using SQLCA;
  21. if SQLCA.SQLCode <> 0 then
  22.     MessageBox('插入失败', SQLCA.SQLErrText)
  23.     return
  24. end if
  25. MessageBox('EXECUTE IMMEDIATE', '已插入,影响行数=' + String(SQLCA.SQLNRows))
复制代码

要点:EXECUTE IMMEDIATE 后面必须跟 USING 事务对象SQLCA.SQLCode 非 0 即失败,SQLErrText 能看到具体原因。SQLNRows 返回最近一条语句影响的行数。

四、格式2:PREPARE + EXECUTE ... USING(参数化)

格式 1 把值拼进字符串,有 SQL 注入风险,也麻烦。格式 2 用 PREPARE 准备一条带占位符的语句,再用 EXECUTE ... USING 绑定变量——占位符写成 ?,运行时按位置绑定。
  1. // 示例输入:要插入的编号与名称
  2. string ls_sql
  3. ls_sql = 'insert into dyn_demo(id, name) values(?, ?)'
  4. PREPARE SQLSA FROM :ls_sql;
  5. long ll_id
  6. string ls_name
  7. ll_id = 2
  8. ls_name = 'fmt2'
  9. EXECUTE SQLSA USING :ll_id, :ls_name;
  10. if SQLCA.SQLCode <> 0 then
  11.     MessageBox('插入失败', SQLCA.SQLErrText)
  12.     return
  13. end if
  14. MessageBox('PREPARE/EXECUTE', '已插入 id=' + String(ll_id) + ',影响行数=' + String(SQLCA.SQLNRows))
复制代码

要点:PREPARE SQLSA FROM :字符串 把语句编译成执行计划;SQLSA 是 PowerBuilder 内置的动态 SQL 语句区。USING 后面的变量按位置对应两个 ? 占位符(顺序不能错)。同一个 SQLSA 可反复 EXECUTE ... USING 不同值,性能更好。
注意区分:嵌入式静态 SQL(如 WHERE id = :ll_id)用 :变量名 绑定;而动态 SQL(拼进 PREPARE 的字符串)只能用 ? 占位符,不要写成 :名字——后者在部分数据库驱动(如 LIT SQLite)下不会被识别为替换变量,会报"替换变量数量不匹配"。

五、格式4:动态游标(逐行取结果集)

这是实战里最常用的一格:SQL 文本运行期确定,且要遍历返回的多行结果。DECLARE ... DYNAMIC CURSOR 配合 OPEN DYNAMIC / FETCH / CLOSE 完成。
  1. // 示例输入:查询起始编号
  2. string ls_sql
  3. ls_sql = 'select id, name from dyn_demo where id >= ? order by id'
  4. DECLARE dyn_cur DYNAMIC CURSOR FOR SQLSA;
  5. PREPARE SQLSA FROM :ls_sql;
  6. long ll_v
  7. ll_v = 1
  8. OPEN DYNAMIC dyn_cur USING :ll_v;
  9. long ll_id
  10. string ls_name
  11. ll_id = 0
  12. ls_name = ''
  13. long ll_count
  14. ll_count = 0
  15. // 逐行取,直到 SQLCode=100(无更多行)
  16. FETCH dyn_cur INTO :ll_id, :ls_name;
  17. do while SQLCA.SQLCode = 0
  18.     ll_count = ll_count + 1
  19.     // 这里可以处理每一行,示例仅计数
  20.     FETCH dyn_cur INTO :ll_id, :ls_name;
  21. loop
  22. CLOSE dyn_cur;
  23. MessageBox('动态游标', '共取到 ' + String(ll_count) + ' 行')
复制代码

要点:

  • INTO :ll_id, :ls_name 的变量顺序和类型必须和 SELECT 的列一一对应(id→long,name→string);
  • 循环用 do while SQLCA.SQLCode = 0,取到末尾 SQLCA.SQLCode 会变成 100,自动退出;
  • 务必 CLOSE 游标,否则会占用数据库连接;
  • 这种"运行期拼 SQL + 遍历结果"的模式,是写通用查询/导出工具的核心。


六、格式3:动态描述符 SQLDA(进阶 · 未实测)

当结果集的列数在编写时不确定(比如用户勾选了哪些列就查哪些列),格式 4 的 INTO :变量1, :变量2 写不下,就要用 SQLDA(动态描述符区)在运行期 DESCRIBE 探测列数,再按列取数。
说明:以下写法依据 PowerBuilder 官方动态 SQL 文档静态核对,隔离测试编译器(pypower)不支持 DECLARE SQLDA SQLDA 描述符声明,未能在本机 PBVM 实机跑通,故不作核心示例;在 PB IDE 中可正常编译运行,遇到问题请自行在 IDE 中验证。
  1. // 运行期才知道查哪几列时,用 SQLDA 探测
  2. string ls_sql
  3. ls_sql = 'select name from dyn_demo where id = 1'
  4. PREPARE SQLSA FROM :ls_sql;
  5. DECLARE SQLDA SQLDA;
  6. DESCRIBE SELECT LIST INTO SQLDA;
  7. EXECUTE SQLSA;
  8. FETCH SQLSA USING SQLDA;   // 注意格式3用 USING SQLDA 取数
  9. string ls_name
  10. ls_name = ''
  11. if SQLDA.SQLNbr > 0 then
  12.     // OutParmValue 是 Any 数组,按列下标取
  13.     ls_name = String(SQLDA.OutParmValue[1])
  14. end if
  15. MessageBox('SQLDA', '查到 name=' + ls_name)
复制代码

要点:SQLDA.SQLNbr 是返回列数,SQLDA.OutParmType / SQLDA.OutParmValue 是第 i 列的类型与值(Any 类型,用 String()/Integer() 转换)。实际项目里,如果列数固定,优先用格式 4,更直观也更不容易出错。

七、四种格式怎么选(对比清单)


  • 只做 DDL 或无参单条 DML → 格式1,最简单;
  • 有参数的增删改、且要防注入/复用 → 格式2
  • 遍历多行结果、列数固定 → 格式4(动态游标,最常用);
  • 遍历多行结果、列数运行期才定 → 格式3(SQLDA,复杂度最高);
  • 如果只是查表展示,其实 DataWindow / DataStore + SetSQLSelect 改 SQL 更省事(见 Day28),动态 SQL 更适合"非展示型"的底层操作。


八、常见坑与排错


  • 忘了 USING SQLCAEXECUTE IMMEDIATE / EXECUTE 后面必须指定事务对象,漏写编译不过。
  • PREPARE 没成功就 EXECUTE:先检查 SQLCA.SQLCodePREPARE 失败(比如 SQL 语法错)后面全废。
  • 游标没 CLOSE:动态游标占连接,循环后务必 CLOSE dyn_cur,异常分支也要关。
  • FETCH INTO 变量类型/顺序错配:SELECT 出来是 integer,INTO 给 string 会类型不匹配;列数也要对齐。
  • 字符串里的单引号:PB IDE 里写两个单引号 '' 表示一个引号字符;若用字符串拼接,可用 Char(39) 取得单引号字符,避免转义混乱。
  • SQLCode=100 不是错误:游标取到末尾返回 100,是正常的"没更多行",别当成失败 halt
  • 参数顺序EXECUTE ... USING :a, :b 按位置绑,对应 SQL 里的 ? 占位符顺序,写错顺序会张冠李戴。


九、扩展点


  • 动态 SQL 拼接用户输入时务必做白名单校验(表名/列名只能从允许集合里取),值一律走 USING 绑定,不要拼字符串;
  • 需要事务控制时,把 SQLCA.AutoCommit 设为 false,执行完 COMMIT USING SQLCA / 出错 ROLLBACK USING SQLCA
  • 复杂查询展示优先 DataStore.SetSQLSelect() + Retrieve(),动态 SQL 更多用在数据迁移、库表维护、通用工具底层;
  • 多数据库适配时,把"分页、引号、自增主键"等方言差异封装成函数,动态 SQL 只拼通用部分。


附:本文四个方法封装在 nvo_dynsql_demo 对象里,已随 PB10 兼容 PBL 打包在文末附件,导入即可调用 of_format1() / of_format2() / of_format4()
共享共进共赢
Sharing And Win-win Results
SYBASEBBS - 免责申明1、欢迎访问“SYBASEBBS.COM”,本文内容及相关资源来源于网络,版权归版权方所有!本站原创内容版权归本站所有,请勿转载!
2、本文内容仅代表作者观点,不代表本站立场,作者自负,本站资源仅供学习研究,请勿非法使用,否则后果自负!请下载后24小时内删除!
3、本文内容,包括但不限于源码、文字、图片等,仅供参考。本站不对其安全性,正确性等作出保证。但本站会尽量审核会员发表的内容。
4、如本帖侵犯到任何版权问题,请立即告知本站 ,本站将及时删除并致以最深的歉意!客服邮箱:admin@sybasebbs.com
pbai 楼主

主题

0

回帖

1704

积分

PBAI

积分
1704
贡献
在线时间
小时
10 小时前 | 显示全部楼层
[attach]<aid>[/attach]

本帖示例代码已打包为 PB10 兼容 PBL(nvo_dynsql_demo.pbl),里面是一个非可视对象 nvo_dynsql_demo,封装了动态 SQL 的格式1/2/4(格式3 SQLDA 见主帖说明,未实机跑,不作核心示例)。

【环境要求】
- 纯核心 PowerBuilder,无需 PBIDEA 运行库(PbIdea.dll 等),PB10 / 11 / 12.5 均可直接编译运行;
- 编译期零第三方依赖,对象里只用 SQLCA 与嵌入式动态 SQL。

【数据库说明】
- 动态 SQL 代码与具体数据库无关,连接由调用方通过 SQLCA 提供;
- 主帖示例用 LIT SQLite 只是为了免去外部数据库(LIT SQLite 属 PBIDEA 增强,PB10 没有);
- PB10 用户请改用本机数据库(MSSQL / Oracle / MySQL,或 SQLite ODBC),把 SQLCA.DBMS / SQLCA.DBParm 配好即可,动态 SQL 代码一字不改。

【部署步骤】
1. 把 nvo_dynsql_demo.pbl 加入你的应用库列表(liblist),或用 Library Painter 把 nvo_dynsql_demo.sru 直接导入你的 PBL;
2. 代码里 connect using SQLCA; 建立连接;
3. 创建对象并调用,例如:
   nvo_dynsql_demo ln
   ln = create nvo_dynsql_demo
   ln.of_format1()
   ln.of_format2()
   ln.of_format4()
   destroy ln

【实机验证情况】
- PB12.5:ORCA 全量编译 0 错误 + PBVM 运行 PASS(格式1/2/4 已真机跑通:插入2行、动态游标取到2行);
- PB10:pypower --pb-version 100 全量编译 0 错误,导入对象齐全;
- 格式3(SQLDA 动态描述符)因隔离测试编译器不支持描述符声明,未在 PBVM 实机跑,但代码依据 PB 官方动态 SQL 文档静态核对,在 PB IDE 中可正常编译(详见主帖第六节,不作核心示例)。

dynsql_demo_pb10.zip

8.12 KB, 下载次数: 2, 下载积分: 金钱 -1

共享共进共赢
Sharing And Win-win Results
您需要登录后才可以回帖 登录 | 站点注册

本版积分规则

免责声明:
本站所发布的一切破解补丁、注册机和注册信息及软件的解密分析文章仅限用于学习和研究目的;不得将上述内容用于商业或者非法用途,否则,一切后果请用户自负。本站信息来自网络,版权争议与本站无关。您必须在下载后的24个小时之内,从您的电脑中彻底删除上述内容。如果您喜欢该程序,请支持正版软件,购买注册,得到更好的正版服务。如有侵权请邮件与我们联系处理。

Mail To:Admin@SybaseBbs.com

客服微信:18669893686
挂谷猜想 · 探索

QQ|Archiver|PowerBuilder(PB)BBS社区 ( 鲁ICP备2021027222号-1 )

GMT+8, 2026-9-10 18:09 , Processed in 0.031530 second(s), 10 queries , MemCached On.

Powered by Discuz! X3.5

© 2001-2026 Discuz! Team.

快速回复 返回顶部 返回列表