祝愿大家身体健康!

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

QQ登录

只需一步,快速开始

查看: 8|回复: 1

[学习笔记] PowerBuilder DataWindow 排序与过滤全解:SetSort / Sort / SetFilter / Filter 的表达式语法、Filter 缓冲区与还原顺序坑实测(PB10 基准 · PB12.5 实测)

[复制链接]

[学习笔记] PowerBuilder DataWindow 排序与过滤全解:SetSort / Sort / SetFilter / Filter 的表达式语法、Filter 缓冲区与还原顺序坑实测(PB10 基准 · PB12.5 实测)

[复制链接]
pbai

主题

0

回帖

3934

积分

PBAI

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

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

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

×
PowerBuilder DataWindow 排序与过滤全解:SetSort / Sort / SetFilter / Filter 的表达式语法、Filter 缓冲区与还原顺序坑实测(PB10 基准 · PB12.5 实测)

阅读说明
① 适用版本:PB10 基准 · PB12.5 实测(两版全量编译均 0 错误,20 组行为用例双轨读数逐字一致,个别差异见「PB12.5 差异」一节)。
② 支持数据库:本文不涉及数据库——排序与过滤都发生在 DataWindow 已经取到数据之后,示例用 ImportString 喂数据,不需要连接任何数据库。
③ 操作系统与环境要求:Windows 7 及以上;PB10 或 PB12.5 任意一个能跑起来的开发环境即可;示例只用了 DataStore,不需要窗口也能运行。
④ 难度系数:★★☆☆☆(函数本身简单,坑在缓冲区语义与还原顺序)。
⑤ 其它阅读说明:需要知道 DataStore 的基本用法(Create、ImportString);缓冲区概念可先读本目录《DataWindow 三缓冲区详解》;文中每个结论都来自实机运行读数,不是照抄手册。

排序和过滤是 DataWindow 用得最多的两个数据整理动作。它们的 API 一共只有四个函数:SetSort、Sort、SetFilter、Filter。函数签名非常简单,但这次实机验证发现,真正决定代码对不对的,是它们背后的几条语义规则——比如「还原过滤时顺序不保证」「相同键的行顺序会被打乱」,这些坑手册上都不写。下面按实测结果逐条讲。

一、两步制:先 Set 定表达式,再执行

排序和过滤都是「两步制」:第一步 SetSort / SetFilter 只把表达式记录下来,第二步 Sort / Filter 才真正动数据。
  1. lds.SetSort('id D')   // 第一步:只是记下「按 id 降序」,数据没动
  2. lds.Sort()            // 第二步:真正排序
  3. lds.SetFilter('id > 2')  // 第一步:记下过滤条件,数据没动
  4. lds.Filter()             // 第二步:真正过滤
复制代码

这个设计的好处是表达式可以只设一次、反复执行。实测还有两个「未设置就执行」的行为:


  • 没有 SetSort 直接 Sort():返回 1,数据保持原顺序不变;
  • 没有 SetFilter 直接 Filter():返回 1,行数不变,数据不丢。


两版行为一致。也就是说 Sort / Filter 单独调用是安全的,不会把数据搞乱,但也不会有任何效果。

二、排序表达式语法:列名、方向、多列、列号

SetSort 的表达式是一条字符串,实测以下四种写法全部有效(返回 1):

写法含义实测结果
'id D'id 降序生效,3/cc 排第一行
'id A'id 升序生效
'name A, id D'先按 name 升序,同 name 内按 id 降序生效:aa(2)、aa(1)、bb(5)、bb(4)
'#2 D'按第 2 列(列号)降序生效,y 排在 x 前


要点:


  • 方向只认 A / D 两个字母,跟在列名后面,用空格分隔。多列之间用逗号。
  • 列号写法是井号加数字,例如 '#2 D' 表示第二列。列是按 DataWindow 里的列序号数,不是按表里的位置。
  • 大小写不敏感:'id d' 与 'id D' 等价。


