ABAP / FIELD NOTES

READ TABLE에 BINARY SEARCH를 붙였더니 있는 행을 왜 못 찾을까

BINARY SEARCH는 테이블이 검색 키 순서로 오름차순 정렬되어 있다고 믿고 절반씩 버립니다. 정렬하지 않았거나 정렬 순서가 다를 때 있는 행을 놓치는 이유, 못 찾았을 때 sy-subrc 4와 8, sy-tabix를 쓰는 법, 정렬 보조 키라는 대안을 가상 예로 봅니다.

정렬해 둔 줄 알았던 표준 테이블에서 READ TABLE ... BINARY SEARCH로 공급업체를 찾았는데, 분명히 들어 있는 행을 "없음"으로 판정하는 일이 있습니다. 덤프도 구문 오류도 없이 결과만 틀립니다. 이 글을 읽고 나면 이진 검색이 기대하는 정렬 조건 세 가지를 코드에서 확인하고, 못 찾았을 때 sy-subrc와 sy-tabix를 바르게 쓰고, 언제 정렬 보조 키로 바꿀지 판단할 수 있습니다.

예시는 가상 구조 ty_vendor(공급업체 lifnr, 이름 name)를 행으로 하는 표준 테이블 lt_vendors입니다. 다섯 행이 넣은 순서대로 V-0040, V-0010, V-0050, V-0020, V-0030이고, 여기서 V-0020을 찾습니다. 공급업체 값과 프로그램은 모두 지어낸 것이고 코드는 실제 시스템에서 실행하지 않았습니다. 동작의 근거는 ABAP 키워드 문서의 READ TABLE, free_key와 READ TABLE itab(7.58판, 7.50판)이며, 문서 본문은 영어입니다.

이진 검색은 정렬을 믿고 절반을 버린다

BINARY SEARCH를 붙이지 않으면 표준 테이블은 첫 행부터 차례로 찾습니다. 느릴 수는 있어도 들어 있는 행은 반드시 찾습니다. BINARY SEARCH를 붙이면 테이블을 이진 검색합니다. 가운데 행과 비교해 찾는 값이 작으면 앞쪽 절반만, 크면 뒤쪽 절반만 남기는 방식이라, 행이 대략 100개를 넘으면 실행 시간을 크게 줄일 수 있습니다. 대신 테이블이 검색 키의 열 기준 오름차순으로 정렬되어 있어야 하고, 그렇지 않으면 보통 올바른 행을 찾지 못합니다.

가상 예를 교과서식 절반 나누기로 따라가 보면 이렇습니다. 3행 V-0050보다 V-0020이 작으니 앞쪽 1~2행만 남기고, 1행 V-0040보다도 작으니 더 볼 곳이 없습니다. V-0020은 버린 쪽인 4행에 있습니다. 커널이 실제로 어느 행부터 비교하는지는 문서에 나와 있지 않으므로 이 경로는 설명용입니다. 문서가 보장하는 것은 "정렬이 맞지 않으면 결과를 믿을 수 없다"는 것뿐입니다. 먼저 SORT lt_vendors BY lifnr.로 정렬하면 V-0020은 2행에 오고, 찾으면 sy-subrc는 0, sy-tabix는 찾은 행 번호 2입니다.

흐름 도식. 가상 공급업체 다섯 행이 V-0040, V-0010, V-0050, V-0020, V-0030 순서일 때 V-0020을 교과서식 이진 검색으로 찾으면 3행 V-0050, 1행 V-0040을 거쳐 더 볼 곳이 없어 4행에 있는 V-0020을 지나친다. SORT BY lifnr로 V-0010부터 V-0050까지 정렬한 뒤에는 3행 V-0030, 1행 V-0010, 2행 V-0020 순으로 비교해 찾고 sy-subrc 0, sy-tabix 2가 된다.
이진 검색은 가운데 행과 비교해 절반을 버립니다. 정렬되지 않은 테이블에서는 버린 절반에 찾는 행이 있을 수 있습니다.

