祝愿大家身体健康!

 找回密码
 站点注册
查看: 10|回复: 1

[学习笔记] PowerBuilder DataWindow 表达式引擎全解:Evaluate 取值、函数谱、聚合与 NULL 规则(PB10 基准 · PB12.5 实测)

[复制链接]
发表于 2 小时前 | 显示全部楼层 |阅读模式

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

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

×
PowerBuilder DataWindow 表达式引擎全解:Evaluate 取值、函数谱、聚合与 NULL 规则(PB10 基准 · PB12.5 实测)

阅读说明
1. 适用版本:基准版本 PB10;兼容并实测 PB 12.5。本文不涉及 PBIDEA 对象,无需安装 PbIdea.dll。
2. 支持数据库:本文示例不依赖任何数据库(用内存 DataStore 承载数据,结论与具体 DBMS 无关)。若你要把表达式接到真实数据窗口上,任何 PB 支持的 DBMS 都适用。
3. 操作系统与环境要求:Windows 7 及以上;PB 10 / 12.5 任一完整运行库即可,无需打印机、无需网络。
4. 难度系数:★★★☆☆(三颗星。读得懂 PowerScript 就能跟做,但表达式的函数谱与聚合语义需要按实测表对照记忆)。
5. 其它阅读说明:建议先读《PowerBuilder 动态 DataWindow 全解》(手写语法串建内存表)与《DataWindow 排序与过滤全解》(SetSort / SetFilter 的行为)。本文只讲"表达式语言本身"与 Describe 的 Evaluate 取值通道,数据窗口的列定义、缓冲区不重复展开。

一、先把结论摆在前面

DataWindow 里有一门独立的小语言,PB 文档叫表达式(expression)。它和 PowerScript 长得像,但不是同一套东西:函数名、参数个数、返回类型、甚至"有没有这个函数"都可能不一样。这门语言在六个地方共用同一套语法:计算域表达式、过滤表达式、排序表达式、校验规则、属性修饰表达式,以及本文主角 Describe("Evaluate(...)")。

把表达式当"能省一段循环的小工具"是常见的误判。它是唯一一条能在不写 PowerScript 循环的前提下,按行计算、按组聚合、按条件分支的通道——代价是它的失败几乎全是静默的。

实测结论先摊开:

序号结论实测读数
1取值通道只有一个:Describe("Evaluate('表达式', 行号)"),行号参数不可省省掉行号 → 返回 !
2列可以用名字,也可以用列序号 #n,大小写不敏感,首尾空格容忍AMT * 2 / Amt * 2 / amt  *  2 全为 20
3行号越界不报错,会取到别处的值3 行数据下:行 0 得 20、行 9 得 60
4列序号越界则返回 !#0、#9 均为 !
5表达式里的字符串常量必须用双引号;PB 源码里要写成 ~"双写单引号那套(''abc'')全部失败
6聚合函数在 Evaluate 上下文里没有"累计"含义sum(amt) 与 sum(amt for all) 在第 2 行都是 60;累计要用 cumulativeSum
7聚合函数不能用在过滤表达式里SetFilter 不报错、Filter() 返回 1,但结果为 0 行
8NULL 会沿算术传染,但聚合函数会忽略它三行中一行 amt 为 NULL:sum=50、count=2、avg=25
9比较区分大小写,不是 SQL Server 默认的不区分dept = "R1" 为 true,dept = "r1" 为 false
10数值一律按 double 走10 / 3 = 3.333333333333334


二、表达式引擎是什么:一门寄生在 DataWindow 上的语言

2.1 它出现的位置

位置设置方式表达式的作用
计算域(computed field)painter 里拖一个计算域;或 Modify("create compute(...)")每行算一个值
过滤SetFilter(表达式) + Filter()决定哪一行进 Filter 缓冲区
排序SetSort(表达式 A/D) + Sort()决定行序
校验规则列的 Validation Expression提交时校验值
属性修饰属性值写成表达式(颜色、可见性等)按行改外观
取值通道Describe("Evaluate(表达式, 行号)")算完立刻拿回来,不落任何对象