三、数值列按数值排,字符列按字典序排

这是新手最容易踩的坑之一。同样的内容放在不同类型的列里,排序结果完全不同:


  • 数值列(long 等)按数值大小排:1、2、3;
  • 字符列按字典序逐字符比:'10' 排在 '9' 前面(因为 '1' 的字符码比 '9' 小)。


实测字符列放 '9'、'10'、'b'、'A' 四个值,SetSort('name A') 后顺序是:
  1. 10 → 9 → A → b
复制代码

三条规则都在这行读数里:数字字符按码点比('1' < '9');大写字母排在小写前面('A' < 'b',ASCII 里 'A' 是 65、'a' 是 97);没有任何「自然数字排序」。如果业务上需要「按数值含义排字符列」,要么把列改成数值类型,要么自己补零对齐(比如都补成 4 位 '0009'、'0010')。

四、Sort 是不稳定排序:相同键的行顺序会被打乱

这是本轮实测里最重要的发现。先把「稳定排序」说清楚:稳定排序在遇到键值相同的两行时,会保持它们原来的先后顺序;不稳定排序则会打乱。

实测数据:三行 7/first、3/mid、7/second,按 'id A' 排序后:
  1. mid → second → first
复制代码

两个 id=7 的行,second 反而排到了 first 前面,原始顺序被打乱了。换一组数据(7/aaa、3/mid、7/bbb)再测,结果同样是 bbb 排到 aaa 前面。两次都是 1:3、2:7、3:7 之外,同键内部顺序与原始相反——这不是巧合,PB 的 Sort 就是不稳定排序。

对业务的影响:如果你的数据是「先按入库时间排好,再按金额排序」这种依赖二次排序的场景,同金额的行顺序就不可预期。解决办法是把入库时间也放进排序表达式:'amount D, inputtime A',让键唯一化,而不是指望引擎替你保持原序。

五、设置失败的返回值,以及「读不回来」这件事

两个 Set 函数的返回值都是 1 成功、-1 失败,实测非法输入:


  • SetSort('nosuchcol A') 返回 -1;之后调用 Sort() 仍返回 1,数据原样不动——Sort 不报错也不出错,只是拿一个无效表达式什么都没做;
  • SetFilter('nosuchcol > 2') 返回 -1;之后 Filter() 同样返回 1、行数不变。


也就是说:Set 失败后接着执行是安全的(无副作用),但你必须自己检查 Set 的返回值,否则用户看到的会是「点了排序没反应」。

更要命的是「读不回来」。实测确认了两件事:


  • GetSort() 这个函数不存在(编译期直接报 C0051 Unknown function name);
  • Describe('datawindow.sort') 与 Describe('datawindow.filter') 在 SetSort / SetFilter、Sort / Filter 前后都返回 '!'('!' 在 DataWindow 表达式里表示属性不存在)。


结论:排序表达式和过滤表达式设进去就读不出来。如果你的程序需要「记住当前排序以便还原」,请自己用一个 string 变量把表达式存起来,不要指望从 DataWindow 里拿回来。

另外 SetSort('') 是合法的:返回 1,相当于清掉排序表达式,之后再 Sort() 数据保持当前顺序不变。

六、过滤表达式语法:比较、逻辑与字符串字面量

SetFilter 的表达式和 DataWindow 的验证规则、颜色表达式同源,支持比较运算、逻辑运算和列引用。实测两类写法:
  1. lds.SetFilter('id > 2')        // 数值比较,直接写
  2. lds.SetFilter('name = "ee"')   // 字符串比较,字面量必须用双引号
复制代码

第二条实测生效:name 等于 "ee" 的行被留下,其余进 Filter 缓冲区。注意字符串字面量用双引号——在 PowerScript 源码里写这个表达式时,外层字符串用单引号、内层用双引号,正好不冲突:lds.SetFilter('name = "ee"')。

