马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?站点注册
×
PowerBuilder DataWindow 运行时换 SQL 全解:SetSQLSelect / GetSQLSelect 的真实行为、三条失败铁律与 UpdateTable 去向(PB10 基准 · PB12.5 实测)
阅读说明
1. 适用版本:基准 PB10,兼容实测 PB 12.5(两版读数逐字一致,差异见第十一节)
2. 支持数据库:MSSQL(SQL Server 2000 及以上,本文用 SQL Server 2008 R2 实测)。换 SQL 这组函数本身不挑 DBMS,文中结论对 Oracle、MySQL 等同样适用,只是验证环境在 MSSQL
3. 操作系统与环境要求:Windows 7 及以上;无需 PBIDEA;需能连上你的数据库,并具备建表权限(示例要建两张演示表)
4. 难度系数:★★★☆☆(三星。函数只有两个,但「什么时候换会失败、失败后 dw 还能不能用」这几处必须弄清,否则线上改 SQL 一改就 -1 找不到北)
5. 其它阅读说明:建议先读总目录里的 DataWindow 入门篇与动态 SQL 篇;示例表建表 SQL 在第二节,照抄即可跑通
一、这组函数是干什么的,跟近邻们怎么分工
DataWindow 一旦建好,它取数的 SELECT 语句就「焊」在了对象里。想在线上不重建对象、不重新 Create,直接把取数条件换掉——这就是 SetSQLSelect 的活:把 DataWindow 当前数据源的 SELECT 语句整个替换成新串。GetSQLSelect 则把当前生效的 SELECT 读回来。
它跟几条近亲路线的分工要分清:
| 路线 | 干什么 | 适合场景 | | SetSQLSelect | 只换 SELECT 语句本体 | 改 where、换表、换排序,列结构不变 | | Modify("datawindow.table.select=…") | 属性层面改 select | 与 SetSQLSelect 等效,另可顺手改其它 table 属性 | | SyntaxFromSQL + Create | 造一个新 DataWindow 对象 | 列结构都要变的场合(第九节对比) | | 动态 SQL 四形态 | 绕开 DataWindow 直接取数 | 不需要 dw 缓冲区/显示层的纯取数 |
一句话:列结构不变就换 SQL(SetSQLSelect),列结构要变才重建(SyntaxFromSQL + Create)。这界线正是 SetSQLSelect 所有失败形态的根源,往下看。
二、示例表与建库路线
本文所有探针用两张同构表(列名完全相同、数据不同),建表 SQL 如下,照抄执行:
- create table dbo.t_s92a(id int primary key, name nvarchar(10), qty int);
- insert into dbo.t_s92a values(1,'A',10);
- insert into dbo.t_s92a values(2,'B',20);
- insert into dbo.t_s92a values(3,'C',30);
- insert into dbo.t_s92a values(4,'D',40);
- insert into dbo.t_s92a values(5,'E',50);
- create table dbo.t_s92b(id int primary key, name nvarchar(10), qty int);
- insert into dbo.t_s92b values(1,'X',100);
- insert into dbo.t_s92b values(2,'Y',200);
复制代码
换 SQL 的前提是 dw 里已经有一个有效数据源。探针统一用 SyntaxFromSQL 造 dw(它生成的对象天生带更新属性,主键自动标好,这为第八节讨论 UpdateTable 埋了伏笔):
- // 示例输入:建表用事务对象(SNC 连 SQL Server)
- SQLCA.DBMS = 'SNC SQL Native Client(OLE DB)'
- SQLCA.LogId = 'sa' // 登录名
- SQLCA.LogPass = '你的口令' // 登录口令
- SQLCA.ServerName = '你的服务器,端口' // 服务器地址,端口
- SQLCA.Database = 'master' // 数据库
- SQLCA.DBParm = "Provider='SQLNCLI10',DataSource='你的服务器,端口'"
- CONNECT USING SQLCA;
- string ls_syn, ls_err
- datastore lds
- ls_syn = SQLCA.SyntaxFromSQL("select id,name,qty from dbo.t_s92a", 'style(type=grid)', ls_err)
- lds = create datastore
- lds.Create(ls_syn)
- lds.SetTransObject(SQLCA)
- MessageBox('基线行数', string(lds.Retrieve())) // 弹 5
复制代码
注意:SyntaxFromSQL 返回的是五千多字符的完整语法串,但它塞进 dw 的 select 被规范化成了一句小写、无分号的短句。
三、基线实测:GetSQLSelect 回读什么
对上面建好的 dw 调 GetSQLSelect(),实测回读:
- select id,name,qty from dbo.t_s92a
复制代码
三个细节:①整串小写(SyntaxFromSQL 内部做了规范化);②不带分号;③不带任何包装。长度 34 字符,跟提交给 SyntaxFromSQL 的原句几乎一致,但别指望它保留你原来的排版。
而 Describe('DataWindow.Table.Select') 在 dw 没有 select(手写语法串造的 dw,见第十节)时返回 !,GetSQLSelect 返回空串——两者空态不一样,判空要分开写。
四、改 WHERE:最常见场景与「不清数据」的时间差
把 where 条件换掉是最日常的用法。实测链路:Retrieve 5 行 → SetSQLSelect 加 where → 立刻查行数 → 再 Retrieve:
- // 前置:窗口 w_demo 上有控件 dw_1(DataObject 已设好)并已 SetTransObject
- long ll_rc, ll_n
- ll_rc = dw_1.SetSQLSelect("select id,name,qty from dbo.t_s92a where qty > 10")
- if ll_rc = 1 then
- ll_n = dw_1.Retrieve()
- MessageBox('换 WHERE 后行数', string(ll_n)) // 弹 4(B/C/D/E)
- else
- MessageBox('换 SQL 失败', 'rc=-1,dw 仍按旧 SQL 工作')
- end if
复制代码
实测三个关键读数:
- rc=1 成功,GetSQLSelect 回读的新串原样带着 where 子句;
- SetSQLSelect 之后、Retrieve 之前,缓冲区里还是旧数据(行数仍是 5)——换 SQL 本身不清数据,Retrieve() 才刷新。这跟 SyntaxFromSQL + Create 全新对象不一样,别指望换完 SQL 行数立刻变;
- Retrieve 后 4 行,条件真实生效。
五、失败形态一:列不匹配
SetSQLSelect 对列集合查得很严——新 SELECT 的列清单必须与 dw 现有列完全对应:
| 尝试 | 新 SELECT | 实测 rc | 后续状态 | | 删一列 | select id,name from …(丢了 qty) | -1 | 列数仍 3、旧数据仍在、旧 SQL 照常 Retrieve 出 5 行 | | 加一列 | select id,name,qty,id*2 as dbl from … | -1 | 同上 | | 只改 where/from/order | 列清单不动 | 1 | 正常换 |
两条硬结论:①失败的 SetSQLSelect 不留任何残迹——dw 的列定义、缓冲区、旧 select 全部原样,业务代码可以放心按旧数据继续跑;②想多取一列、少取一列,SetSQLSelect 都不收,那属于「列结构变化」,请走 SyntaxFromSQL + Create 路线。
六、失败形态二:检索参数——一拒一坑
新 SELECT 里带检索参数是实测最有意思的地方,两种写法下场完全不同:
- :参数名 写法(PB 标准检索参数):SetSQLSelect("select … where qty > :qtyarg") 直接 rc=-1。SetSQLSelect 不支持带检索参数的 SELECT,想要参数化条件,改用 SetFilter、SetSort,或干脆在应用层拼串;
- ? 占位写法:SetSQLSelect("select … where qty > ?") 居然 rc=1——验证时 ? 被 OLE DB 当参数标记放行了!但随后 Retrieve(25) 返回 -1,取数彻底失败。
后者比前者危险得多:rc=1 会骗过「只判 SetSQLSelect 返回值」的代码,炸点推迟到了 Retrieve。所以判成败不能只看 SetSQLSelect,换 SQL 后的第一次 Retrieve 返回值也要接住。
七、失败形态三:没挂事务对象(含未连库、无源 dw)
这是最隐蔽的一坑:SetSQLSelect 的验证走 dw 当前挂的事务对象。没挂(或根本没连库)就换 SQL,无论你的 SELECT 写得多对,一律 rc=-1。实测矩阵:
| 场景 | 实测 rc | | 连库 + SetTransObject + 换 where / 换表 | 1 | | 连库但没调过 SetTransObject(SyntaxFromSQL 刚 Create 完) | -1 | | 完全没连库,手写语法 dw,SELECT 本身合法 | -1 | | 完全没连库,SELECT 是垃圾串 | -1 |
顺带钉死一个边界:手写语法串造的 dw(table 段里没有 select=)不能靠 SetSQLSelect「装上」数据源——即使连了库、挂了事务、SELECT 合法,实测仍 rc=-1,Retrieve 也返回 -1。SetSQLSelect 只能替换已有数据源,不是给无源 dw 配数据源的工具;那种需求请用 SetTransObject 挂库后走 Retrieve(前提是语法串里写好了 dbname 绑定)或者直接重建对象。
八、换表与 UpdateTable:自动跟随,实测放心
跨表切换是 SetSQLSelect 的进阶用法:两张表列结构相同,直接换 from。实测 SetSQLSelect("select id,name,qty from dbo.t_s92b") 返回 1,Retrieve 出来第一行 name='X'、qty=100——数据真的换到了新表。
关键问题是:这个 dw 原本可更新(SyntaxFromSQL 造的,UpdateTable=t_s92a),换表后 Update 会打到哪张表?实测链路:换表 → 换数据 → SetItem 改值 → Update → 回查两张表:
- // 前置:lds 已按第二节建好并 Retrieve 过,已 SetTransObject
- long ll_rc, ll_nr
- string ls_back
- ll_rc = lds.SetSQLSelect("select id,name,qty from dbo.t_s92b")
- ll_nr = lds.Retrieve() // 2 行,来自 t_s92b
- lds.SetItem(1, 'name', 'ZZ')
- ll_nr = lds.Update() // 返回 1,SQLNRows=1
- select name into :ls_back from dbo.t_s92a where id = 1 using SQLCA;
- MessageBox('t_s92a 的 id=1', ls_back) // 'A'——旧表没被碰
- select name into :ls_back from dbo.t_s92b where id = 1 using SQLCA;
- MessageBox('t_s92b 的 id=1', ls_back) // 'ZZ'——新表真被更新
复制代码
结论比预想的干净:UpdateTable 自动跟随新 SELECT(Describe('DataWindow.Table.UpdateTable') 从 t_s92a 变成了 t_s92b),Update 一打一个准,新表落笔、旧表无恙。另外 Modify('DataWindow.Table.UpdateTable="t_s92b"') 手工改更新表也实测可用(返回空串即成功),多表混用想人工锁死更新目标时可以加这道保险。
九、Modify 路线:等效的另一半
不改 SetSQLSelect,改 table.select 属性也能达到同样效果,实测两条全过:
- // 前置:dw_1 已建好并 SetTransObject,成功时 Modify 返回空串
- string ls_m
- ls_m = dw_1.Modify('datawindow.table.select="select id,name,qty from dbo.t_s92a where qty>=30"')
- MessageBox('改 where 结果', '[' + ls_m + ']') // 空串=成功,Retrieve 出 3 行
- ls_m = dw_1.Modify('datawindow.table.select="select id,name,qty from dbo.t_s92a order by qty desc"')
- MessageBox('改 order 结果', '[' + ls_m + ']') // 空串=成功,第一行 name='E'
复制代码
与 SetSQLSelect 的差别在报错形态:Modify 失败时返回错误描述串(如 incorrect syntax),SetSQLSelect 失败时只回 -1 不给原因。要排查「为什么换失败」,用 Modify 试一遍拿错误串,比 SetSQLSelect 干瞪眼强。注意 Modify 的值要用成对双引号包住 SELECT(外层 PB 串用单引号即可,不用转义)。
十、order by、大小写与原样保留
- order by 可以进 SetSQLSelect:实测 … order by qty desc 后 Retrieve 第一行是 qty=50 的 E 行,排序真实生效,GetSQLSelect 回读也带着 order by。之前的排序文章讲过「GetSort 不存在、Describe 读不回 sort」的坑,而在 select 里写 order by 是能读回来的(它属于 SQL 本体),这是两条路线的实用差别;
- 提交什么存什么:用大写、多空格的 SELECT 调 SetSQLSelect(SELECT ID, NAME, QTY FROM … WHERE QTY >= 30),rc=1,GetSQLSelect 原样回读这串大小写与空格——规范化只发生在 SyntaxFromSQL 造对象的时刻,SetSQLSelect 不做任何清洗。对读回的串做字符串匹配时留意这一点。
十一、选型决策与两个高频问法
问:改 where、换表、换排序,到底走 SetSQLSelect 还是 Modify?
两者在「换 SQL」这件事上等效,选型看三点:要看失败原因,用 Modify(返回错误串);要在改 select 的同一顺手改 table 属性(如手工锁 UpdateTable),用 Modify;代码可读性和语义直白,用 SetSQLSelect。实测建议主用 SetSQLSelect,失败时拿 Modify 复试一遍拿错误串,两把刀一起用。
问:过滤条件想参数化,怎么办?
SetSQLSelect 拒收 :参数、? 是雷(见第六节),正确的参数化姿势有三条路:①条件值在应用层拼进 SELECT 再提交(值来源可信时,注意字符串值要按 DBMS 规则给引号);②用 SetFilter/Filter 在缓冲区层面过滤,不动 SQL;③数据量大、必须让数据库过滤时,用 Retrieve(参数) 的检索参数——但那要求 dw 建对象时就带参数(SyntaxFromSQL 的 SQL 里写好 :参数名),而不是事后 SetSQLSelect 补。
问:换 SQL 后列名对不上会怎样?
第五章的失败矩阵已给结论:列集合不一致一律 -1 且 dw 不受影响。想要「同列名不同类型」这类微调也不行——SetSQLSelect 只认列清单一致,类型信息动不了,那是重建对象的事。
十二、PB12.5 差异
全部 20 组探针在 PB10 与 PB12.5 双轨各跑一遍:依赖连接库的 18 组在 PB12.5 连库真跑、PB10 轨因无 SNC 运行库连接失败按预期早退(编译 0 错);不依赖数据库的 2 组(未连库换 SQL 矩阵、GetSQLSelect 空态)两轨读数逐字一致。结论:本主题 PB12.5 与 PB10 行为一致,未发现版本差异。
十三、坑清单(14 条,全部实测)
- SetSQLSelect 前必须 SetTransObject(或挂好事务),否则一律 -1,错误原因不留痕;
- 新 SELECT 的列清单必须与 dw 现有列完全匹配,多列少列都 -1;
- 带 :参数名 检索参数的 SELECT 直接 -1,SetSQLSelect 不支持检索参数;
- ? 占位能骗过 SetSQLSelect(rc=1)但 Retrieve 必炸 -1——成败要看第一次 Retrieve;
- SetSQLSelect 成功不清缓冲区,旧行数还在,Retrieve 才刷新;
- SetSQLSelect 失败不留残迹,dw 按旧 SQL 完全可用;
- 手写语法串(无 select=)造的 dw 不能用 SetSQLSelect 装数据源,连库也 -1;
- 换表后 UpdateTable 自动跟随新表,Update 打到新表(t_s92a/t_s92b 实证);
- 想人工锁死更新表,用 Modify 改 DataWindow.Table.UpdateTable,返回空串即成功;
- GetSQLSelect 对无 select 的 dw 返回空串,Describe('DataWindow.Table.Select') 返回 !,判空写法不同;
- SyntaxFromSQL 建的 dw,GetSQLSelect 回读小写规范串;SetSQLSelect 提交什么回读什么(大小写空格原样保留);
- order by 进 SetSQLSelect 完全合法且能读回,比 sort 属性路线多一个「可读回」的便宜;
- Modify 改 select 失败返回错误描述串,SetSQLSelect 只回 -1——排错用 Modify 试;
- 表达式列(id*2 as dbl)也是列结构变化,SetSQLSelect 拒收,别当成「加个计算列」的捷径。
十四、完整示例与实机验证
下面这段把「取数 → 换 where → 换表」三段连起来,可直接放进 NVO 方法或按钮事件(连库参数照第二节):
- // 示例输入:三段演示用的连接与对象(上文第二节已连库并建表)
- string ls_syn, ls_err, ls_out
- long ll_n, ll_rc
- datastore lds
- ls_syn = SQLCA.SyntaxFromSQL("select id,name,qty from dbo.t_s92a", 'style(type=grid)', ls_err)
- lds = create datastore
- lds.Create(ls_syn)
- lds.SetTransObject(SQLCA)
- ll_n = lds.Retrieve() // 第一段:基线 5 行
- ls_out = 'A_rows=' + string(ll_n)
- ll_rc = lds.SetSQLSelect("select id,name,qty from dbo.t_s92a where qty > 10")
- ll_n = lds.Retrieve() // 第二段:换 where 后 4 行
- ls_out = ls_out + ' B_rc=' + string(ll_rc) + '_rows=' + string(ll_n)
- ll_rc = lds.SetSQLSelect("select id,name,qty from dbo.t_s92b")
- ll_n = lds.Retrieve() // 第三段:换表后 2 行,首行 X
- ls_out = ls_out + ' C_rc=' + string(ll_rc) + '_rows=' + string(ll_n) + '_r1=' + lds.GetItemString(1, 'name')
- destroy lds
- MessageBox('三段实测', ls_out) // A_rows=5 B_rc=1_rows=4 C_rc=1_rows=2_r1=X
复制代码
实机验证情况:本文全部示例与探针在 PB10(--pb-version 100 编译 0 错 + 运行验证)与 PB12.5(编译 0 错 + 连库运行 PASS)双轨完成——20 组探针 PB12.5 全 PASS(连库真跑,Update 落表逐行回查),PB10 轨编译 0 错、不依赖数据库的用例运行 PASS、连库用例因本机 PB10 无 SNC 运行接口库按预期早退;`源码静态检查 0 错 0 提醒、编码检查 bad=0。两版输出串逐条比对一致,无「编译级冒充运行级」项。配套 PB10 兼容版 PBL 与自验证件见本帖 2 楼附件。 |