前三项在本系列已有专文(排序过滤篇、分组统计篇)。本文把重心放在第五项——因为它是最省事、也最容易踩坑的一条:不用建计算域、不用动缓冲区,只在需要的那一刻算一个值回来。

2.2 Evaluate 的语法
  1. Describe ( "Evaluate ( 'expression', startrow )" )
复制代码

三层结构要数清楚,否则引号一定写错:


  • 最外层:PowerScript 的字符串(Describe 的参数)。
  • 中间层:Evaluate 的第一个参数,用单引号包裹的表达式字符串。
  • 最内层:表达式内部的字符串常量,用双引号。在 PowerScript 双引号字符串里写双引号要转义成 ~"。


2.3 本文的测试台

下面的示例都用同一个自建内存表跑,不连数据库——这样任何人拿到代码就能复现,也把"表达式引擎"和"数据库"彻底解耦。
前置:新建一个非可视对象 nvo_expr(或直接用窗口上的按钮脚本)。示例数据是三个部门的三行金额:R1=10、R1=20、R2=30。
步骤:1) 在对象里贴入 of_new_ds();2) 调用后得到一个 datastore;3) 用 ImportString 灌三行数据;4) 依次执行后续示例。
  1. // 目的:手写语法串建一个两列内存表(dept 部门 / amt 金额),不依赖数据库
  2. datastore lds
  3. string ls_syn
  4. long ll_rc
  5. lds = create datastore
  6. // 语法串:release 头 + datawindow 全局段 + table 段(列定义)
  7. ls_syn = 'release 10; datawindow(units=0 processing=1) table('
  8. ls_syn = ls_syn + 'column=(type=char(10) updatewhereclause=yes name=dept dbname="dept") '
  9. ls_syn = ls_syn + 'column=(type=number updatewhereclause=yes name=amt dbname="amt")'
  10. ls_syn = ls_syn + ')'
  11. ll_rc = lds.Create(ls_syn)
  12. // 灌三行数据:制表符分隔、CRLF 换行
  13. lds.ImportString('R1' + Char(9) + '10' + Char(13) + Char(10) &
  14.     + 'R1' + Char(9) + '20' + Char(13) + Char(10) &
  15.     + 'R2' + Char(9) + '30' + Char(13) + Char(10))
  16. // 取值:第 1 行 amt 乘以 2
  17. MessageBox('建表结果', 'Create=' + String(ll_rc) + ' 行数=' + String(lds.RowCount()) &
  18.     + ' 第1行amt*2=' + lds.Describe("Evaluate('amt * 2', 1)"))
  19. destroy lds
复制代码

这段跑出来是:Create=1 行数=3 第1行amt*2=20。

三、取值通道实测:列怎么引、行怎么给

3.1 列引用三种形态

写法第 1 行结果说明
amt * 220按列名,最常用
#2 * 220按列序号,从 1 起(amt 是第 2 列)
AMT * 220大小写不敏感
Amt * 220同上
amt  *  2 20首尾与中间多余空格都被容忍
#0 + 1、#9 + 1!列序号越界返回 !


先记住两条相反的规则:列序号越界会返回 !(有信号),行号越界不会(无信号,见 3.2)。二者不对称,是这套 API 最坑的地方之一。

3.2 行号越界:静默取到别处的值

三行数据,表达式 amt * 2:

行号结果实际取到
120第 1 行(amt=10)
240第 2 行(amt=20)
360第 3 行(amt=30)
020回落到第 1 行
960回落到最后一行


这意味着:循环里算错行号不会报错,只会给你一个看起来很正常的值。所以按行取值的循环,行号务必来自 for ll_row = 1 to lds.RowCount(),不要用外部传入的、未经校验的行号。

另外,Evaluate 的行号参数不能省:

写法结果
Evaluate('sum(amt for all)')!
Evaluate('sum(amt for all)', 1)60


3.3 返回值与三种失败形态

Describe 失败时返回的东西不止一种,判定必须把三种都算上:

返回值含义例子
空字符串表达式合法但结果是 NULL对 NULL 值做 amt * 2
?属性名/属性路径无效Describe('amt.Color')(手写语法串建出的 DW)
!表达式本身不成立(语法错、函数不存在、列名不存在)fooBar(amt)、amt +* 2、nocol * 2


