056、WHERE条件与运算符
“这个WHERE条件明明能查出数据怎么一加运算符就全空了”——上周五下午隔壁组的同事小周抱着笔记本冲过来屏幕上是一个简单的SELECT条件就两个LV_BUKRS ‘1000’再加一个金额字段的判断。他查了半天最后发现是运算符优先级把整个条件逻辑带偏了。ABAP里的WHERE和运算符看起来像SQL实际坑起来比SQL还隐蔽今天就把这些年踩过的雷一并倒出来。先说说最基础的等值条件。大多数人写的第一条ABAP SQL大概是SELECT * FROM MARA WHERE MTART ‘FERT’。这里有个习惯性的坏毛病把变量写在左边字段写在右边。比如WHERE LV_MTART MTART。ABAP的Open SQL在语法上允许你两边互换但性能上有区别吗老实说在ABAP里差别不大可读性却差很多。我自己的习惯永远是字段在左变量在右这样一眼就能看出是“哪个表字段和哪个变量比较”而且和ABAP内表操作的写法保持一致。你要是敢把变量放左边半年后回头看自己代码保准愣三秒。等值之外最常用的就是范围条件了。BETWEEN是个好东西但注意它包含边界值。WHERE DATUB BETWEEN LV_BEGDA AND LV_ENDDA这表示DATUB LV_BEGDA AND DATUB LV_ENDDA。很多新手会忘掉边界包含这回事第二天突然多出来第一天和最后一天的数据别问我怎么知道的。还有BETWEEN后面跟着的两个值如果LV_BEGDA比LV_ENDDA大那查出来就是空这种逻辑错误在调试时很难一眼看出来所以写完之后最好加个断言或者IF检查一下。运算符这块最容易翻车的是反逻辑。你想查“金额不等于1000”的凭证下意识写WHERE ZZMONEY 1000。在Open SQL里这个没问题但如果你用了内表FILTER或者FOR ALL ENTRIES情况立刻变得微妙。记住一个铁则SQL的NULL不参与任何比较运算。如果某行ZZMONEY是NULL那么ZZMONEY 1000这个条件永远不成立那行会被过滤掉。也就是说用不等号查不出NULL的值。解决办法要么在SELECT里加WHERE ZZMONEY IS NOT NULL要么把不等条件改成ZZMONEY 1000再在程序里做反向差集。后者更稳妥因为NULL的语义在不同数据库上实在不统一。再来看LIKE运算符。这是模糊查询的老朋友但ABAP的LIKE和SQL标准有个微妙差异转义字符。比如你想查包含百分号%的文本直接写WHERE NAME LIKE %%%‘是不行的因为ABAP里转义符默认不是反斜杠。正确写法是先声明转义字符WHERE NAME LIKE ‘%|%%’ ESCAPE ‘|’。这里有个坑ESCAPE后面的字符必须用单引号括起来而且只能是一位字符。还有下划线_在LIKE里是通配符表示任意单字符如果你真要匹配下划线也得用转义。举例查物料号第二字符是’1’写WHERE MATNR LIKE ‘_1%’如果查物料号本身包含下划线那必须用ESCAPE不然结果会比你想象的多得多。IN运算符和FOR ALL ENTRIES的恩怨可能每个ABAPer都经历过。内表大几千行直接SELECT … FOR ALL ENTRIES IN ITAB WHERE FIELD ITAB-FIELD这是常规操作。但很多人不知道FOR ALL ENTRIES会在条件一多时自动把IN条件展开成OR而且当内表为空时会忽略所有WHERE条件直接全表扫这就是著名的“空内表陷阱”。我有个血的教训当时在报表里忘了清空内表一运行直接查了全公司的主数据数据库差点报警。所以无论你用什么条件只要用了FOR ALL ENTRIES请先判断内表是否为空。另外FOR ALL ENTRIES生成的SQL会把多个字段条件合并成OR这会让普通索引失效如果性能敏感建议改用内表JOIN或者分批查询。关于WHERE里的括号这才是小周翻车的罪魁祸首。ABAP的Open SQL里括号可以用于分组逻辑但和算术运算一样AND优先级高于OR。如果你写WHERE TYPE ‘A’ OR TYPE ‘B’ AND STATUS ‘X’实际执行的是TYPE ‘A’ OR (TYPE ‘B’ AND STATUS ‘X’)因为AND绑得更紧。这很容易漏数据。想查类型A或B中状态为X的必须加括号WHERE ( TYPE ‘A’ OR TYPE ‘B’ ) AND STATUS ‘X’。千万别省括号也别依赖记忆中的优先级。即使你觉得很清楚过两周改需求的人不一定清楚。写进代码里的括号就是给未来的自己看的。还有字符串比较的坑。ABAP默认的字符串比较是区分大小写的吗在Open SQL里取决于数据库设置通常不区分。但如果你在HANA上默认行为可能不同。更麻烦的是尾部空格。DB2上CHAR类型比较会忽略尾部空格VARCHAR不会。你用WHERE NAME TOM 左边的表字段是CHAR(10)那’TOM’存进去自动补了7个空格两个值一比较数据库说相等但如果你用内表排序或者去重ABAP却认为不相等。这种不一致会让人抓狂。我的建议是所有关键字段统一使用STRING或固定长度的类型永远不要在程序里依赖“自动补空格”来匹配。日期和时间条件的写法值得单独说。在ABAP里日期不能直接和字符串比较除非你写对格式。WHERE BUDAT ‘20240101’这没问题因为DATS类型内部就是8位字符。但如果你写BUDAT ‘2024-01-01’那就废了格式不匹配啥也查不到。还有日期加运算符的坑WHERE BUDAT SY-DATUM - 30这种写法看起来是30天前但SY-DATUM是DATS类型整数减法会把日期当成数字算遇到月初就会出大问题。比如20240101减去30等于20240071这不是合法日期。正确做法是用计算函数WHERE BUDAT SY-DATUM - 30其实在HANA上这个可以因为日期允许数字加减但跨月时真的会错。我一般用ADD_DAYS_TO_DATE或者直接算好日期范围再传入。说到括号再补一个ABAP内表行号条件的奇怪用法。你想取每组的最大值可能会写WHERE ( AUFNR, VERSION ) ( SELECT … )这语法在ABAP新版本里支持多字段元组比较。但注意元组比较中每个字段的顺序必须和表字段一致而且括号不要多也不要少。调试时如果发现“无效的表字段”错误八成是元组里的字段名写错或者顺序乱了。这种写法高级是高级但可读性差我宁可拆成两个条件也没问题真的性能瓶颈不在这种地方。运算符还有一个容易忽略的点CASE语句只能在SELECT列表里用不能直接放在WHERE里。很多人想写WHERE CASE WHEN … THEN … END ‘X’ABAP会直接报语法错。解决办法是把它包在子查询里或者用OR组合条件。别跟数据库原生SQL比ABAP有自己的限制该绕弯就绕弯。调试真实问题的时候还有一种情况是SQL的隐式转换。比如某个字段是NUMC类型你拿一个字符串变量去比较只要字符串全是数字ABAP通常会转换但如果有字母就会报“类型冲突”或者产生意想不到的结果。我碰到过一个Z字段是CHAR10里面存了’00123’程序里用LV_NUM 123去比较WHERE ZFIELD LV_NUM结果发现查不出来。因为ABAP把LV_NUM转成’000123’和’00123’位数不一样永远不等。这个坑很让人崩溃。所以老老实实类型转换WHERE ZFIELD |{ LV_NUM WIDTH 10 ALIGN RIGHT }|或者直接CONCATENATE出来。最后说一个老生常谈但永远有人犯的错把内表字段直接和变量比较时忘记“表别名”。在FOR ALL ENTRIES里内表字段必须带上表别名不语法上不需要。但是当你在WHERE里同时引用内表字段和数据库表字段而且字段名相同时系统可能认为是同一个字段造成条件恒成立。比如SELECT * FROM BKPF FOR ALL ENTRIES IN ITAB WHERE BUKRS ITAB-BUKRS AND BUKRS ITAB-BUKRS这样写只会让数据库认为BUKRS BUKRS永远真。这种低级错误我见过不止一次。正确做法是给数据库表起别名FROM BKPF AS A WHERE A~BUKRS ITAB-BUKRS。这样才清楚。总结不写了。给你一个我自己的习惯写WHERE条件前先把所有可能为空的值处理掉写运算符时能加括号就加括号别管优先级写比较时永远假设数据库字段可能有NULL和尾部空格写完立刻跑一次带极端数据的测试比如空字符串、全空格、00开头、和NULL。这些才是ABAP调试的真相。等你被NULL坑过三次被日期加减坑过两次被空内表坑过一次你自然就会记得住。到那时候你也能坐在电脑前一边敲WHERE一边笑当年自己傻得可爱。