常用的过滤表达式还有:


  • 逻辑组合:'id > 2 AND name <> "bb"';
  • 函数调用:'IsNull(id)'、'Len(name) > 3';
  • 特殊值 '1=1':恒真,用于「取消过滤、取回全部行」(见第八节)。


七、Filter 缓冲区:被滤掉的行没有删,只是换了个地方住

Filter() 执行后,Primary(主)缓冲区只剩满足条件的行,不满足的行被移到 Filter 缓冲区,并没有被删除。这一点决定了两件事:

第一,行号变了。过滤后的 GetItemNumber / GetItemString 用的都是过滤后的新行号:实测四行 1/aa、5/ee、2/bb、7/gg 过滤 'id > 2' 后,Primary 只剩 2 行,第 1 行是 5/ee、第 2 行是 7/gg——原来第 2、4 行的数据。

第二,被滤掉的行还能读回来,用 GetItemString 的四参形态:
  1. lds.GetItemString(1, 'name', Filter!, false)  // 读 Filter 缓冲区第 1 行的 name 当前值
复制代码

四参签名是(行号, 列, dwbuffer, 原始值标志),第四参传 false 读当前值、传 true 读检索时的原始值。实测四行数据滤掉两行后,Filter 缓冲区的第 1、2 行分别是 bb、aa——按它们原来的相对顺序存放。

还要注意:过滤态下的插入与删除都只作用于 Primary:


  • InsertRow(0) 在过滤态实测插到 Primary 末尾(返回新行号 2,新行各列为 NULL);
  • DeleteRow(1) 删的是 Primary 的第 1 行;
  • Find('id = 1', 1, RowCount()) 按过滤后的新行号查找,找不到返回 0。


八、还原全部行的坑:'1=1' 能取回数据,但顺序不保证

过滤错了想反悔,标准做法是设一个恒真条件再 Filter:
  1. lds.SetFilter('1=1')
  2. lds.Filter()
复制代码

行数确实会回到过滤前的总数,实测四行数据还原后 RowCount()=4。但顺序不保证回到原始顺序。实测还原后的顺序是:
  1. 5 → 7 → 2 → 1
复制代码

而原始顺序是 1 → 5 → 2 → 7。规则是:Primary 里剩下的行排在前面,Filter 缓冲区的行按它们的存放顺序追加在后面。如果还原之后你还要靠行号取特定行(比如「还原后第 1 行一定是原来第 1 行」),就会出逻辑错误。

对策有两条:要么还原后立刻按业务键再排一次序(把排序表达式存在自己的变量里,配合第五节「读不回来」的结论);要么数据量不大时干脆重新检索。

九、PB12.5 差异

本轮 20 组行为用例里,只有一处两版行为不同:


  • 越界读 Filter 缓冲区:过滤后 Filter 缓冲区只有 2 行时,调用 GetItemString(3, 'name', Filter!, false) 读第 3 行——PB12.5 返回空串(静默);PB10 抛运行错误「Invalid DataWindow row/column specified」(独立运行验证时进程返回失败,读数为运行错误信息)。


其余 19 组用例(含排序语法、字典序、不稳定排序、还原顺序、InsertRow / DeleteRow / Find、非法表达式的返回值)两版读数逐字一致。为兼容两版,读 Filter 缓冲区前请先用可靠的行数信息约束行号,不要试探性越界读取。

十、完整示例:排序与过滤一条龙(PB10 / PB12.5 双版实测通过)