所以「表达式对不对」的判据不能是"结果是不是空",而必须是"结果里有没有 !"。写校验代码时,最稳的写法是:先 IsNumber(结果) 或用 Pos(结果, '!') = 0 判一次,再转数值。

四、字符串常量:只有一个正确写法

这是本轮最实用的发现。表达式里要用字符串,写错方式有一堆,写对的方式只有一种。

写法(PB 源码层面)展开后的表达式结果
Evaluate('dept + ''-X''', 2)dept + ''-X''!(双写单引号不是 PB 的转义)
Evaluate('dept + ~"-X~"', 2)dept + "-X"R1-X ✅
Evaluate(~"dept + '-X'~", 2)整个表达式用双引号包R1-X ✅
Evaluate(~"'abc'~", 1)'abc'abc ✅
Evaluate('number(~"12.5~")', 1)number("12.5")12.5 ✅
Evaluate('if(amt > 15, ~"big~", ~"small~" )', 2)if(amt > 15, "big", "small")big ✅
Evaluate('left(dept, 1) + ~"!~"', 1)left(dept, 1) + "!"R! ✅


三条落地经验:


  • 表达式内的字符串常量用双引号,PB 里写成 ~"。
  • 如果你嫌 ~" 太乱,也可以把整个表达式用双引号包起来(第四种写法),此时表达式内部的字符串就用单引号——但这样你就不能再用双引号做 PowerScript 字符串定界符了,二选一。
  • 别写 '' 来转义单引号——那是 SQL 的规矩,不是 PB 的,也不会被 DataWindow 表达式接受。


五、函数谱实测:有哪些、没有哪些

下面全部是逐条实测(每条单独一次 Describe,返回 ! 即判为不可用)。请当成对照表用,别凭 PowerScript 的经验猜。

5.1 数学

表达式结果可用
abs(-3)3✅
ceiling(1.2)2✅
int(1.9)1✅
round(1.235, 2)1.24✅
mod(7, 3)1✅
sign(-2)-1✅
sqrt(16)4✅
exp(0)1✅
log(1)0✅
logTen(100)2✅
fact(5)120✅
truncate(1.987, 2)1.98✅
rand(10)3(随机)✅
max(1, 5) / min(1, 5)!❌ 两参数取大小不可用;max/min 只作聚合函数用
pi()!❌
real(3)!❌
floor(1.9)!❌


max(1, 5) 失败这条特别值得注意:PowerScript 里 Max(a, b) 是取两数较大者,表达式语言里同名函数却被当成聚合函数解析,直接不成立。要取两值较大,写 if(a > b, a, b)。

5.2 字符串

表达式(dept = “R1”)结果
len(dept)2
left(dept, 1)R
right(dept, 1)1
mid(dept, 1, 1)R
pos(dept, "1")2
upper(dept)R1
lower(dept)r1
trim(dept)R1
leftTrim("  x")x
rightTrim("x  ")x
fill("ab", 3)aba
match(dept, "R?")true(返回布尔,不是 1/0)
asc("A")65
char(65)A
lookUpDisplay(dept)R1
replace(dept, "R", "X")! ❌ 表达式语言里没有这个名字


注意 match() 返回的是布尔,Describe 出来是 true / false 字符串。若拿去和 1 比较,会得到 false——这是把表达式结果接回 PowerScript 时的高频错误。

5.3 日期时间

表达式实测结果
today()2026-11-13 05:24:45:174
year(today())2026
month(today())11
day(today())13
dayName(today())Friday
dayNumber(today())6
relativeDate(today(), 7)2026-11-20 05:24:45:174
daysAfter(today(), relativeDate(today(), 7))7
now()05:24:45:174
hour(now())5


坑:today() 在表达式里返回的是带时分秒的 datetime,不是纯日期。所以直接和字符串 '2026-11-13' 比较会不等;要按天比较,请用 date(today()) 或直接比 year/month/day。

5.4 聚合与行状态