표준 테이블에서는 이 실수를 런타임이 막아 주지 않는다는 점이 중요합니다. 문서의 예외 목록에 있는 ITAB_ILLEGAL_BINARY_SEARCH는 정렬 테이블에서 검색 키가 테이블 키의 앞부분이 아닐 때의 오류입니다. 표준 테이블이 정렬되지 않았다는 이유의 예외는 목록에 없으므로, 틀린 결과는 조용히 다음 로직으로 넘어갑니다.

SORT가 있어도 틀리는 경우

코드에 SORT가 있다고 안심하기 전에 세 가지를 봅니다. 첫째, 정렬 우선순위가 검색 키에 적은 열 순서와 정확히 같아야 합니다. SORT ... BY werks matnr로 정렬하고 WITH KEY matnr = ... werks = ...로 찾으면 조건이 맞지 않습니다. 둘째, 오름차순이어야 합니다. DESCENDING으로 정렬한 테이블은 요건을 벗어납니다. 이진 검색은 크기에 따른 기본 정렬, 곧 문자열이라면 이진 표현 기준 비교를 전제하므로 AS TEXT로 로캘 기준 텍스트 정렬을 한 테이블에서는 예상하지 못한 결과가 나올 수 있습니다. 셋째, 정렬 상태가 READ 직전까지 유지되어야 합니다. 정렬한 다음 APPEND로 행을 끝에 붙이면 그 행은 정렬 위치에 있지 않습니다.

세 카드. 첫째, SORT BY werks matnr 뒤 READ WITH KEY matnr werks로 찾으면 정렬 우선순위가 검색 키 순서와 달라 틀린다. 둘째, SORT DESCENDING은 오름차순이 아니고 SORT AS TEXT는 로캘 기준 텍스트 정렬이라 이진 표현으로 비교하는 BINARY SEARCH와 맞지 않는다. 셋째, SORT 다음 APPEND로 끝에 붙인 행은 정렬 위치에 있지 않으므로 READ 직전의 정렬 상태가 기준이다.
SORT 문이 있다는 것만으로는 부족합니다. 검색 키와 같은 순서, 오름차순, 기본 이진 비교로 정렬되어 READ까지 그 상태가 유지되어야 합니다.

테이블 종류에 따라 규칙도 다릅니다. 정렬 테이블에는 검색 키가 기본 키의 앞부분이거나 키를 포함할 때만 BINARY SEARCH를 적을 수 있고, 이때는 따로 효과가 없습니다. 이미 키 순서로 이진 검색하기 때문입니다. 해시 테이블에는 BINARY SEARCH를 쓸 수 없습니다.

못 찾았을 때의 sy-subrc와 sy-tabix

정렬이 맞는 테이블이라면 결과 코드가 쓸모 있는 정보를 줍니다. 정렬된 가상 테이블 V-0010~V-0050에서 V-0020을 찾으면 sy-subrc 0, sy-tabix 2입니다. 같은 키가 여러 행이면 행 번호가 가장 작은 행을 읽습니다. V-0025처럼 없는 값을 찾으면 sy-subrc는 4이고, sy-tabix는 정렬을 지키며 INSERT ... INDEX로 넣을 자리인 3입니다. V-0060처럼 마지막 행보다 큰 값을 찾아 끝에 닿으면 sy-subrc는 8이고 sy-tabix는 행 수에 1을 더한 6입니다. 이진 검색이 아닌 읽기에서 못 찾으면 sy-tabix는 정해지지 않습니다.

"없으면 정렬 위치에 넣는다"는 흔한 패턴에서 여기가 함정입니다. IF sy-subrc = 4.로만 검사하면 테이블 끝 뒤에 와야 하는 값(8)을 놓칩니다. INSERT ... INDEX는 행 수에 1을 더한 값을 받으면 마지막 행으로 붙이므로, IF sy-subrc <> 0.으로 검사하고 sy-tabix 위치에 넣으면 두 경우가 모두 처리됩니다.

