제어 단절 보고서 - 지점별 소계
이 장에서 배우는 것
앞 장까지는 레코드 하나를 읽어 처리하고 한 줄을 출력하는 프로그램을 만들었다. 실무 보고서는 대개 "지점별로 묶어서 합계를 보여 달라"는 요구로 이어진다. 이 장에서는 같은 키를 가진 레코드가 이어지는 동안 값을 모으다가, 키가 바뀌는 순간 소계를 내는 방식을 익힌다. 이 방식을 제어 단절(control break)이라고 부른다. 여기서 키(key)는 레코드를 묶는 기준이 되는 항목이고, 이 장의 예제에서는 지점 코드다.
- 제어 단절이 입력이 정렬되어 있어야 성립하는 이유를 설명할 수 있다.
- 이전 키를 저장해 두고 현재 키와 비교해서 키 변화를 감지할 수 있다.
- 첫 레코드와 마지막 레코드에서 소계가 빠지거나 헛도는 문제를 막을 수 있다.
- 지점별 소계를 총계에 더하고 소계 칸을 비우는 순서를 지킬 수 있다.
- SORT 문으로 입력 파일을 미리 정렬할 수 있다.
문제 상황
신용협동조합의 전산 담당자가 하루치 입출금 거래 파일을 받았다. 파일에는 서울, 부산, 대구 지점에서 들어온 거래가 도착한 순서대로 섞여 있다. 지점장은 "지점별로 거래를 모아서 입금 합계, 출금 합계, 순증감액을 보고 싶다. 마지막에는 전체 합계도 있어야 한다"고 요청한다.
가장 먼저 떠오르는 방법은 지점마다 파일을 한 번씩 읽는 것이다. 지점이 세 개면 세 번, 서른 개면 서른 번 읽어야 하므로 거래가 많아질수록 느려진다. 배치 처리에서는 파일을 한 번만 읽고 끝내는 방식이 표준이다. 그러려면 같은 지점의 거래가 연달아 나와야 하고, 지점이 바뀌는 순간에 소계를 찍어야 한다. 이 장의 프로그램은 이 두 조건을 순서대로 만족시킨다. 먼저 SORT 문으로 거래 파일을 지점 순으로 정렬하고, 정렬된 파일을 한 번 읽으면서 키 변화를 감시한다.
정렬된 입력과 키 변화 감지
왜 정렬이 먼저인가
프로그램은 레코드를 한 건씩 읽을 뿐 앞으로 무엇이 올지 알지 못한다. "지금 읽은 레코드의 지점이 방금 전 레코드의 지점과 다르다"는 사실로만 한 묶음이 끝났다고 판단할 수 있다. 같은 지점의 거래가 떨어져 있으면, 그 지점은 소계가 여러 번 나온다. 서울 거래가 세 곳에 흩어져 있다면 서울 소계 줄이 세 개 생기고, 그중 어느 것도 서울 전체의 합계가 아니다. 그래서 제어 단절 보고서의 입력은 키 순서로 정렬되어 있어야 한다.
이전 키와 현재 키 비교
키 변화를 감지하는 방법은 단순하다. 작업 영역에 "이전 레코드의 키"를 담아 둘 칸을 하나 두고, 새 레코드를 읽을 때마다 현재 키와 비교한다. 같으면 같은 묶음이 계속되는 것이고, 다르면 이전 묶음이 끝난 것이다. 아래 표는 반복문 안에서 마주치는 네 가지 상황을 정리한 것이다.
| 상황 | 판정 | 해야 할 일 | 예제에서 |
|---|---|---|---|
| 첫 레코드 | 비교할 이전 키가 없다 | 소계 없이 키만 저장하고 지점 머리글을 출력한다 | 맨 위의 BUS 거래 |
| 같은 키 | 현재 키와 이전 키가 같다 | 상세 줄을 출력하고 금액을 누적한다 | BUS 의 두 번째 거래 |
| 키 변화 | 현재 키와 이전 키가 다르다 | 이전 묶음의 소계를 출력하고 합산 후 초기화한 뒤 새 묶음을 시작한다 | BUS 에서 DAE 로 넘어갈 때 |
| 파일 끝 | 읽을 레코드가 없다 | 마지막 묶음의 소계와 전체 총계를 출력한다 | 마지막 SEL 거래 뒤 |
여기서 중요한 점은 소계를 출력하는 시점이다. 키 변화는 새 레코드를 읽은 뒤에야 알 수 있다. 이때 작업 영역의 레코드 칸에는 이미 다음 지점의 데이터가 들어 있다. 소계 줄에 지점 코드를 찍고 싶다면 레코드의 키가 아니라 저장해 둔 이전 키를 써야 한다. 또 이전 키는 소계를 출력한 다음에 새 키로 바꾸어야 한다.
첫 레코드와 마지막 레코드
반복문 안에서 키 변화만 검사하면 양 끝에서 문제가 생긴다. 먼저 첫 레코드가 있다. 이전 키 칸이 공백인 상태로 반복문에 들어가면 첫 레코드의 지점은 공백과 다르므로, 존재하지 않는 묶음의 소계가 먼저 출력된다. 이를 막으려고 반복문에 들어가기 전에 레코드를 한 건 미리 읽고, 그 키를 이전 키에 넣어 둔다. 이 "미리 읽기"를 프라이밍 읽기(priming read)라고 부른다.
마지막 레코드는 반대 문제다. 마지막 묶음 뒤에는 다른 키를 가진 레코드가 없으므로 키 변화가 일어나지 않는다. 파일 끝에서 반복문이 끝나면, 마지막 묶음의 소계는 아직 출력되지 않은 채 남아 있다. 그래서 반복문이 끝난 직후에 소계를 한 번 더 출력해야 한다. 입력이 비어 있을 수도 있으므로, 처리한 거래가 한 건이라도 있을 때만 출력한다.
총계는 소계를 모아서 만든다
총계를 만드는 방법은 두 가지다. 거래 한 건마다 소계와 총계에 모두 더하는 방법이 있고, 소계를 출력하는 순간 소계 값을 총계에 더하는 방법이 있다. 이 장은 뒤쪽을 택한다. 덧셈 횟수가 지점 수만큼으로 줄고, 소계를 비우는 자리와 총계에 더하는 자리가 한곳에 모여서 빠뜨리기 어렵다. 순서는 항상 출력, 총계에 합산, 소계 초기화다.
SORT 문으로 미리 정렬하기
COBOL 은 정렬을 언어 안에 갖고 있다. SORT 문은 입력 파일을 읽어 지정한 키 순서로 정렬하고, 결과를 다른 파일에 쓴다. 정렬하는 동안 레코드를 임시로 담아 두는 작업 파일은 FD 가 아니라 SD(sort description)로 선언한다. SD 아래에 레코드 형식을 적고, 정렬 키는 이 레코드 안의 항목 이름으로 지정한다.
| 절 | 역할 | 예제에서 |
|---|---|---|
| SORT 작업파일 | 정렬 작업에 쓰는 SD 파일을 지정한다 | SORT-WORK |
| ON ASCENDING KEY | 오름차순 정렬 키를 왼쪽부터 우선순위 순으로 나열한다 | 지점, 계좌, 구분, 금액 |
| USING | 정렬할 입력 파일을 지정한다. 파일을 열고 닫는 일은 SORT 가 맡는다 | TRANS-FILE |
| GIVING | 정렬 결과를 쓸 출력 파일을 지정한다 | SORTED-FILE |
정렬 키를 지점 코드 하나만 쓰면 같은 지점 안의 거래 순서는 정해지지 않을 수 있다. 소계는 순서와 상관없이 같지만, 상세 줄의 순서가 실행마다 달라지면 보고서를 비교하기 어렵다. 그래서 예제는 지점, 계좌 번호, 구분, 금액 순으로 키를 이어 붙여 결과 순서를 고정했다. 키가 완전히 같은 레코드끼리의 원래 순서까지 지켜야 한다면 SORT 문에 WITH DUPLICATES IN ORDER 절을 쓴다. 정렬 전후에 레코드를 걸러내거나 고치고 싶다면 USING, GIVING 대신 INPUT PROCEDURE, OUTPUT PROCEDURE 를 쓰는 방법도 있다. 절의 정확한 문법은 GnuCOBOL 프로그래머 가이드의 SORT 항목에서 확인할 수 있다.
완성 코드
trans.dat
거래 한 건은 17바이트다. 1~3째 자리는 지점 코드, 4~9째는 계좌 번호, 10째는 구분(D 는 입금, W 는 출금), 11~17째는 금액이다. 일부러 지점이 뒤섞인 순서로 적었다.
SEL100002D0300000
BUS200001D0120000
SEL100001D0500000
DAE300001W0070000
BUS200002W0045000
SEL100001W0150000
BUS200001D0030000
DAE300002D0250000
SEL100002W0100000
main.cob
* CONTROL BREAK REPORT BY BRANCH
IDENTIFICATION DIVISION.
PROGRAM-ID. MAIN.
ENVIRONMENT DIVISION.
INPUT-OUTPUT SECTION.
FILE-CONTROL.
SELECT TRANS-FILE ASSIGN TO "trans.dat"
ORGANIZATION IS LINE SEQUENTIAL.
SELECT SORT-WORK ASSIGN TO "sortwork.tmp".
SELECT SORTED-FILE ASSIGN TO "sorted.dat"
ORGANIZATION IS LINE SEQUENTIAL.
DATA DIVISION.
FILE SECTION.
FD TRANS-FILE.
01 TRANS-REC PIC X(17).
SD SORT-WORK.
01 SORT-REC.
05 SR-BRANCH PIC X(3).
05 SR-ACCT PIC 9(6).
05 SR-TYPE PIC X(1).
05 SR-AMOUNT PIC 9(7).
FD SORTED-FILE.
01 IN-REC.
05 IN-BRANCH PIC X(3).
05 IN-ACCT PIC 9(6).
05 IN-TYPE PIC X(1).
05 IN-AMOUNT PIC 9(7).
WORKING-STORAGE SECTION.
01 WS-EOF-FLAG PIC X VALUE "N".
88 END-OF-INPUT VALUE "Y".
01 WS-PREV-BRANCH PIC X(3) VALUE SPACES.
01 WS-NET PIC S9(9) VALUE 0.
01 WS-BRANCH-TOTALS.
05 WS-BR-COUNT PIC 9(4) VALUE 0.
05 WS-BR-DEPOSIT PIC 9(9) VALUE 0.
05 WS-BR-WITHDRAW PIC 9(9) VALUE 0.
01 WS-GRAND-TOTALS.
05 WS-GR-COUNT PIC 9(4) VALUE 0.
05 WS-GR-DEPOSIT PIC 9(9) VALUE 0.
05 WS-GR-WITHDRAW PIC 9(9) VALUE 0.
01 WS-DASHES PIC X(31) VALUE ALL "-".
01 WS-DETAIL-LINE.
05 FILLER PIC X(2) VALUE SPACES.
05 DL-ACCT PIC 9(6).
05 FILLER PIC X(2) VALUE SPACES.
05 DL-TYPE-NAME PIC X(8).
05 FILLER PIC X(2) VALUE SPACES.
05 DL-AMOUNT PIC ZZZ,ZZZ,ZZ9.
01 WS-SUB-LINE.
05 SL-LABEL PIC X(20).
05 SL-AMOUNT PIC ZZZ,ZZZ,ZZ9.
01 WS-NET-LINE.
05 NL-LABEL PIC X(19).
05 NL-AMOUNT PIC -ZZZ,ZZZ,ZZ9.
PROCEDURE DIVISION.
MAIN-PARA.
SORT SORT-WORK
ON ASCENDING KEY SR-BRANCH SR-ACCT SR-TYPE SR-AMOUNT
USING TRANS-FILE
GIVING SORTED-FILE
OPEN INPUT SORTED-FILE
DISPLAY "DAILY TRANSACTIONS BY BRANCH"
DISPLAY WS-DASHES
PERFORM READ-NEXT
IF NOT END-OF-INPUT
MOVE IN-BRANCH TO WS-PREV-BRANCH
DISPLAY "BRANCH: " IN-BRANCH
END-IF
PERFORM UNTIL END-OF-INPUT
IF IN-BRANCH NOT = WS-PREV-BRANCH
PERFORM BRANCH-BREAK
MOVE IN-BRANCH TO WS-PREV-BRANCH
DISPLAY "BRANCH: " IN-BRANCH
END-IF
PERFORM PROCESS-DETAIL
PERFORM READ-NEXT
END-PERFORM
IF WS-BR-COUNT > 0
PERFORM BRANCH-BREAK
END-IF
PERFORM PRINT-GRAND
CLOSE SORTED-FILE
STOP RUN.
READ-NEXT.
READ SORTED-FILE
AT END SET END-OF-INPUT TO TRUE
END-READ.
PROCESS-DETAIL.
IF IN-TYPE = "D"
MOVE "DEPOSIT" TO DL-TYPE-NAME
ADD IN-AMOUNT TO WS-BR-DEPOSIT
ELSE
MOVE "WITHDRAW" TO DL-TYPE-NAME
ADD IN-AMOUNT TO WS-BR-WITHDRAW
END-IF
ADD 1 TO WS-BR-COUNT
MOVE IN-ACCT TO DL-ACCT
MOVE IN-AMOUNT TO DL-AMOUNT
DISPLAY WS-DETAIL-LINE.
BRANCH-BREAK.
DISPLAY " End of " WS-PREV-BRANCH
MOVE " Subtotal count" TO SL-LABEL
MOVE WS-BR-COUNT TO SL-AMOUNT
DISPLAY WS-SUB-LINE
MOVE " Subtotal deposit" TO SL-LABEL
MOVE WS-BR-DEPOSIT TO SL-AMOUNT
DISPLAY WS-SUB-LINE
MOVE " Subtotal withdraw" TO SL-LABEL
MOVE WS-BR-WITHDRAW TO SL-AMOUNT
DISPLAY WS-SUB-LINE
COMPUTE WS-NET = WS-BR-DEPOSIT - WS-BR-WITHDRAW
MOVE " Subtotal net" TO NL-LABEL
MOVE WS-NET TO NL-AMOUNT
DISPLAY WS-NET-LINE
DISPLAY WS-DASHES
ADD WS-BR-COUNT TO WS-GR-COUNT
ADD WS-BR-DEPOSIT TO WS-GR-DEPOSIT
ADD WS-BR-WITHDRAW TO WS-GR-WITHDRAW
INITIALIZE WS-BRANCH-TOTALS.
PRINT-GRAND.
MOVE "TOTAL count" TO SL-LABEL
MOVE WS-GR-COUNT TO SL-AMOUNT
DISPLAY WS-SUB-LINE
MOVE "TOTAL deposit" TO SL-LABEL
MOVE WS-GR-DEPOSIT TO SL-AMOUNT
DISPLAY WS-SUB-LINE
MOVE "TOTAL withdraw" TO SL-LABEL
MOVE WS-GR-WITHDRAW TO SL-AMOUNT
DISPLAY WS-SUB-LINE
COMPUTE WS-NET = WS-GR-DEPOSIT - WS-GR-WITHDRAW
MOVE "TOTAL net" TO NL-LABEL
MOVE WS-NET TO NL-AMOUNT
DISPLAY WS-NET-LINE.
줄별 해설
파일 선언
SELECT 문이 세 개다. TRANS-FILE 은 정렬 전 입력이고, SORTED-FILE 은 정렬 결과가 담기는 파일이다. 두 파일은 줄 단위로 읽고 쓰는 LINE SEQUENTIAL 이다. SORT-WORK 는 정렬 작업용이어서 ORGANIZATION 절이 없다. 파일 이름 sortwork.tmp 는 형식상 적어 둔 것이고, 실제 작업 공간은 정렬 기능이 알아서 관리한다.
FD TRANS-FILE 의 레코드는 17바이트짜리 문자 하나로만 선언했다. 이 파일은 SORT 가 통째로 읽어 가기 때문에 프로그램이 항목별로 들여다볼 일이 없다. 정렬 키가 되는 항목은 SD 의 SORT-REC 에 있고, 항목 배치가 입력 레코드(1~3째 지점, 4~9째 계좌, 10째 구분, 11~17째 금액)와 정확히 겹쳐야 한다. 정렬된 결과를 읽는 FD SORTED-FILE 의 IN-REC 도 같은 배치로 선언했다. 세 레코드의 길이는 모두 17바이트다.
작업 영역
WS-EOF-FLAG 와 조건 이름 END-OF-INPUT 은 파일 끝 표시다. WS-PREV-BRANCH 가 앞에서 말한 이전 키 칸이다. 소계용 WS-BRANCH-TOTALS 와 총계용 WS-GRAND-TOTALS 는 같은 모양이다. 건수, 입금 합계, 출금 합계를 담는다. 소계를 한 번에 비우려고 세 칸을 한 그룹으로 묶었다. WS-NET 은 입금에서 출금을 뺀 값이므로 음수가 될 수 있어서 부호 있는 S9(9) 로 선언했다.
출력 줄은 세 가지다. WS-DETAIL-LINE 은 상세 줄, WS-SUB-LINE 은 이름표 하나와 금액 하나로 된 합계 줄, WS-NET-LINE 은 부호가 붙을 수 있는 순증감액 줄이다. 금액 칸의 ZZZ,ZZZ,ZZ9 는 앞쪽의 0을 공백으로 바꾸고 세 자리마다 쉼표를 넣는 편집 형식이다. NL-AMOUNT 의 맨 앞 하이픈은 음수일 때만 마이너스 기호가 되고, 양수이면 공백이 된다. 세 줄 모두 오른쪽 끝이 31칸에 맞도록 너비를 정했다.
MAIN-PARA
SORT 문이 가장 먼저 실행된다. 이 한 문장이 TRANS-FILE 을 열어 읽고, 네 개 키로 정렬한 뒤, SORTED-FILE 을 만들어 닫는다. 그래서 프로그램에는 TRANS-FILE 의 OPEN 이나 CLOSE 가 없다. 이어서 정렬 결과를 OPEN INPUT 으로 연다.
READ-NEXT 를 한 번 실행하는 것이 프라이밍 읽기다. 입력이 비어 있지 않으면 첫 레코드의 지점을 이전 키에 넣고 지점 머리글을 출력한다. 입력이 비어 있으면 END-OF-INPUT 이 이미 켜져서 반복문이 한 번도 돌지 않는다.
PERFORM UNTIL 반복의 몸통은 세 단계다. 먼저 현재 키와 이전 키를 비교한다. 다르면 BRANCH-BREAK 로 이전 묶음을 마무리하고, 그다음에 이전 키를 새 키로 갱신하고 새 머리글을 출력한다. 갱신이 소계 출력 뒤에 오는 순서를 눈여겨보라. 그다음 PROCESS-DETAIL 로 현재 레코드를 처리하고, 마지막에 다음 레코드를 읽는다. 읽기가 반복문 맨 끝에 있기 때문에, 파일 끝에서 UNTIL 조건이 참이 되면 처리되지 않은 레코드는 남지 않는다.
반복문 뒤의 IF WS-BR-COUNT > 0 이 마지막 묶음 처리다. 지금 모으는 소계 건수가 0보다 크다는 것은 아직 출력하지 못한 묶음이 있다는 뜻이다. 거래가 한 건도 없으면 건수가 0이어서 소계가 출력되지 않는다. 이후 PRINT-GRAND 가 총계를 찍고 파일을 닫는다.
PROCESS-DETAIL 과 BRANCH-BREAK
PROCESS-DETAIL 은 구분이 D 이면 입금 합계에, 아니면 출금 합계에 금액을 더하고 건수를 하나 올린 뒤 상세 줄을 출력한다. BRANCH-BREAK 는 네 줄의 소계를 출력하고 구분선을 찍는다. 이어서 소계 세 칸을 총계에 더한 다음 INITIALIZE 로 소계 그룹을 0으로 되돌린다. "출력, 합산, 초기화" 순서가 한 문단 안에 있으므로, 소계가 필요한 곳에서는 이 문단 하나만 실행하면 된다. 소계 제목 줄의 WS-PREV-BRANCH 는 이 시점에 아직 이전 지점의 코드를 갖고 있다.
실행 결과
세 파일을 같은 폴더에 두고 컴파일한 뒤 실행한다. 실행하면 같은 폴더에 정렬 결과 sorted.dat 가 생긴다.
$ cobc -x -Wall main.cob
$ ./main
DAILY TRANSACTIONS BY BRANCH
-------------------------------
BRANCH: BUS
200001 DEPOSIT 30,000
200001 DEPOSIT 120,000
200002 WITHDRAW 45,000
End of BUS
Subtotal count 3
Subtotal deposit 150,000
Subtotal withdraw 45,000
Subtotal net 105,000
-------------------------------
BRANCH: DAE
300001 WITHDRAW 70,000
300002 DEPOSIT 250,000
End of DAE
Subtotal count 2
Subtotal deposit 250,000
Subtotal withdraw 70,000
Subtotal net 180,000
-------------------------------
BRANCH: SEL
100001 DEPOSIT 500,000
100001 WITHDRAW 150,000
100002 DEPOSIT 300,000
100002 WITHDRAW 100,000
End of SEL
Subtotal count 4
Subtotal deposit 800,000
Subtotal withdraw 250,000
Subtotal net 550,000
-------------------------------
TOTAL count 9
TOTAL deposit 1,200,000
TOTAL withdraw 365,000
TOTAL net 835,000
지점은 문자 순서(BUS, DAE, SEL)로 나온다. 세 소계의 건수를 더하면 9, 입금은 150,000+250,000+800,000=1,200,000, 출금은 45,000+70,000+250,000=365,000이므로 총계와 일치한다. 총계가 소계의 합과 맞는지 확인하는 것은 제어 단절 프로그램을 검증하는 가장 쉬운 방법이다.
실무에서 자주 틀리는 것
정렬하지 않은 파일을 그대로 읽는다
SORT 를 빼고 trans.dat 를 바로 읽으면 프로그램은 오류 없이 돌아가지만 보고서가 틀린다.
OPEN INPUT TRANS-FILE
READ TRANS-FILE
...
입력 순서가 SEL, BUS, SEL, DAE, BUS, SEL, BUS, DAE, SEL 이므로 지점이 바뀔 때마다 소계가 찍힌다. 결과적으로 SEL 소계가 세 번, BUS 소계가 두 번 나오고 어느 것도 전체 합계가 아니다. 컴파일러와 실행기는 이를 알려 주지 않는다. 고친 코드는 이 장의 방식 그대로, 읽기 전에 SORT 를 실행하고 그 결과 파일을 읽는 것이다.
SORT SORT-WORK
ON ASCENDING KEY SR-BRANCH SR-ACCT SR-TYPE SR-AMOUNT
USING TRANS-FILE
GIVING SORTED-FILE
OPEN INPUT SORTED-FILE
마지막 묶음의 소계를 빠뜨린다
반복문 안에서만 소계를 출력하면 마지막 지점이 사라진다.
PERFORM UNTIL END-OF-INPUT
...
END-PERFORM
PERFORM PRINT-GRAND
마지막 SEL 묶음 뒤에는 키가 바뀌는 레코드가 없다. 그 결과 SEL 소계가 출력되지 않고, 총계에도 SEL 의 금액이 들어가지 않는다. 총계가 보고서에 보이는 소계들의 합과 맞지 않으면 이 오류를 의심하라. 고치려면 반복문이 끝난 직후에 한 번 더 소계를 처리한다.
END-PERFORM
IF WS-BR-COUNT > 0
PERFORM BRANCH-BREAK
END-IF
PERFORM PRINT-GRAND
첫 레코드에서 빈 소계가 나온다
프라이밍 읽기 없이 이전 키를 공백으로 둔 채 반복문에 들어가는 경우다.
MOVE SPACES TO WS-PREV-BRANCH
PERFORM UNTIL END-OF-INPUT
READ SORTED-FILE
AT END SET END-OF-INPUT TO TRUE
END-READ
IF IN-BRANCH NOT = WS-PREV-BRANCH
PERFORM BRANCH-BREAK
첫 레코드의 지점은 공백이 아니므로 키 변화로 판정되어, 지점 이름이 빈 건수 0짜리 소계가 맨 앞에 출력된다. 또 이 모양은 파일 끝에서 마지막 READ 가 실패한 뒤에도 몸통이 한 번 더 실행되는 문제를 안고 있다. 고친 코드는 반복문 앞에서 한 건을 읽고 이전 키에 저장하는 것이다.
PERFORM READ-NEXT
IF NOT END-OF-INPUT
MOVE IN-BRANCH TO WS-PREV-BRANCH
DISPLAY "BRANCH: " IN-BRANCH
END-IF
소계 칸을 비우지 않는다
BRANCH-BREAK 에서 총계에 더하기만 하고 초기화를 빠뜨린 경우다.
ADD WS-BR-COUNT TO WS-GR-COUNT
ADD WS-BR-DEPOSIT TO WS-GR-DEPOSIT
ADD WS-BR-WITHDRAW TO WS-GR-WITHDRAW
* (INITIALIZE 가 없다)
이러면 BUS 의 건수 3이 그대로 남아 DAE 소계는 5건, SEL 소계는 9건으로 출력된다. 총계도 이중으로 더해져서 부풀려진다. 첫 번째 소계는 맞게 나오기 때문에 눈에 잘 띄지 않는다. 합산 뒤에 소계 그룹을 비우는 문장을 같은 문단에 두면 이 실수를 막을 수 있다.
ADD WS-BR-WITHDRAW TO WS-GR-WITHDRAW
INITIALIZE WS-BRANCH-TOTALS.
한눈에 보기
| 요소 | 방법 | 기억할 점 |
|---|---|---|
| 정렬 | SORT ... ON ASCENDING KEY ... USING ... GIVING ... | 입력이 키 순서가 아니면 소계가 중복된다 |
| 이전 키 | 작업 영역에 한 칸을 두고 새 키 처리 뒤에 갱신한다 | 소계 출력이 먼저이고 갱신이 나중이다 |
| 첫 레코드 | 반복문 앞의 프라이밍 읽기 | 이전 키를 첫 레코드로 채운다 |
| 키 변화 | 현재 키와 이전 키 비교 | 출력, 합산, 초기화의 순서를 한 문단에 둔다 |
| 마지막 레코드 | 반복문 뒤에서 소계를 한 번 더 처리 | 미처리 건수가 0이면 건너뛴다 |
| 총계 | 소계를 출력할 때 총계에 더한다 | 총계는 소계 합과 반드시 맞아야 한다 |
이 장의 BRANCH-BREAK 처럼 한 가지 일을 맡은 문단은 다른 프로그램에서도 쓰고 싶어진다. 레코드 형식 선언도 여러 프로그램이 똑같이 반복해서 적게 된다. 다음 장에서는 이런 코드를 서브프로그램과 카피북으로 나누는 방법을 다룬다.
연습 문제
- SORT 문의 첫 키 SR-BRANCH 앞의 ASCENDING 을 DESCENDING 으로 바꾸면 보고서에서 지점은 어떤 순서로 나오는가. 소계와 총계의 값은 달라지는가.
- trans.dat 가 내용 없는 빈 파일이면 이 프로그램은 무엇을 출력하는가. 어느 문장 때문에 그렇게 되는지 설명하라.
- 지점 소계에 입금 건수와 출금 건수를 따로 보여 주려면 작업 영역과 문단을 어떻게 고쳐야 하는가. 고칠 위치를 모두 적어라.
- 지점별로 가장 큰 거래 금액을 소계 뒤에 한 줄 더 출력하려 한다. 최대값을 담을 칸을 어디에서 갱신하고, 어디에서 초기화해야 하는가.
정답과 해설
- 지점은 SEL, DAE, BUS 순으로 나온다. 다른 세 키(계좌, 구분, 금액)는 오름차순이 그대로여서 지점 안의 순서는 변하지 않는다. 각 지점의 소계와 총계의 값은 그대로다. 정렬은 묶음의 순서만 바꾸고 묶음 안의 내용은 바꾸지 않기 때문이다.
- 머리 두 줄과 총계 네 줄만 출력된다. 프라이밍 읽기에서 곧바로 파일 끝이 되어 END-OF-INPUT 이 켜지므로 첫 IF 의 몸통과 PERFORM UNTIL 반복이 실행되지 않는다. 반복문 뒤의 IF WS-BR-COUNT > 0 은 건수가 0이라 거짓이어서 소계가 출력되지 않는다. 총계의 건수, 입금, 출금은 0으로, 순증감액도 0으로 출력된다.
- WS-BRANCH-TOTALS 에 입금 건수와 출금 건수 칸 두 개를 추가한다. PROCESS-DETAIL 의 D 쪽과 ELSE 쪽에서 각각 하나씩 올린다. BRANCH-BREAK 에서 두 줄을 출력하고 총계 칸에 더하며, INITIALIZE 가 새 칸도 비우게 한다. 총계 그룹과 PRINT-GRAND 도 같은 방식으로 고친다.
- WS-BRANCH-TOTALS 그룹 안에 최대값 칸을 하나 넣는다. PROCESS-DETAIL 에서 IF IN-AMOUNT > 최대값 칸 이면 그 칸에 IN-AMOUNT 를 옮겨서 갱신한다. 초기화는 BRANCH-BREAK 끝의 INITIALIZE 가 맡는다. 최대값 칸이 그룹 안에 있으면 소계와 함께 0으로 돌아가므로 다음 지점의 값이 섞이지 않는다. 출력은 소계를 비우기 전에 해야 한다.