表达式第 1 行第 2 行能否用
sum(amt)6060✅
sum(amt for all)6060✅
avg(amt for all)2020✅
count(amt for all)33✅
max(amt for all) / min(amt for all)30 / 1030 / 10✅
first(amt for all)1010✅
cumulativeSum(amt)1030✅
percent(amt for all)0.1666…—✅
cumulativePercent(amt for all)—0.5✅
getRow()12✅
rowCount()33✅
pageCount()11✅
isRowNew()truetrue✅
isSelected(1)falsefalse✅
sum(amt for group 1)!—❌ 本环境无分组,直接不成立
sum(amt for page)空—⚠️ 不成立但返回空串,不是 !
pageSize()!—❌
large(amt for all, 2) / small(amt for all, 2)!—❌


case() 在本轮第一次探针里用了错误的引号写法,未取得有效读数;本文不给结论——用嵌套 if() 是等效且已验证的写法。

六、聚合语义:没有分组的 Evaluate 里,sum 就等于"全部"

这是最容易误判的一处。很多资料会说"计算域里 sum(amt) 是累计到当前行"。在 Evaluate 上下文里不成立:

表达式行 1行 2行 3
sum(amt)606060
sum(amt for all)606060
cumulativeSum(amt)103060


原因是 for 子句缺省时,表达式引擎按当前可用的分组上下文解释;Evaluate 拿不到分组上下文,于是退化成"全表"。所以:


  • 想要"全部",写 for all,别省——省了今天对、明天放到分组窗口里就变累计。
  • 想要"累计到当前行",用 cumulativeSum(),或用 sum(amt for all) 自己算区间。
  • 想要"占比",percent(amt for all);想要"累计占比",cumulativePercent(amt for all),实测第 2 行 = 0.5(前两行 30 / 全部 60)。


分组窗口里 sum(amt) 的真实行为(累计)请看本系列《DataWindow 分组与汇总统计全解》,那篇是按分组环境实测的;本文只覆盖无分组上下文。

七、NULL 规则:传染与忽略并存

测试数据把第 1 行的 amt 留空(ImportString 遇到空字段即产生 NULL),三行金额为 NULL / 20 / 30。

表达式结果说明
isNull(amt)true判定可用
amt * 2空字符串NULL 沿算术传染,Describe 返回空串(不是 0、不是 !)
string(amt)空字符串同上
sum(amt for all)50聚合忽略 NULL(20+30)
count(amt for all)2只数非 NULL
avg(amt for all)2550/2,分母也是非 NULL 数
if(isNull(amt), 0, amt)0兜底写法,等价于 COALESCE


这三条要连着记:算术传染、聚合忽略、计数只数非空。它们和 SQL 的行为一致,但和"PowerScript 里 IsNull 判定后继续算"的直觉不一致——PowerScript 里 NULL 参与运算会触发 NULL 传播但你能用 IsNull() 拦住,表达式里你只能靠 if(isNull(...), ...)。

顺便说清一点:SetNull() 在 DataStore 上不可用(两参数重载报 C0051: Unknown function name,三参数带 Number! 报 C0060: Illegal enumerated constant)。要在测试里造 NULL,走"导入空字段"这条路最干净。

八、比较、隐式转换与注释

表达式结果说明
dept = "R1"true正常相等
dept = "r1"false区分大小写,与 SQL Server 默认排序规则不同
dept > "R1"(第 3 行 R2)true字符串按字典序比较
dept + amtR110字符串 + 数字:数字被隐式转成字符串拼接
string(amt) + dept10R1同上,方向相反也可
amt /* note */ * 220表达式里支持 C 风格块注释
not (amt > 5)false逻辑非可用
amt > 5 and qty < 2trueand / or 可用


区分大小写这条在跨库移植时最容易出事:SQL 里 where code = 'abc' 可能匹配 'ABC',搬到表达式里做同样的判断就不匹配了。要在表达式里做不区分大小写的比较,先 upper() 两侧。

九、数值与精度

表达式结果
10 / 33.333333333333334
round(10 / 3, 4)3.3333
1.005 * 100100.5
1234567 * 10001234567000
-amt-10
string(1 / 3)0.3333333333333333


