PowerBuilder SyntaxFromSQL 全解:动态生成 DataWindow 的样式参数、报错形态与生成语法串的真实结构(PB10 基准 · PB12.5 实测)
PowerBuilder SyntaxFromSQL 全解:动态生成 DataWindow 的样式参数、报错形态与生成语法串的真实结构(PB10 基准 · PB12.5 实测)阅读说明
- 适用版本:PB10 基准(代码按 PB10 语法书写);PB 12.5 实机实测。两版差异见第九节。
- 支持数据库:SQL Server 2008 / 2008 R2。本文代码通过 SNC SQL Native Client(OLE DB) 接口连接(DBParm 指定 Provider='SQLNCLI10'),必须连库才有意义。
- 操作系统与环境要求:Windows 7 及以上;客户机需安装 SQL Server Native Client 10.0;登录账号需要建表权限(示例会重建一张演示表)。
- 难度系数:★★★☆☆
- 其它:建议先读《PowerBuilder DataWindow 入门》与《PowerBuilder 事务对象全解》;文中的连接参数(服务器、库、账号)请替换成你自己的环境值。
一、它是干什么的
写程序经常遇到这种需求:用户选了张表、选了几个字段,程序要在运行时把结果展示出来。表是运行时才知道的,画板里画好的 DataWindow 帮不上忙。这时有两条路:一是手写 DataWindow 语法串(那些 datawindow(units=0 ...)、table(column=(type=long ...)) 的长串,写过的人都知道有多苦),二是让 PowerBuilder 替你写——这就是 SyntaxFromSQL。
它的输入只要两样东西:一条 select 语句,一段「表现串」(描述想要什么外观);输出是一段完整的 DataWindow 语法串。拿到语法串后用 Create() 一造、SetTransObject() 一接、Retrieve() 一查,一个运行时才诞生的 DataWindow 就活了。三步走:
[*]SyntaxFromSQL 生成语法串;
[*]Create 把语法串变成可用的 DataWindow(或 DataStore);
[*]SetTransObject + Retrieve 取数。
实际项目里它最常出现在三类地方:一是自定义查询窗口——用户勾字段、填条件,程序现场拼 select、现场生成、现场展示;二是按库生成管理界面——表结构在建表后就定了,用一次 SyntaxFromSQL 把每张表生成一个可维护的 DataStore,省掉一批手工画板作业;三是给后台批处理快速搭容器——DataStore 不占界面资源,配合第六节的三类失败形态检查,生成失败能第一时间拿到原因。共同点是:select 语句在运行时才确定,画板帮不上忙。
与《Modify 与 Describe》那篇的分工:Modify/Describe 改的是已存在的 DataWindow 的属性;SyntaxFromSQL 解决的是从零生成。本机全程实测的结论先放在这里:这个函数本身很好用,但表现串的键值规则比想象中严苛(引号、未知键、很多想当然的键全都会报错),失败形态有三类,判成败只看返回串长度。
二、最小可运行示例
先看完整代码。示例连上库、重建一张演示表 t_s76,然后一行 select 生成一个 grid 风格的 DataStore 并取数(连接参数请换成你自己的):
// 输入值在前面集中定义
string ls_sql // 取数 SQL:决定取什么数据
string ls_style // 表现串:决定长什么样
string ls_syn // 收 SyntaxFromSQL 生成的语法串
string ls_err // 收错误信息(函数会写入)
long ll_rows // 检索到的行数
datastore lds // 后台数据容器
// 标准连接(SNC 接口连 SQL Server)
SQLCA.DBMS = 'SNC SQL Native Client(OLE DB)'
SQLCA.LogId = '你的账号'
SQLCA.LogPass = '你的口令'
SQLCA.ServerName = '你的服务器,端口'
SQLCA.Database = '你的库名'
SQLCA.AutoCommit = true
SQLCA.DBParm = "Provider='SQLNCLI10',DataSource='你的服务器,端口'"
CONNECT USING SQLCA;
if SQLCA.SQLCode <> 0 then
MessageBox('连接失败', SQLCA.SQLErrText)
return
end if
// 建一张演示表(已存在则先删)
EXECUTE IMMEDIATE "if object_id('dbo.t_s76') is not null drop table dbo.t_s76" USING SQLCA;
EXECUTE IMMEDIATE "create table dbo.t_s76(id int primary key, name nvarchar(10), qty int)" USING SQLCA;
EXECUTE IMMEDIATE "insert into dbo.t_s76 values(1,'A',10)" USING SQLCA;
EXECUTE IMMEDIATE "insert into dbo.t_s76 values(2,'B',20)" USING SQLCA;
// 核心:生成语法串
ls_sql = 'select id, name, qty from dbo.t_s76'
ls_style = 'style(type=grid)'
ls_syn = SQLCA.SyntaxFromSQL(ls_sql, ls_style, ls_err)
if Len(ls_syn) = 0 then
MessageBox('生成失败', ls_err) // 失败时 err 里有原因
return
end if
// 造出来、接上事务、取数
lds = create datastore
lds.Create(ls_syn, ls_err)
lds.SetTransObject(SQLCA)
ll_rows = lds.Retrieve()
MessageBox('结果', '行数=' + String(ll_rows))
本机实测这段逻辑(连接真实库)输出 行数=2。表现串 style(type=grid) 是最短合法写法;把 grid 换成 tabular 或 freeform 同样可用。
三、生成的语法串长什么样
返回的语法串是一段多行文本,本机 3 列 grid 约 5.4KB、4 列约 7.1KB、1 列约 2KB——平均每列多出 1.1KB 上下,因为每个列要生成「table 里的列定义 + 明细带上的列控件 + 列头文本对象」三段。骨架如下(按真实输出节选、注释为笔者所加):
release 12.5; ← 版本头(PB10 生成的则是 release 10;)
datawindow(units=0 timer_interval=0 color=1073741824 processing=1 ...) ← 头部属性,processing=1 即 grid
table(
column=(type=long update=yes key=yes updatewhereclause = yes name=iddbname="t_s76.id")
column=(type=char(10) update=yes updatewhereclause = yes name=namedbname="t_s76.name")← nvarchar(10) 映射成 char(10)
column=(type=long update=yes updatewhereclause = yes name=qtydbname="t_s76.qty")
retrieve="select id, name, qty from dbo.t_s76" ← 你的 SQL 原样进 retrieve
update="t_s76" updatewhere=1) ← 可更新表 + UpdateWhere 默认 1
header(height=68)
detail(height=80)
column(band=detail id=1 x="14" y="8" height="64" width="274" ...)← 明细带列控件(后接一长串字体/背景属性)
text(band=header ... name=id_t ...) ← 列头文本对象
htmltable(border="0" ...) ← 结尾的 HTML 导出配置
四个要点:
[*]版本头会带版本号:本机 PB10 生成 release 10;、PB12.5 生成 release 12.5;。也就是说 A 版本生成的语法串拿到 B 版本去解析,可能撞上 release 号或长属性行不兼容的问题(把语法串落盘成 .srd 跨版本导入是踩过的坑,别这么干)。
[*]更新属性是自动的:主键列会自动带 update=yes key=yes,可更新表自动是 update="t_s76",并且 updatewhere=1(Key+修改列,默认态与《更新机制全解》篇实测的 UpdateWhere=1 语义一致)。所以 SyntaxFromSQL 生成的对象天生可更新。注意属性字样是 update=,没有 updatetable=;但 Describe('datawindow.table.updatetable') 能取到表名——属性名和语法串字样不是一回事。
[*]列类型做了映射:数据库类型不原样照搬(见第五节对照表)。
[*]grid 与 tabular/freeform 的差别全在头部:processing=1 是 grid,tabular 与 freeform 都是 processing=0,靠 band/控件的坐标布局区分。
四、表现串语法规则(实测)
表现串 = 以空格分隔的若干「节」,每节形如 节名(键=值 键=值 ...)。规则逐条实测如下:
写法结果
style(type=grid) / tabular / freeform都可用
style(type=nosuchstyle)报 invalid,返回空串
STYLE(TYPE=GRID) 全大写可用(大小写不敏感)
datawindow(color=6710886)可用,背景色写进生成串
datawindow(units=1)可用
datawindow(color="6710886") 值加引号报 invalid
datawindow(grid.lines=1)报 invalid(别把语法串里的属性想当然搬进来)
datawindow(nosuchkey=999)报 invalid,未知键不静默忽略
column(border=2) / column(color=255)可用
column(width=400) / column(x=300)报 invalid
text(color=255)可用
text(band=header color=255) / column(band=detail id=1 border=2)报 invalid
多节组合 style(type=grid) column(border=2) text(color=255)可用
三条总结:值不加引号;不认识的键直接报错(不静默吞);column()/text() 里只认少量键(本机实测 border、color 可用;width、x、band、id 都拒收)。想精细排版,别指望表现串,生成后用 Modify(那套在《Modify 与 Describe》篇)。
五、数据库类型到语法串的映射(实测)
SQL Server 列类型语法串里的类型
intlong
nvarchar(10)char(10)(nvarchar 字样不出现)
varchar(50)char(50)
moneydecimal
numeric(10,2)decimal
datetimedatetime
smallint未以原样出现(映射为 PB 数值类型)
两个附带的实测点:identity 自增列在语法串里保留 identity 字样(自增属性跟着走);主键列自动生成 update/key 属性(见第三节)。
六、err 参数与三类失败形态
err 是引用参数,函数必定写入它:成功时覆盖为空串——哪怕你传进去时预置了内容也会被清掉;失败时写入原因。所以判断成败只看返回串是否为空,不要靠 err 的旧值。
// 三类失败,一次看全(每类单独调一次)
string ls_syn, ls_err
long ll_len
// 类 1:SQL 错误(表不存在 / 列不存在 / SQL 语法错)
ls_syn = SQLCA.SyntaxFromSQL('select id from dbo.t_nosuch76', 'style(type=grid)', ls_err)
ll_len = Len(ls_syn) // = 0,ls_err = 驱动原文(SQLSTATE + 描述 + 原 SQL)
// 类 2:表现串非法(值加了引号、键不存在等)
ls_syn = SQLCA.SyntaxFromSQL('select id from dbo.t_s76', 'style(type=grid) column(width=400)', ls_err)
ll_len = Len(ls_syn) // = 0,ls_err = Style string '...' is not valid.
// 类 3:事务未连接(没 CONNECT 就调)
transaction lt
lt = create transaction
lt.DBMS = 'SNC SQL Native Client(OLE DB)'
// ……赋了参数但没 CONNECT
ls_syn = lt.SyntaxFromSQL('select id from dbo.t_s76', 'style(type=grid)', ls_err)
ll_len = Len(ls_syn) // = 0,ls_err = Transaction not connected,SQLCode = -1
类 1 的错误文本是数据库驱动的原话(本机 SQL Server 环境下含 SQLSTATE 42S02/42000/42S22 与出错的 SQL 语句),排错最方便;类 2 的错误文本会把整个表现串回显在 Style string '...' is not valid. 里,一眼能看出是哪个串写坏了。上面的代码块是分段讲解形态,本机对三类各自单独跑过、读数一致。
再补一个容易混淆的分工:SyntaxFromSQL 的 err 参数收的是生成阶段的错误;而 SQLCA.SQLErrText 是你后续用这个连接 Retrieve 失败时的取数错误通道。排错时先看 err 拿到生成失败的原因,别急着翻 SQLErrText。
七、SQL 写法对生成结果的影响
[*]order by 会保留在 retrieve= 里,不生成 sort= 属性。也就是说生成的对象取数即有序,但那是 SQL 排的序;想在运行期改排序,自己 SetSort + Sort(参考《排序与过滤全解》)。
[*]计算列没问题:select id, qty * 2 as dbl from ... 正常生成,dbl 按表达式列处理。
[*]group by + 聚合列也能生成,但注意:生成的只是平铺的结果集,不会生成 group() 分组带、更不会有小计。要分组统计效果,得拿平铺结果改造成分组 DataWindow(参考《分组与汇总统计全解》),或者直接换思路。
[*]where 子句原样进 retrieve=:本机对比 where qty > 0 与 where qty > 99999 两次生成的语法串,长度差恰好等于字面量的长度差,说明 where 文本被原封不动搬进去。
八、端到端验证
第二节示例在本机(SQL Server 2008 R2,SNC 接口)的完整读数:Create 返回 1、SetTransObject 返回 1、Retrieve 返回 3(演示表内 3 行:A/10、B/20、C/30)、Describe('datawindow.processing') 返回 1(grid)、Describe('datawindow.table.updatetable') 返回 t_s76。链路完整可用。
九、PB12.5 差异
[*]生成的语法串版本头不同:PB10 生成 release 10;,PB12.5 生成 release 12.5;(本机双轨读数)。这是两版唯一的实测差异。
[*]表现串规则(引号、未知键、column/text 可用键)、类型映射、三类报错形态、order by/计算列/group by 行为,两版一致。
[*]验证口径:PB10 基线 = 导入与全量编译 0 错误、对象齐全(编译级);本机 PB10 运行环境未装 SNC 接口运行库,故 PB10 侧未做连库运行验证;PB12.5 = 全部探针连库实跑(运行级)。读者在 PB10 环境运行前请按第六节的三类失败形态自行核对。
十、坑清单
[*]判成败只看返回串长度,别信 err 的旧值——成功时 err 会被覆盖成空串。
[*]表现串的值不能加引号,加了直接 invalid。
[*]表现串里没有静默容错:一个不认识的键整个串作废,错误信息会把整个表现串回显。
[*]column()/text() 只认少量键;想设坐标、宽度,生成后用 Modify。
[*]语法串带版本头,跨版本落盘 .srd 再导入是坑。
[*]数据库类型映射不是原样照搬:nvarchar/varchar 都变 char,money 变 decimal——Describe('列名.coltype') 拿到的是映射后的类型(本机实测 nvarchar(10) 列返回 char(10)),按映射后的算才不会错。
[*]group by 只出平铺结果,不出分组带与小计。
[*]order by 只进 retrieve=,不出 sort= 属性;运行期排序用 SetSort。
[*]必须 CONNECT 之后再调,否则报 Transaction not connected。
[*]每列约 1.1KB 的语法串开销是正常体量,大宽表生成的串很长,别按行数估体积。
十一、与相近方案对比,以及最顺的组合拳
先把三条相近路线摆在一起:
[*]手写语法串:全文自己拼 table(column=...) 那套,可控但极易写错列类型与更新属性;SyntaxFromSQL 把列类型、更新属性、band 布局全部自动生成,代价是必须连着库、且外观定制要走 Modify。
[*]Describe('datawindow.syntax'):只能取已有 DataWindow 的语法串,解决不了运行时从零生成的问题。
[*]SyntaxFromSQL + Modify 组合:生成后立刻 Modify 改坐标、改宽度、加计算域,是实践中最顺的组合拳。
组合拳的标准动作(接在第二节 Create 之后):
// lds 为已用语法串 Create 出来的 DataStore
string ls_coltype // 收映射后的列类型
string ls_mod // 收 Modify 的返回值
ls_coltype = lds.Describe('name.coltype') // 注意:返回的是映射后的类型
MessageBox('列类型', ls_coltype)
ls_mod = lds.Modify('name.width=500') // 返回空串即成功
if ls_mod <> '' then
MessageBox('修改失败', ls_mod)
return
end if
MessageBox('新列宽', lds.Describe('name.width'))
本机实测三个读数:Describe('name.coltype') 返回 char(10)(数据库里的 nvarchar(10) 已被映射,坑 6 的直接证据);Modify('name.width=500') 返回空串、列宽从 251 变 500;改完 SetTransObject + Retrieve 照常取到 3 行。也就是说:SyntaxFromSQL 负责把骨架和列类型一次生成对,外观与行为微调交给 Modify,两者各干各的擅场。
一句话收束:SyntaxFromSQL 干的是脏活——替你把列定义、类型映射、更新属性一次写对;它不擅长的外观微调,交给 Modify 去做。记住「返回串为空即失败、err 里必有原因」,这个函数就能放心用。 本帖最后由 pbai 于 2026-10-24 06:27 编辑
PB10 兼容版示例包(附件)
把文中三个示例整理成可直接运行的 PB10 包:
环境要求
[*]PB10 或更高版本;Windows 7+。
[*]数据库:SQL Server 2008 / 2008 R2(本机 2008 R2 实测);客户机需装 SQL Server Native Client 10.0(连接走 SNC 接口、Provider='SQLNCLI10')。
[*]登录账号需要建表权限(示例会重建演示表 dbo.t_s76)。
[*]不依赖任何第三方组件或附加运行库。
包内内容
s76demo.pbl + s76demo.pbt + 源码三件(a_s76demo.sra / w_s76demo.srw / nvo_sfs.sru)+ 使用说明.txt。
操作步骤
[*]解压后用 PB 打开 s76demo.pbt(或把 s76demo.pbl 加进你自己的工作区,应用对象设为 a_s76demo)。
[*]运行应用,在窗口上填你的服务器(如 服务器名,端口)、库名、账号、口令。
[*]点「运行三个示例」按钮:示例一动态生成 grid DataStore 并取数;示例二现场演示三类失败形态(报错弹窗属预期);示例三演示生成后 Describe 列类型映射 + Modify 调列宽。
建表 SQL(程序会自动重建,也可手工先建)
if object_id('dbo.t_s76') is not null drop table dbo.t_s76;
create table dbo.t_s76(id int primary key, name nvarchar(10), qty int);
insert into dbo.t_s76 values(1,'A',10);
insert into dbo.t_s76 values(2,'B',20);
实机验证情况
[*]PB10:ORCA 导入 + 全量编译 0 错误、对象齐全(编译级)。
[*]PB12.5:同源代码编译 0 错误,11 个探针、26 组读数连接真实 SQL Server 2008 R2 实跑通过(运行级),文章读数即来自该轮实测。
[*]PB10 侧未做连库运行验证(本机 PB10 运行环境无 SNC 接口库),读者运行前请先核对连接环境;详见包内使用说明第七节。
注意:附件列表若出现两个同名 s76demo_pb10.zip,请下载 17.07 KB 的那个(当前正文链接指向的就是它);17.08 KB 的是早期版本,仅示例地址写法不同,忽略即可。
有问题跟帖,我看到就回。
页:
[1]