3. ABAP数据库操作与Open SQL:SELECT、INSERT、UPDATE、MODIFY语句详解、FOR ALL ENTRIES 用法与性能陷阱、JOIN与子查询
好,咱们直接进入正题。这一章是ABAP开发里最核心、最绕不开的内容——数据库操作。说白了,你写的每一行业务代码,最终都要跟数据库打交道。我见过太多新手,甚至工作两三年的同事,在Open SQL上栽跟头。今天我就把那些坑和经验,一次性给你讲透。
3.1 四大金刚:SELECT、INSERT、UPDATE、MODIFY
这四个语句,是ABAP操作数据库的基石。咱们一个一个来。
3.1.1 SELECT —— 查询的艺术
SELECT 是用的最多的。我个人习惯,能用单条查询解决的,绝不用循环。先看个最基础的:
SELECT * FROM mara INTO wa_mara
WHERE matnr = '100-100'.
嗯,这里要注意。SELECT * 在开发环境里很方便,但上了生产系统,尤其是数据量大的表,千万别这么干。为什么?因为数据库要传输所有字段,网络开销大,内存占用也大。我建议你只选需要的字段:
SELECT matnr mtart matkl
FROM mara INTO CORRESPONDING FIELDS OF wa_mara
WHERE matnr = '100-100'.
用 INTO CORRESPONDING FIELDS,结构体里只匹配同名字段,干净利落。
SELECT SINGLE。它会在找到第一条记录后立即停止,性能比 SELECT ... ENDSELECT 好得多。
3.1.2 INSERT —— 插入数据
INSERT 就是把数据写进数据库。但有个细节——它不会覆盖已有记录。如果主键重复,直接报错。我在项目中遇到过,批量导入数据时,因为没检查重复,导致程序dump。后来我养成了习惯:插入前先查一下。
INSERT INTO mara FROM wa_mara.
IF sy-subrc = 0.
WRITE: '插入成功'.
ELSE.
WRITE: '插入失败,可能主键重复'.
ENDIF.
检查 sy-subrc 是必须的。0表示成功,4表示记录已存在。
3.1.3 UPDATE —— 更新数据
UPDATE 用来修改已有记录。它只更新你指定的字段,其他字段保持不变。举个例子:
UPDATE mara SET mtart = 'FERT'
WHERE matnr = '100-100'.
如果你用 UPDATE mara FROM wa_mara,它会用工作区里的所有非空字段去更新。这里有个坑——如果工作区里某个字段是初始值,它也会被更新成空。我曾经因为这个,把一批物料的描述全清空了……嗯,从那以后,我更新数据一定用 SET 方式,明确指定要改的字段。
3.1.4 MODIFY —— 插入或更新
MODIFY 是 INSERT 和 UPDATE 的结合体。如果记录不存在,就插入;如果存在,就覆盖。听起来很方便,对吧?但性能上要注意:MODIFY 内部会先做一次 SELECT 判断,所以比单纯的 INSERT 或 UPDATE 要慢。
MODIFY mara FROM wa_mara.
我个人建议:如果你确定记录不存在,用 INSERT;确定存在,用 UPDATE;不确定,才用 MODIFY。别图省事一把梭。
3.2 FOR ALL ENTRIES —— 好用但有毒
FOR ALL ENTRIES(简称FAE)是ABAP里一个非常强大的语法。它允许你用内表作为查询条件,从数据库里批量取数据。比如:
SELECT matnr maktx
FROM makt INTO TABLE lt_makt
FOR ALL ENTRIES IN lt_mara
WHERE matnr = lt_mara-matnr.
你看,一行代码就把所有物料的描述查出来了。但这里藏着几个大坑。
所以,用 FAE 之前,一定要检查内表是否为空:
IF lt_mara[] IS NOT INITIAL.
SELECT ... FOR ALL ENTRIES IN lt_mara ...
ENDIF.
还有第二个坑:FAE 会自动去重。如果 lt_mara 里有重复的 matnr,它只会查一次。这有时候是好事,但有时候会漏数据。你想想看,如果你要查的是订单行项目,每个订单有多个行项目,FAE 去重后可能只返回一条。
第三个坑:FAE 生成的 SQL 语句会被拆分成多个 OR 条件。如果内表数据量太大(比如超过1000条),生成的 SQL 会非常长,数据库解析起来很慢。我建议,内表超过500条时,考虑分批查询。
3.3 JOIN与子查询 —— 多表联查的正确姿势
很多时候,你需要从多个表里取数据。这时候 JOIN 和子查询就派上用场了。
3.3.1 JOIN —— 表连接
ABAP 支持 INNER JOIN、LEFT OUTER JOIN、RIGHT OUTER JOIN。最常用的是 INNER JOIN:
SELECT a~matnr a~mtart b~maktx
FROM mara AS a
INNER JOIN makt AS b ON a~matnr = b~matnr
INTO TABLE lt_result
WHERE a~matnr IN s_matnr.
这里要注意:JOIN 的条件字段,最好都有索引。我在项目中遇到过,两个表 JOIN 时,因为关联字段没建索引,查询跑了30秒。加上索引后,0.5秒就出来了。
LEFT OUTER JOIN 也很常用。比如你要查所有物料,即使没有描述也要显示:
SELECT a~matnr a~mtart b~maktx
FROM mara AS a
LEFT OUTER JOIN makt AS b ON a~matnr = b~matnr AND b~spras = sy-langu
INTO TABLE lt_result.
嗯,这里有个细节:LEFT OUTER JOIN 的 ON 条件里,可以加额外的筛选条件(比如语言)。但要注意,这些条件只对右表生效,不会影响左表的数据量。
3.3.2 子查询 —— 嵌套查询
子查询就是查询里套查询。ABAP 支持在 WHERE 条件里用子查询:
SELECT matnr mtart
FROM mara
INTO TABLE lt_mara
WHERE matnr IN (SELECT matnr FROM makt WHERE spras = '1').
这个例子查的是:所有在 MAKT 表里有中文描述的物料。子查询的好处是,它只返回一个结果集给外层查询,逻辑清晰。
但子查询的性能,有时候不如 JOIN。为什么?因为子查询会先执行内层查询,再执行外层查询。如果内层查询结果很大,内存消耗就大。我建议:能用 JOIN 的,尽量用 JOIN;子查询适合那种「存在性判断」的场景。
3.4 避坑指南
- SELECT * 的代价: 我曾经接手一个程序,里面全是 SELECT *,每次跑要10分钟。改成只选必要字段后,3分钟就跑完了。
- FOR ALL ENTRIES 的空表问题: 前面说了,一定要加
IF ... IS NOT INITIAL检查。 - UPDATE 的字段覆盖: 用
FROM wa更新时,确保工作区里只有你想改的字段有值,其他字段清空或保持原值。 - JOIN 的索引: 关联字段和 WHERE 条件字段,一定要有索引。没有索引的 JOIN,就是灾难。
- 子查询的嵌套深度: 不要超过两层。三层以上的子查询,可读性差,性能也差。
好了,这一章的内容就这些。数据库操作是ABAP开发的基本功,也是最能体现功力的地方。你把这些语法和陷阱都吃透了,写出来的程序自然又快又稳。下一章,咱们聊聊内表操作和性能优化,那又是另一片天地。