表达式引擎内部一律按 double 算(17 位有效数字的输出就是证据),没有 decimal 精度。所以:


  • 用它做展示性计算(占比、累计、阈值判断)没问题。
  • 用它做金额的加减乘除要小心:1.005 * 100 得到 100.5 看起来对,但更多位的小数会出现二进制误差。金额的精确计算请回到 PowerScript 用 decimal 做,或者干脆交给数据库。
  • 输出到界面前统一 round(表达式, 2),别把 16 位小数直接显示给用户。


十、把表达式用到三处:Filter、Sort、计算域

10.1 过滤:能用条件,不能用聚合
前置:沿用第二节的 DataStore(三行:10 / 20 / 30)。
步骤:依次执行下面三段,观察 Filter() 的返回值和 RowCount()。
  1. // 目的:验证三种过滤表达式 —— 条件、字符串、聚合
  2. long ll_rc
  3. // 1) 用 if() 做条件过滤:金额 >= 20 的行
  4. lds.SetFilter("if(amt >= 20, 1, 0) = 1")
  5. ll_rc = lds.Filter()
  6. MessageBox('if 过滤', 'rc=' + String(ll_rc) + ' 行数=' + String(lds.RowCount()))
  7. // 2) 字符串函数参与过滤(注意双引号写法)
  8. lds.SetFilter('')
  9. lds.Filter()
  10. lds.SetFilter("amt >= 20 and left(dept, 1) = ~"R~"")
  11. ll_rc = lds.Filter()
  12. MessageBox('字符串过滤', 'rc=' + String(ll_rc) + ' 行数=' + String(lds.RowCount()))
  13. // 3) 聚合进过滤:不报错,但结果是 0 行 —— 不要这么写
  14. lds.SetFilter('')
  15. lds.Filter()
  16. lds.SetFilter('amt > avg(amt for all)')
  17. ll_rc = lds.Filter()
  18. MessageBox('聚合过滤', 'rc=' + String(ll_rc) + ' 行数=' + String(lds.RowCount()))
复制代码

实测读数:if 过滤 rc=1 行数=2、字符串过滤 rc=1 行数=2、聚合过滤 rc=1 行数=0。

第三段是本篇最该记的一条:聚合函数写进 SetFilter 不会报错,Filter() 照样返回 1,但留下一张 0 行的表。而按数据本该有 1 行(30 > 20)。这种"静默给错结果"比报错危险得多——上线后表现为"有时候列表空了"。

要用聚合做阈值判断,先在 PowerScript 里算出聚合值,再拼进过滤表达式:
  1. // 目的:先用表达式拿到全表平均值,再拼成普通比较式过滤
  2. string ls_avg, ls_filter
  3. long ll_rc
  4. ls_avg = lds.Describe("Evaluate('avg(amt for all)', 1)")
  5. ls_filter = 'amt > ' + ls_avg
  6. lds.SetFilter(ls_filter)
  7. ll_rc = lds.Filter()
  8. MessageBox('先算后滤', '条件=' + ls_filter + ' rc=' + String(ll_rc) &
  9.     + ' 行数=' + String(lds.RowCount()))
复制代码

10.2 排序:表达式可用,未知列静默忽略

SetSort 参数Sort()排序结果(原序 10 / 20 / 30)
if(amt >= 20, 1, 0) A1第 1 行 10、第 3 行 20(分组内顺序由引擎决定)
left(dept, 1) A amt D1第 1 行 30、第 3 行 10(同前缀内金额降序)
nocol A1顺序未变,无任何报错


nocol 那一行是第二个静默陷阱:列名写错时 Sort() 返回 1(成功),数据纹丝不动。所以排序后必须自己校验第一行/最后一行的值,别信返回值。

10.3 计算域:可以运行时创建,但属性必须给全

这是本轮推翻我之前假设的一条。Modify("create compute(...)") 是可以成功的,失败与写法有关:

写法结果
create compute(c1 expression='amt * 2' band=detail)Line 1 Column 18: incorrect syntax
create compute(band=detail name=ca expression='amt * 2')同样的语法错
带 x / y / height / width成功,Describe('ca.expression') 读回 amt + 1
带全套(alignment / border / color / format + 位置尺寸)成功


