
054、Open SQL概述上礼拜半夜两点被手机震醒群里有人喊“生产机某个报表挂了”。我爬起来看日志发现一个特别诡异的短转储UNCAUGHT_EXCEPTION再往里追是一句再普通不过的SELECT。那代码长这样SELECT * FROM zbooks INTO wa_book WHERE author 鲁迅. ENDSELECT.单看语法没毛病可问题是程序里没有先SELECT SINGLE结果满足条件的记录有好几十条ENDSELECT一直循环到天荒地老最后把工作进程给拖死了。后来我打开那个报表发现前面还有个IF sy-subrc 0的判断逻辑看似完整实际上根本没处理多行返回的情况。那晚改完代码我就在笔记本的标题栏写了几个字Open SQL一半是SQL一半是ABAP的脾气。回到正题。如果你刚摸到ABAP的边一定听过Open SQL这四个大字。用官方的话讲它是SAP提供的一套数据库访问语句语法覆盖SELECT、INSERT、UPDATE、MODIFY、DELETE。但你别把它当成普通的SQL——它不是你写个WHERE就万事大吉的东西它背后连接的是ABAP数据字典、内表、工作区、系统字段SY-SUBRC、甚至SAP的锁机制。说白了你要学会的不是“SQL怎么写”而是“在ABAP的环境里怎么正确地跟数据库说话”。大家知道SAP系统底层可以跑Oracle、DB2、SQL Server、HANA连HANA都分三代。要是你的代码里直接写了JOIN的语法是数据库原生的哪天数据库从DB2换到HANA你的报表可能连夜炸给你看。Open SQL的核心价值就是屏蔽差异你写ABAP的SELECTSAP会帮你翻译成后端数据库能听懂的方言。你在开发环境里跑得好好的上了生产环境换了个数据库家族代码照样能跑。这就是Open SQL最朴素的好处。但如果你以为可以用它搞定一切那可太天真了。Native SQL的坑我不展开只说一句除非你是DBA否则别在ABAP程序里直接写EXEC SQL。数据库锁、事务控制、类型转换全是地雷炸一次你一个月工资就扣没了。接下来我们聊点实用的。所有Open SQL操作都围绕三类数据数据库表、内表、结构体。数据库表好理解就是ABAP数据字典里定义的表。在SE11里建表的时候你会给表定义字段、主键、技术属性然后激活。这表在底层数据库里真实存在但你在ABAP里只认它的技术名称比如ZBOOKS它不像那些带XX_KK前缀的透明表一样复杂但你得有自己的命名空间。SELECT最常用的用法是往内表里取数。新手最爱写的是SELECT * FROM zbooks INTO TABLE gt_books.注意我的写法我把*和换行弄得啰嗦一点其实ABAP不在乎这个。但我想说的是别一上来就SELECT *。有的开发同事为了省事全字段捞然后把不需要的列占着内存等到查大数据时直接TIME_OUT。你说表就几十条记录那无所谓要是一张堆了千万的记录的表那就是灾难。稍微高级一点的写法是只挑需要的字段SELECT author, title, price FROM zbooks INTO TABLE gt_books WHERE price 30.这里有个隐藏的坑SELECT后面的字段顺序要和INTO后面的结构体或内表字段顺序一一对应。如果你写INTO CORRESPONDING FIELDS OF可以按名称自动匹配我强烈建议你用这个写法SELECT author, title, price FROM zbooks INTO CORRESPONDING FIELDS OF TABLE gt_books WHERE price 30.这样就算字段顺序乱了也不会数据错位。不过性能上稍微有点损耗数据量大的话你得自己权衡。然后是WHERE条件。Open SQL里WHERE和标准SQL差不多支持等值、范围、模式匹配。但一个重要的区别是你可以在WHERE里使用ABAP变量甚至内表作为筛选条件。例如SELECT * FROM zbooks INTO TABLE gt_books WHERE author IN gt_authors.这时如果gt_authors为空你猜会发生什么结果是空结果不会报错。这很好但如果你用了FOR ALL ENTRIES那就完全是另一回事了。看下面这行SELECT * FROM zbooks INTO TABLE gt_books FOR ALL ENTRIES IN gt_books_filter WHERE author gt_books_filter-author.当gt_books_filter为空时这个SQL会把整张表读进来我第一次踩这个坑的时候差点以为业务逻辑是对的直到用户投诉“查询结果怎么会有几十万条”。所以记住这个铁律用FOR ALL ENTRIES前先判断内表是不是空的。代码里要写IF gt_books_filter IS NOT INITIAL. SELECT ... FOR ALL ENTRIES IN gt_books_filter ... ELSE. 这里给用户一个友好提示或者直接初始化结果内表 ENDIF.这个坑几乎每周都能在论坛上见到我猜你早晚也会遇到。SELECT还有一种方式是单行读取用SELECT SINGLESELECT SINGLE * FROM zbooks INTO wa_book WHERE objectid B-001.SINGLE的意思是如果找到多条记录SAP取第一条实际上数据库可能不会按你想的排序取。但前提是你要确保业务上不可能有多条记录。如果你不确定那还是用SELECT ... UP TO 1 ROWS吧加个排序SELECT * FROM zbooks INTO TABLE gt_books UP TO 1 ROWS ORDER BY create_time DESCENDING.注意UP TO 1 ROWS后面必须跟INTO TABLE还是INTO实际上语法允许INTO单条但建议配合ENDSELECT。不过新语法中UP TO 1 ROWS配合INTO TABLE更稳妥。再说INSERT和UPDATE。Open SQL里写数据一定要小心数据库表的主键。ABAP里INSERT是插入新记录如果主键已存在会报错并填充SY-SUBRC 4。你用标准SQL的习惯写INSERT然后发现SY-SUBRC等于4一脸懵。常见错误是这样INSERT zbooks FROM wa_book. IF sy-subrc 0. COMMIT WORK. ENDIF.如果wa_book里主键已经存在sy-subrc是4但COMMIT不会执行你以为没插进去其实数据库里可能有旧数据。为了避免这种情况很多人直接改用MODIFY有就更新没有就插入。MODIFY zbooks FROM wa_book. COMMIT WORK.MODIFY确实省心但有个微妙的地方它会整行覆盖。如果你只想更新几个字段用MODIFY会把其他字段变成初始化如果结构里没值。这时候得用UPDATEUPDATE zbooks SET price 58 WHERE objectid B-001.UPDATE只改你指定的字段安全得多。不过要在修改前考虑一下并发更新的问题。这就要提到SAP的锁机制了。一般的做法是用ENQUEUE_EZBOOKS加锁然后执行UPDATE最后DEQUEUE释放。锁这块是另一个大坑今天不展开但你得知道Open SQL的UPDATE不会自动加数据库锁它只管把SQL发出去数据库层面有隐式锁但ABAP程序里没有同步机制。DELETE更直接DELETE * FROM zbooks WHERE objectid B-001.一样先看SY-SUBRC再决定要不要COMMIT。注意DELETE删的是物理记录不是逻辑删除。如果你们公司的表设计了DELETED字段那你就别用DELETE了而是用UPDATE把这个字段设成X。我上面一直在提COMMIT WORK这里必须强调在SAP里Open SQL默认是在一次数据库LUW逻辑工作单元里事务由COMMIT或ROLLBACK结束。注意ABAP的COMMIT和数据库的COMMIT不完全一样它还会触发SAP的更新流程。如果你写了COMMIT WORK后面的WAIT UP TO 10 SECONDS可能会让你等锁这些细节别小看生产环境卡死常常就是这么来的。另一个常见的坑是INTO和WHEN的写法。老版本ABAP中SELECT单行读取要配合ENDSELECT而且INTO字段可以直接放结构体。但新语法中建议用SELECT ... INTO ( VALUE #() )甚至用FIELDS子句。这里不展开因为对于零基础的人先把下面的经典结构写熟了DATA: lt_books TYPE TABLE OF zbooks, ls_book TYPE zbooks. SELECT * FROM zbooks INTO CORRESPONDING FIELDS OF TABLE lt_books WHERE author 鲁迅. IF sy-subrc 0. LOOP AT lt_books INTO ls_book. WRITE: / ls_book-title. ENDLOOP. ELSE. MESSAGE 没找到鲁迅的书 TYPE I. ENDIF.这里有个非常容易犯的错误SY-SUBRC是受SQL影响的全局系统字段如果你在SELECT和IF之间不小心调用了其他语句SY-SUBRC值就被覆盖了。代码里被覆盖的例子很常见SELECT ... INTO ... sy-subrc 可能是0 WRITE: / sy-subrc. 不推荐但不会改值 某些函数调用会改 sy-subrc CALL FUNCTION Z_ANYTHING. IF sy-subrc 0. 这里判断的是函数的返回码不是SELECT的所以别把SY-SUBRC当圣旨。正确做法是SELECT后立刻用一个新变量保存sy-subrc或者干脆用IF ... IS NOT INITIAL判断结果内表。最后聊聊性能。Open SQL虽然是抽象的但它能帮你在数据库层做很多事。比如能聚合的尽量用聚合函数别把数据拉到内存里再自己算。下面这句很常见SELECT author, COUNT(*) FROM zbooks GROUP BY author INTO TABLE gt_author_count.如此能省很多内存。还有JOIN要小心。Open SQL支持INNER JOIN但只支持连接不支持LIKE、范围条件等。如果连接条件复杂或者涉及大量数据宁可分成两条SELECT再处理也别试图写花哨的大SQL。我以前为了炫技写过6张表的JOIN结果优化器跑半天后来拆成三个子集合并反而快了几倍。再想提一个事用过时的SELECT *加ENDSELECT输出在小数据量时没问题但你要知道ENDSELECT会逐行读取相当于数据库游标循环里操作别太重。新写的代码建议直接用INTO TABLE一次拿到内表。结尾说点个人习惯。我调试Open SQL问题时不喜欢用WA和ITAB这种匈牙利命名也懒得写注释主要是被逼的——多年后看自己代码都是那时候的泪。你们写的时候一定在关键SQL上方一行注释写上“这块逻辑为什么这么写”“遇到大数据怎么办”。比如 这里必须加UP TO 1 ROWS因为同一订单可能有多条历史记录我只需要最新的 SELECT * ... INTO TABLE lt_order UP TO 1 ROWS ORDER BY create_time DESCENDING.否则半年后你自己回头改bug看到这个SQL肯定会想“当初写这个是图啥”。还有学会用ST05跟踪SQL用SAT来分析性能。Open SQL说白了就是一层皮皮下面的东西是数据库再执行什么怎么执行的你心里得有数。别等到用户喊报表慢才想起来看是不是遗漏了索引。SQL慢八成是索引没建好两成是写法烂。索引这事归DBA管但你要能通过ST04看到全表扫描马上知道问题在哪。最后再唠叨一句别把Open SQL当万能药也别为了追求“正统”而排斥Native SQL。真实项目里偶尔一条性能敏感的复杂报表用Native SQL加注释也可接受但前提是你要明确让后来人知道这条SQL是针对HANA的语法将来迁移数据库可能挂在这。反正我一般能不用就不用毕竟谁都不想半夜被电话震醒。好本次Open SQL概述先聊到这里祝你今天写的每条SQL都命中索引SY-SUBRC永远等于0。