FOR ALL ENTRIES IN @itab로 읽는 SELECT는 드라이버 테이블 itab이 비어 있으면 WHERE 조건 전체를 버립니다. 드라이버 테이블과 비교하는 조건만이 아니라, 같은 WHERE에 적은 날짜 조건과 상태 조건까지 함께 사라집니다. 이 글을 읽고 나면 코드에서 이 구멍이 열려 있는 자리를 찾고, 가드를 어디에 두고 가드가 막았을 때 결과 테이블을 어떻게 둘지 정할 수 있습니다. 테이블에 행이 있어도 결과가 줄어드는 두 번째 함정도 함께 봅니다.
예시는 SAP 키워드 문서의 예제에 나오는 항공편 테이블 SPFLI와 SFLIGHT에 가상 값을 넣은 것입니다. 항공사 코드 ZA와 ZB, 입력값 ZZZ, 날짜와 금액은 모두 지어낸 값이고, 코드는 실제 시스템에서 실행하지 않았습니다. 동작의 근거는 ABAP 키워드 문서의 SELECT, FOR ALL ENTRIES(최신판, AS ABAP 816)와 7.50판의 같은 항목이며, 두 페이지의 본문은 영어입니다.
빈 테이블을 넘기면 무엇이 무시될까
정상 경로부터 보겠습니다. 드라이버 테이블 lt_conn에 ZA/0100과 ZB/0200 두 행이 있다고 합시다. FOR ALL ENTRIES는 WHERE의 논리식 전체를 행마다 한 번씩 평가하고, 결과 집합은 그 평가 결과들의 합집합입니다. carrid·connid 비교와 함께 쓴 fldate >= @p_from도 행마다 적용되므로, 두 노선의 기준일 이후 항공편만 돌아옵니다.
이제 사용자가 가상 출발 도시 ZZZ를 입력해 첫 번째 SELECT가 아무 노선도 찾지 못했다고 합시다. lt_conn은 비어 있습니다. 문서는 이때 WHERE 조건 전체가 무시된다고 경고합니다. 어떤 행도 걸러지지 않고, 중복 행을 지운 뒤 테이블의 행이 모두 결과에 들어갑니다. fldate 조건은 드라이버 테이블과 관계없는 조건인데도 같은 WHERE 안에 있으므로 함께 빠집니다. "조건이 하나 더 있으니 괜찮겠지"라는 기대가 여기서 무너집니다.
사라지지 않는 것도 있습니다. 암시적 클라이언트 처리가 켜져 있으면 현재 클라이언트(또는 USING CLIENT로 지정한 클라이언트) 조건은 남아서, 읽는 범위는 그 클라이언트의 데이터입니다. 폐기된 추가 구문 CLIENT SPECIFIED로 클라이언트 처리를 끄고 WHERE에 클라이언트 열 조건을 직접 적었다면 이야기가 다릅니다. 그 조건도 WHERE의 일부라서 함께 무시되고, 모든 클라이언트의 데이터를 읽습니다.
테이블에 행이 있어도 결과가 줄어드는 경우
FOR ALL ENTRIES는 결과에서 두 번 이상 나온 행을 자동으로 지웁니다. 비교 기준은 읽은 행의 내용 전체입니다. 문서는 이 효과가 선택 집합에 DISTINCT를 쓴 것과 같다고 설명합니다. 드라이버 테이블에 같은 노선이 두 번 들어 있어도 결과가 두 배가 되지 않는 이유가 이것입니다.
문제는 합계를 낼 때입니다. 가상 SFLIGHT에 ZA/0100 노선이 9월 1일 500, 9월 8일 500, 9월 15일 650으로 세 행 있다고 합시다. SELECT 목록에서 fldate를 빼고 carrid, connid, price만 읽으면 앞의 두 행이 완전히 같아지고 하나만 남습니다. 결과는 두 행, 금액 합계는 1,650이 아니라 1,150입니다. 행을 구별하는 키 열을 모두 읽으면 세 행이 그대로 남습니다.
중복 제거가 어디서 일어나는지도 정해져 있지 않습니다. 문장을 하나의 SQL 문으로 데이터베이스에 넘길 수 있으면 데이터베이스가 지우고, 여러 SQL 문으로 나눠야 하면 AS ABAP에서 모아서 지웁니다. 후자에서 PACKAGE SIZE, UP TO, OFFSET을 함께 쓰면 조건에 맞는 행을 먼저 내부 시스템 테이블에 모두 받은 뒤에 이 추가 구문이 적용되므로, 데이터베이스에서 넘어오는 행 수는 줄지 않습니다. 그 시스템 테이블이 최대 크기를 넘으면 런타임 오류가 납니다. 빈 드라이버 테이블과 겹치면 전체 테이블을 이 경로로 받게 될 수 있습니다.
가드는 어디에 두고, 막았을 때는 무엇을 할까
문서의 권고는 짧습니다. FOR ALL ENTRIES 뒤에 쓰기 전에 드라이버 테이블이 초기값이 아닌지 확인하라는 것입니다. 실무에서 가드가 있는데도 사고가 나는 경우는 대개 위치 문제입니다. 가드와 SELECT 사이에 DELETE나 필터가 끼어 있으면, 가드를 통과한 뒤에 테이블이 비워질 수 있습니다. 가드는 SELECT 바로 앞, 드라이버 테이블을 마지막으로 바꾼 뒤에 둡니다.
가드가 SELECT를 건너뛰면 INTO TABLE이 실행되지 않으므로 결과 테이블은 이전 내용을 그대로 갖습니다. 루프 안에서 같은 결과 테이블을 다시 쓰는 코드라면 앞 회차의 행이 남습니다. 비울지 말지는 ELSE에서 직접 정합니다. 아래 예시 화면의 번호가 이 순서를 따라갑니다.
문서에는 알아 두면 좋은 동작이 두 가지 더 있습니다. 드라이버 테이블은 쿼리마다 한 번만 평가되므로 SELECT 루프 안에서 그 내용을 바꿔도 조건에 반영되지 않습니다. 같은 내부 테이블을 FOR ALL ENTRIES와 INTO에 동시에 쓸 수도 있는데, 먼저 조건으로 평가된 뒤 결과로 덮어써집니다. 문서의 예제는 같은 선택을 FROM 절의 조인 하나로도 할 수 있다고 덧붙입니다. 열 형식이 허용하면 DISTINCT를 함께 적어 중복 제거를 코드에 드러낼 수 있습니다.
릴리스에 따라 달라지는 부분
빈 테이블이면 WHERE 전체가 무시된다는 문장은 7.50판 문서와 최신판에 똑같이 있습니다. SINGLE과 함께 쓸 수 없고, ORDER BY는 PRIMARY KEY와 단일 테이블·뷰에서만 쓸 수 있다는 제한도 같습니다.
달라진 부분도 있습니다. 7.50판은 FOR ALL ENTRIES를 SQL 식과 결합할 수 없다고 적고, SELECT 목록의 STRING·RAWSTRING·LCHR·LRAW 열은 7.40 SP05부터 엄격 모드에서 구문 오류라고 설명합니다. 최신판(816)은 개별 열과 단독 COUNT( * )를 SQL 식 제한의 예외로 두고, 이 긴 형식 열은 쓰지 않는 편이 좋다며 구문 검사 경고로 다루고, 이 조합을 엄격 모드로 검사하는 시점을 7.68로 적습니다. UNION과 함께 쓸 수 없다는 제한에 INTERSECT와 EXCEPT가 더해졌습니다. 자기 시스템의 릴리스에 맞는 판을 확인하는 것이 안전합니다.
코드에서 확인할 네 가지
이미 있는 프로그램을 점검한다면 FOR ALL ENTRIES가 나오는 자리마다 네 가지를 봅니다. 첫째, 드라이버 테이블이 비었는지 확인하는 가드가 SELECT 바로 앞에 있는가. 둘째, 가드가 막았을 때 결과 테이블을 비울지 유지할지 코드에 적혀 있는가. 셋째, 합계나 건수를 낼 열이 있다면 행을 구별하는 키 열도 SELECT 목록에 있는가. 넷째, CLIENT SPECIFIED를 쓰는가. 네 번째가 "예"라면 빈 드라이버 테이블은 한 클라이언트가 아니라 모든 클라이언트를 읽게 만듭니다.
하나만 기억한다면 이것입니다. FOR ALL ENTRIES의 WHERE는 드라이버 테이블이 있을 때만 존재합니다. 테이블이 비면 조건을 몇 개 적었든 없는 것과 같습니다.
이 기록에 대화를 더해 주세요.
궁금한 점, 다른 접근, 직접 해 본 결과를 나눠 주세요.
로그인 상태 확인 중…
댓글을 불러오는 중…