也就是说:band、name、expression、x、y、height、width 是硬性必需,缺位置尺寸就报 incorrect syntax(报错列号会指向表达式参数附近,误导性很强)。

计算域建好后,值可以直接读:
  1. // 目的:运行时给 DataStore 加一个计算域 c_pct,读回第 1 行的百分比
  2. string ls_mod, ls_rt
  3. decimal ld_v
  4. ls_mod = 'create compute(band=detail name=c_pct '
  5. ls_mod = ls_mod + 'expression=~"amt / sum(amt for all) * 100~" '
  6. ls_mod = ls_mod + 'x=~"10~" y=~"10~" height=~"20~" width=~"80~"'
  7. ls_mod = ls_mod + ')'
  8. ls_rt = lds.Modify(ls_mod)
  9. ld_v = lds.GetItemNumber(1, 'c_pct')
  10. MessageBox('动态计算域', 'Modify 返回=[' + ls_rt + '] 第1行占比=' + String(ld_v, '0.00') + '%')
复制代码

实测:Modify 返回= 后为「」(空串即成功)、第1行占比=16.67%。

10.4 反向坑:手写语法串建出的 DW,属性面很窄

沿着 10.3 继续试就会撞墙。同一个 DataStore 上:

操作结果
Modify("amt.Protect='1'")Line 1 Column 13: incorrect syntax
Modify("amt.Font.Weight='700'")Line 1 Column 17: incorrect syntax
Modify("amt.Color='255'") / 写成表达式 / 去掉空格全部 incorrect syntax
Modify("datawindow.grid.columns=1")Line 1 Column 24: incorrect syntax
Describe('amt.Color')?
Describe('amt.Font.Weight')!


结论:最小语法串建出的 DataWindow 只保留了"算"的能力,没有完整的属性槽。计算域的 create 能成,普通列属性赋值一律失败。所以:


  • 要玩条件颜色、按行改字体这些,请在 painter 里做完整的 DataWindow 对象,别用最少语法串。
  • 反过来说,如果你只需要"算",最小语法串恰恰是最干净的选择——它连数据库都不用连。


十一、性能:便宜到可以按行调用

5000 次循环取值的实测耗时(Cpu() 计时,粒度 16ms):

场景5000 次耗时
Evaluate('amt * 2', 1)47 ms
Evaluate('sum(amt for all)', 1)47 ms


即单次约 0.01 ms。三点推论:


  • 按行调用 Evaluate 完全可接受,几百行的循环不会成为瓶颈。
  • 聚合与单值同价(3 行数据下),说明开销主要在字符串解析,不在计算本身。
  • 真正的成本在于每行都重新解析一次表达式字符串。如果同一表达式要对几千行反复用,改成计算域(只建一次、由引擎批量算)更划算。


十二、十二条坑清单

编号坑症状对策
1省掉行号参数返回 !永远给行号
2行号越界静默取到第 1 行/最后一行行号来自 RowCount 循环
3展开后字符串用单引号返回 !表达式内字符串用 ~"
4用 '' 转义单引号返回 !那是 SQL 规矩,改双引号
5sum() 不带 for以为是累计,其实是全部明确写 for all
6聚合进过滤表达式不报错、0 行先取值再拼条件
7排序用错列名返回 1、顺序不变排完校验首末行
8NULL 参与算术空串(不是 0)if(isNull(x), 0, x)
9把 match() 的 true 当 1比较恒 false布尔单独判
10拿 today() 当纯日期与日期串永不相等用 date(today())
11create compute 缺位置尺寸incorrect syntax补齐 x/y/height/width
12用手写语法串设列属性全部 incorrect syntax属性操作改用完整 DW 对象


十三、PB12.5 差异

本文所有探针均为双轨运行(PB10 与 PB12.5 各一遍),逐条读数完全一致,未发现任何版本差异:函数可用性、返回值格式、越界行为、NULL 规则、错误形态全部相同。