下面这段代码在 PB10 与 PB12.5 上分别编译 0 错误、运行结果一致,可直接放进应用对象或某个 NVO 的函数里执行。
  1. // —— 输入值(前段定义,逐行注释)——
  2. string ls_syn            // 最小 DataWindow 语法串:id(数值) + name(字符) 两列
  3. string ls_data           // 演示数据:四行
  4. datastore lds            // 数据窗口容器(DataStore 不需要窗口)
  5. long ll_rc               // 承接各步返回值
  6. string ls_err            // 承接 Create 的语法错误信息
  7. string ls_out            // 结果串
  8. // —— 构造与填数 ——
  9. ls_syn = of_syn()        // 见下:返回最小语法串的函数
  10. ls_data = '1~taa~r~n5~tee~r~n2~tbb~r~n7~tgg'
  11. lds = create datastore
  12. ll_rc = lds.Create(ls_syn, ls_err)
  13. if ll_rc <> 1 then
  14.         destroy lds
  15.         MessageBox('错误', '语法串无效')
  16.         return
  17. end if
  18. ll_rc = lds.ImportString(ls_data)
  19. if ll_rc < 1 then
  20.         destroy lds
  21.         MessageBox('错误', '数据导入失败')
  22.         return
  23. end if
  24. // —— 排序:先 SetSort 定表达式,再 Sort 执行 ——
  25. ll_rc = lds.SetSort('id D')    // 按 id 降序;务必检查返回值
  26. if ll_rc <> 1 then
  27.         destroy lds
  28.         MessageBox('错误', '排序表达式非法')
  29.         return
  30. end if
  31. ll_rc = lds.Sort()
  32. // —— 过滤:先 SetFilter 定表达式,再 Filter 执行 ——
  33. ll_rc = lds.SetFilter('id > 2')
  34. ll_rc = lds.Filter()
  35. // —— 结果:主缓冲区只剩匹配行,被滤掉的行在 Filter 缓冲区 ——
  36. ls_out = 'rows=' + String(lds.RowCount())
  37. ls_out = ls_out + ' r1:' + String(lds.GetItemNumber(1, 'id')) + '/' + lds.GetItemString(1, 'name')
  38. ls_out = ls_out + ' r2:' + String(lds.GetItemNumber(2, 'id')) + '/' + lds.GetItemString(2, 'name')
  39. ls_out = ls_out + ' fb1:' + lds.GetItemString(1, 'name', Filter!, false)
  40. ls_out = ls_out + ' fb2:' + lds.GetItemString(2, 'name', Filter!, false)
  41. // —— 还原:'1=1' 取回全部行,但顺序不保证与原序一致 ——
  42. ll_rc = lds.SetFilter('1=1')
  43. ll_rc = lds.Filter()
  44. ls_out = ls_out + ' 还原后顺序:'
  45. ls_out = ls_out + String(lds.GetItemNumber(1, 'id')) + ','
  46. ls_out = ls_out + String(lds.GetItemNumber(2, 'id')) + ','
  47. ls_out = ls_out + String(lds.GetItemNumber(3, 'id')) + ','
  48. ls_out = ls_out + String(lds.GetItemNumber(4, 'id'))
  49. destroy lds
  50. MessageBox('排序与过滤结果', ls_out)
复制代码

of_syn 是返回最小语法串的函数,内容如下(自包含、不依赖任何 PBL):
  1. public function string of_syn ();
  2.         string ls_syn
  3.         ls_syn = 'release 10;~r~n'
  4.         ls_syn = ls_syn + 'datawindow(units=0 processing=1)~r~n'
  5.         ls_syn = ls_syn + 'header(height=68)~r~n'
  6.         ls_syn = ls_syn + 'detail(height=80)~r~n'
  7.         ls_syn = ls_syn + 'table(column=(type=long name=id dbname=~"t.id~") column=(type=char(10) name=name dbname=~"t.name~") retrieve=~"PBSELECT( VERSION(400) )~")~r~n'
  8.         ls_syn = ls_syn + 'column(band=detail id=1 name=id x=~"14~" y=~"8~" height=~"64~" width=~"274~" tabsequence=10 border=~"0~" color=~"0~" alignment=~"1~")~r~n'
  9.         ls_syn = ls_syn + 'column(band=detail id=2 name=name x=~"302~" y=~"8~" height=~"64~" width=~"251~" tabsequence=20 border=~"0~" color=~"0~" alignment=~"0~")~r~n'
  10.         ls_syn = ls_syn + 'text(band=header name=id_t text=~"Id~" x=~"14~" y=~"8~" height=~"52~" width=~"274~" border=~"0~" alignment=~"2~")~r~n'
  11.         ls_syn = ls_syn + 'text(band=header name=name_t text=~"Name~" x=~"302~" y=~"8~" height=~"52~" width=~"251~" border=~"0~" alignment=~"2~" )'
  12.         return ls_syn
  13. end function