세 카드. 정렬된 가상 테이블 V-0010부터 V-0050까지 다섯 행에서 BINARY SEARCH로 V-0020을 찾으면 sy-subrc 0, sy-tabix 2이고 중복 키라면 가장 앞 행을 읽는다. V-0025를 찾으면 sy-subrc 4, sy-tabix 3으로 정렬을 지키며 INSERT INDEX sy-tabix로 넣을 자리를 알려 준다. V-0060을 찾으면 끝을 지나 sy-subrc 8, sy-tabix 6(행 수 + 1)이며 IF sy-subrc = 4만 검사하면 이 경우를 놓친다.
정렬이 맞으면 못 찾은 경우에도 sy-tabix가 넣을 자리를 알려 줍니다. 끝을 지난 경우는 sy-subrc 8이므로 0이 아닌지로 검사합니다.
SAP GUI ABAP 편집기 화면. 키 없이 선언한 표준 테이블에서 SORT 없이 BINARY SEARCH로 읽는 코드, SORT BY lifnr 뒤 BINARY SEARCH로 읽고 sy-subrc가 4일 때 sy-tabix 위치에 INSERT하는 코드, 정렬 보조 키 by_lifnr를 선언해 WITH KEY by_lifnr COMPONENTS로 읽는 코드에 번호 네 개가 표시되어 있다.
BINARY SEARCH가 기대와 다르게 움직이는 자리와 대안을 한 화면에 모은 예시. ZDEMO_BINARY_SEARCH는 가상 프로그램이며 코드는 실행하지 않았다.

위 화면의 번호를 코드 점검 순서로 읽으면 이렇습니다. 1번은 정렬 없이 BINARY SEARCH를 붙인 읽기로, 있는 행을 못 찾을 수 있습니다. 2번은 검색 키와 같은 열로 오름차순 정렬한 자리입니다. 3번은 4만 검사해 끝 뒤 삽입을 놓치는 조건입니다. 4번은 정렬 보조 키를 선언해 SORT 없이 이진 검색하는 대안입니다.

대안과 릴리스 차이

문서는 BINARY SEARCH 대신 정렬 테이블이나 정렬 보조 키를 쓰라고 권합니다. 표준 테이블에 WITH UNIQUE SORTED KEY by_lifnr COMPONENTS lifnr처럼 정렬 보조 키를 두고 WITH KEY by_lifnr COMPONENTS lifnr = ...로 읽으면 보조 인덱스를 자동으로 이진 검색하므로 SORT가 필요 없고, 행을 넣어도 정렬이 키와 함께 유지됩니다. 이때 sy-tabix는 보조 인덱스의 행 번호이므로, 그 값을 이어지는 인덱스 작업에 쓸 때는 같은 키를 지정해야 합니다. 맞는 보조 키가 있는데 쓰지 않으면 구문 검사가 경고합니다.

이 글에서 다룬 규칙, 곧 오름차순과 정렬 우선순위 요건, 정렬 테이블과 해시 테이블에서의 제한, AS TEXT 주의, 여러 행이 맞을 때 가장 앞 행을 읽는 동작, sy-subrc 4와 8, sy-tabix의 삽입 위치는 7.50판과 7.58판 문서에 같은 내용으로 있습니다. 7.58판에는 필요한 정렬이 되어 있는지 확인하는 메서드로 CL_ABAP_ITAB_UTILITIES=>READ_BINARY_SEARCH_CHECK를 안내하는 힌트가 있고, 7.50판의 같은 절에는 이 힌트가 없습니다. 자기 시스템 릴리스의 문서에서 이 클래스가 있는지 확인한 뒤 쓰세요.

코드에서 확인할 것

기존 프로그램에서 BINARY SEARCH를 찾았다면 세 가지를 봅니다. 바로 앞에 검색 키와 같은 열, 같은 순서, 오름차순의 SORT가 있는가. 그 SORT와 READ 사이에 APPEND나 다른 정렬이 끼어들지 않는가. 못 찾았을 때 sy-subrc <> 0으로 검사하는가. 새로 쓰는 코드라면 정렬 보조 키나 정렬 테이블로 정렬 책임을 테이블 선언에 맡기는 편이 안전합니다.

하나만 기억한다면 이것입니다. BINARY SEARCH는 "이 테이블은 지금 검색 키 순서로 정렬되어 있다"는 약속이고, 그 약속을 검사해 주는 것은 없습니다.

END OF NOTE목록으로
COMMENTS BOX

이 기록에 대화를 더해 주세요.

궁금한 점, 다른 접근, 직접 해 본 결과를 나눠 주세요.

최신순

로그인 상태 확인 중…

댓글을 불러오는 중…