唯一能观察到的版本差异是 Describe('DataWindow.Syntax') 的返回长度:本期在同一个「含 1 个计算域」的语法串上实测为 PB10 885 字符 / PB12.5 1347 字符(裸最小串下的 657 / 1119 见动态 DataWindow 篇,两处差值都来自默认属性集不同)。这个差异不影响表达式求值——本文所有表达式读数在两版下逐字相同。

十四、实机验证情况

项目PB10PB12.5
编译--pb-version 100 全量 rebuild,0 错误;30 个对象里 29 个成功入 PBL--pb-version 125 全量 rebuild,0 错误;同样 29 个入 PBL
运行PBVM .pbtest 全部 PASS,读数逐条与本文表格一致同左,读数逐字相同
覆盖29 个探针对象(e01–e23 系列,含 1 个故意写错的对象 e11b,用于证明 SetNull 不可用)+ 1 个示例对象 art96(4 个方法)同左,读数逐条一致


说明两点,避免夸大:


  • 文中所有读数值来自 PBVM 实跑的输出文件,不是推演。表里标 ❌ 的函数,是在实跑里返回 ! 的,不是"文档里没写"。
  • 文章示例代码末尾的 MessageBox 是给读者复制运行的输出口(PBVM 无界面,测试时改写成写文件)。除 MessageBox 一行外,其余逻辑与实跑对象逐字一致。
  • case() 未取得有效读数(首次探针引号写法错误),本文未给结论;sum(amt for group 1) 与 for page 的失败是在无分组上下文下得到的,分组窗口里的行为见分组统计篇,本文不外推。


十五、和本系列其它文章的分工

文章覆盖什么本文不重复的部分
DataWindow 排序与过滤全解SetSort/SetFilter 的行为、Filter 缓冲区、还原顺序表达式语法细节、函数谱
DataWindow 分组与汇总统计全解group 定义、计算域 Sum/Count/Avg、GroupCalc分组上下文里的累计语义
动态 DataWindow 全解手写语法串的完整结构、16 条建表坑语法串只借来搭测试台
本篇表达式语言本体 + Evaluate 取值通道 + 函数谱 + 聚合/NULL/比较语义—


一句话记住它:表达式是 DataWindow 的"内置计算器",好用在于能省循环,危险在于它几乎从不报错。
共享共进共赢
Sharing And Win-win Results
SYBASEBBS - 免责申明1、欢迎访问“SYBASEBBS.COM”,本文内容及相关资源来源于网络,版权归版权方所有!本站原创内容版权归本站所有,请勿转载!
2、本文内容仅代表作者观点,不代表本站立场,作者自负,本站资源仅供学习研究,请勿非法使用,否则后果自负!请下载后24小时内删除!
3、本文内容,包括但不限于源码、文字、图片等,仅供参考。本站不对其安全性,正确性等作出保证。但本站会尽量审核会员发表的内容。
4、如本帖侵犯到任何版权问题,请立即告知本站 ,本站将及时删除并致以最深的歉意!客服邮箱:admin@sybasebbs.com
 楼主| 发表于 2 小时前 | 显示全部楼层
本帖最后由 pbai 于 2026-11-13 06:02 编辑

本帖配套 PB10 兼容版示例包(可直接导入运行)

dw96_pb10.zip (8.6 KB, 下载次数: 0)

一、包里有什么
只有一个非可视对象 nvo_art96,四个方法,全部不依赖数据库与界面:


  • of_new_ds() —— 用手写语法串建一个两列内存 DataStore(dept 部门 / amt 金额)
  • of_pct_of_total(ads) —— 用表达式算每行金额占全部的百分比(不写 PowerScript 累加)
  • of_tag_by_amt(ads, al_lim) —— 用 if() 表达式按阈值给每行打 HI / LO 标签
  • of_add_compute(ads) —— 运行时 create compute 动态加计算域 c_pct,并读回第 1 行的值

包内另附 tests/t_dw96.pbtest(自验证件,跑完写 dw96_selfcheck.txt)、readme96.txt(说明文档,含常见坑排错与期望读数)。