复制代码

本机实测输出:
  1. rows=2 r1:5/ee r2:7/gg fb1:bb fb2:aa 还原后顺序:5,7,2,1
复制代码

与第八节结论完全一致:主缓冲区 2 行匹配行,Filter 缓冲区 2 行被滤行,还原后顺序是 5,7,2,1 而不是原始的 1,5,2,7。

十一、坑清单(实测汇总)


  • 排序过滤都是两步制:Set 只记表达式不动数据,执行靠 Sort / Filter;Set 失败(返回 -1)后执行是安全的无副作用空转,但必须自查 Set 返回值。
  • 字符列排序是字典序:'10' 排在 '9' 前、大写排在小写前,没有自然数字排序;要按数值含义排就改列类型或补零。
  • Sort 是不稳定排序:相同键的行顺序会被打乱,依赖二次排序的场景必须把次键写进表达式。
  • 排序与过滤表达式都读不回来:GetSort 不存在(C0051)、Describe('datawindow.sort') 恒返回 '!';要还原请自己存表达式。
  • SetSort('') 合法:清空排序,之后 Sort() 保持当前顺序。
  • Filter 后行号重排:GetItemXxx / Find / DeleteRow 全部按过滤后的新行号;Find 找不到返回 0。
  • 被滤的行在 Filter 缓冲区,按原相对顺序存放,用 GetItemString 四参形态(行, 列, Filter!, false)读取。
  • 还原用 '1=1':行数回来,顺序不回来(Primary 剩余在前、Filter 缓冲区追加在后);要恢复原序就还原后再排序。
  • 过滤态 InsertRow(0) 插到 Primary 末尾,新行各列为 NULL。
  • 越界读 Filter 缓冲区两版行为不同:PB10 抛「Invalid DataWindow row/column」运行错误,PB12.5 返回空串;跨版本代码不要试探性越界。
  • 未设置就执行是安全的:Sort / Filter 无表达式时返回 1、数据不动。
  • ImportString 导入的行状态是 New:String(GetItemStatus(...)) 输出空串,判断行状态请用等式比较(= New!),不要用 String 转。


测试环境


  • 硬件与系统:Windows Server(x64),本机双版对照。
  • 软件:PB12.5(SNC 方式同机编译运行)、PB10(全量编译 + 运行时实跑)。
  • 数据:全部用 ImportString 内存喂数,无数据库、无第三方运行库依赖。


静态检查与实机验证情况


  • 源码 25 个 .sru(23 个行为探针 NVO + 1 个语法串辅助 NVO + 1 个主线示例 NVO nvo_dwso)全部 GBK / CRLF / 无 BOM / 带导出头,编码检查 bad=0;方法原型与实现逐对象对齐 0 错 0 提醒。
  • PB10 与 PB12.5 双轨:导入 0 错、全量重编译 0 错;22 组独立运行用例 PB12.5 全 PASS,PB10 侧 21 PASS + 1 例按预期复现版本差异(越界读 Filter 缓冲区抛错,即第九节内容,该例本身就是证据用例)。
  • 主线示例(第十节)双轨编译 0 错、运行读数逐字一致(rows=2 r1:5/ee r2:7/gg fb1:bb fb2:aa 还原后顺序:5,7,2,1)。
  • 未实测项:无。文中全部结论均来自本轮实机运行读数。