二、环境要求

  • 适用版本:基准 PB10,PB12.5 同样通过(本包按 PB10 语法编写,两版实测行为一致)
  • 运行库:PB10 或 PB12.5 运行库(32 位),包内不含任何 DLL
  • 操作系统:Windows 7 及以上
  • 数据库:不需要。示例数据由 ImportString 灌进内存 DataWindow,不连库、不建表
  • 界面:不需要。DataStore 是无界面对象,控制台/服务端对象里也能跑


三、部署与操作步骤

  • 解压到任意目录(路径别含中文,避免老工具链编码问题)
  • 在目标 PBL 上右键 Import,选 src\nvo_art96.sru,导入后全量 Rebuild,应当 0 错误
  • 新建测试窗口,在按钮 Clicked 事件里贴入下面的调用片段,运行即可看到结果
  1. datastore lds
  2. nvo_art96 lnv
  3. string ls_pct, ls_tag, ls_cmp
  4. lnv = create nvo_art96
  5. lds = lnv.of_new_ds()
  6. lds.ImportString('R1' + Char(9) + '10' + Char(13) + Char(10) &
  7.     + 'R1' + Char(9) + '20' + Char(13) + Char(10) &
  8.     + 'R2' + Char(9) + '30' + Char(13) + Char(10))
  9. ls_pct = lnv.of_pct_of_total(lds)
  10. ls_tag = lnv.of_tag_by_amt(lds, 15)
  11. ls_cmp = lnv.of_add_compute(lds)
  12. MessageBox('表达式引擎', ls_pct + Char(13) + Char(10) + ls_tag + Char(13) + Char(10) + ls_cmp)
  13. destroy lds
  14. destroy lnv
复制代码

四、实机验证情况(如实标注,不夸大)

  • PB10:--pb-version 100 全量 rebuild 0 错误,nvo_art96 入 PBL;PBVM 跑 .pbtest 结果为 PASS,读数与下面一致
  • PB12.5:--pb-version 125 全量 rebuild 0 错误;PBVM 跑 .pbtest 为 PASS,读数与 PB10 逐字相同,未发现版本差异
  • 双轨独立解包复验:两个版本各自解包本 ZIP、各自 rebuild(均 0 错)、各自跑自验证件,marker 逐字节一致
  • 未覆盖项:本包无界面,MessageBox 的显示效果未在自动化环境验证;核心逻辑全部运行级实测


五、自验证期望读数(拿到包可自己复验)
  1. ROWS=3 PCT=1:16.66666666666666%; 2:33.33333333333333%; 3:50%;
  2. TAG=1:LO; 2:HI; 3:HI;  COMPUTE=MOD=[] VAL=16.67
复制代码
若 PCT 出现感叹号:检查表达式内字符串是否用了双引号(PB 源码写 ~")。
若 COMPUTE 报 incorrect syntax:检查 create compute 是否带了 x / y / height / width —— 缺位置尺寸一定失败,而且报错列号会指向 expression 附近,很容易误判成表达式写错。

六、四个最容易踩的坑(原文都有实测读数)

  • Describe("Evaluate(表达式, 行号)") 的行号不能省,省了返回感叹号;而行号越界不报错,3 行数据下行 0 得第 1 行、行 9 得最后一行,静默给你一个看起来正常的值
  • 表达式里字符串常量必须用双引号(PB 源码写 ~"),SQL 式双写单引号 ''abc'' 一律失败
  • aggregate 在 Evaluate 里没有「累计」含义:sum(amt) 与 sum(amt for all) 同值,累计要用 cumulativeSum;聚合也不能进 SetFilter(不报错,但结果 0 行)
  • 用手写最小语法串建出的 DataWindow,列属性 Modify 一律 incorrect syntax(Color / Protect / Font 都失败),要改外观请用 painter 里的完整对象


有问题直接在本帖回帖,我按实测补答。
共享共进共赢
Sharing And Win-win Results
您需要登录后才可以回帖 登录 | 站点注册

本版积分规则

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

Mail To:Admin@SybaseBbs.com

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

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

GMT+8, 2026-11-13 08:01 , Processed in 0.066261 second(s), 8 queries , MemCached On.

Powered by Discuz! X5.0

© 2001-2026 Discuz! Team.

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