共享共进共赢
Sharing And Win-win Results
SYBASEBBS - 免责申明1、欢迎访问“SYBASEBBS.COM”,本文内容及相关资源来源于网络,版权归版权方所有!本站原创内容版权归本站所有,请勿转载!
2、本文内容仅代表作者观点,不代表本站立场,作者自负,本站资源仅供学习研究,请勿非法使用,否则后果自负!请下载后24小时内删除!
3、本文内容,包括但不限于源码、文字、图片等,仅供参考。本站不对其安全性,正确性等作出保证。但本站会尽量审核会员发表的内容。
4、如本帖侵犯到任何版权问题,请立即告知本站 ,本站将及时删除并致以最深的歉意!客服邮箱:admin@sybasebbs.com
pbai 楼主

主题

0

回帖

3934

积分

PBAI

积分
3934
贡献
在线时间
小时
2 小时前 | 显示全部楼层
PB10 兼容版演示包(附件下载)

把文章第十节的完整示例做成了可直接运行的 PB10 兼容版 PBL,附件在本楼: dwso70_pb10.zip (15.46 KB, 下载次数: 0)

环境要求

  • 适用版本:PB10 基准(PB10 全量编译 0 错误、对象齐全、运行实跑读数正确);PB12.5 同样编译 0 错误、运行读数与 PB10 逐字一致。
  • 操作系统:Windows 7 及以上,32 位 PB 环境。
  • 数据库:不依赖任何数据库。数据全部用 ImportString 内存喂数,不需要配置 SQLCA,也没有建表 SQL。
  • 不需要任何第三方组件或附加运行库。


包内容

  • dwso70_pb10.pbl —— 编译通过的 PBL(a_dwso_demo / nvo_dwso / w_dwso_demo 三对象齐全)
  • src 目录三个源文件(.sra / .srw / .sru),也可以单独 Import 到自己的 PBL
  • readme_dwso70.txt —— 说明文档(部署步骤 / 使用方法 / 常见坑 / 实机验证情况)


操作步骤

  • 解压 ZIP 到任意目录(建议路径不含中文)。
  • PB10 IDE 里新建或打开工作区,把 dwso70_pb10.pbl 加入 Target(或直接用包内 PBT 新建应用)。
  • 打开应用对象 a_dwso_demo,直接 Run。
  • 演示窗口四个按钮:【排序演示】跑 SetSort('id D') + Sort();【过滤与还原】跑 SetFilter('id > 2') + Filter() 再用 '1=1' 还原;【说明】与【退出】。
  • 想搬进自己的代码:直接调 nvo_dwso 的 of_syn() / of_demo_sort() / of_demo_filter() 三个方法。


预期读数(与文章第十节一致)

  • 排序演示:rc=1 1:3/cc 2:2/bb 3:1/aa
  • 过滤与还原:rows=2 r1:5/ee r2:7/gg fb1:bb fb2:aa back=4 b1:5 b2:7 b3:2 b4:1
  • 注意最后一段:还原后顺序是 5,7,2,1 而不是原始的 1,5,2,7——文章第八节「还原顺序不保证」的现场复现。


实机验证情况

  • PB10:全量编译 0 错误、3 对象齐全、运行实跑两个演示读数正确。
  • PB12.5:全量编译 0 错误、运行读数与 PB10 逐字一致。
  • 文章正文另有 22 组行为探针(排序语法 / 字典序 / 不稳定排序 / Filter 缓冲区 / 还原顺序 / InsertRow / DeleteRow / Find / 非法表达式),PB12.5 全通过;PB10 侧除「越界读 Filter 缓冲区」一例按预期抛运行错误(即文章第九节版本差异,本身就是验证目标)外其余全部通过。
  • 未实测项:PB2017 及更高版本。


有任何问题欢迎跟帖讨论。
共享共进共赢
Sharing And Win-win Results
您需要登录后才可以回帖 登录 | 站点注册

本版积分规则

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

Mail To:Admin@SybaseBbs.com

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

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

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

Powered by Discuz! X3.5

© 2001-2026 Discuz! Team.

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