- Tác giả

- Name
- Nguyễn Đức Xinh
- Ngày xuất bản
- Ngày xuất bản
WORKING-STORAGE nâng cao trong COBOL – Hiểu rõ OCCURS và REDEFINES qua ví dụ thực tế
Vì sao phải học WORKING-STORAGE nâng cao (OCCURS & REDEFINES)?
Ở các bài trước, bạn đã:
- Bài 002: hiểu level number, PIC, group item, numeric/string
- Bài 006: quen với PERFORM VARYING – vòng lặp có biến đếm
- Bài 007: làm việc với FILE SECTION và record
01để đọc file tuần tự
Trong chương trình batch thực tế như RZZBSQLN1.PCO, WORKING-STORAGE không chỉ có vài biến lẻ 9(4) hay X(20) mà thường chứa:
- Table (mảng 1 chiều) để lưu nhiều record tạm thời – dùng
OCCURS - Struct "2 mặt" cho cùng một vùng nhớ – dùng
REDEFINESđể chia nhỏ date/time, phân tích record phức tạp, đổi format dữ liệu
Bài này tập trung vào hai công cụ rất mạnh (và cũng dễ gây bug nếu không hiểu kỹ):
OCCURS– khai báo table/mảng trong COBOLREDEFINES– cho phép nhiều layout khác nhau cùng "chồng" lên một vùng nhớ
Chúng ta sẽ:
- Xây dựng mental model tương đương với
array/structtrong Java/Python - Phân tích các ví dụ thực tế lấy cảm hứng từ
RZZBSQLN1nhưWK_SQL_TBL,WK_PARM_TBL,SYS_DATE/SYS_DATE-R - Làm bài tập thiết kế table và layout date/time
Ôn lại: WORKING-STORAGE và level number
WORKING-STORAGE dùng để khai báo biến dùng suốt chương trình:
DATA DIVISION.
WORKING-STORAGE SECTION.
01 CUSTOMER.
05 CUST-ID PIC 9(5).
05 CUST-NAME PIC X(30).
77 WS-COUNTER PIC 9(5) VALUE 0.
01 CUSTOMERlà record (group item) – giống một struct/class instance05 CUST-ID,05 CUST-NAMElà field con bên trong77 WS-COUNTERlà biến đơn lẻ (không thuộc group)
Khi thêm OCCURS và REDEFINES, bạn đang chuyển từ biến đơn sang cấu trúc dữ liệu phức tạp.
OCCURS – Khai báo table (mảng) trong COBOL
1. OCCURS là gì?
OCCURS n TIMES cho COBOL biết rằng một field hoặc group sẽ lặp lại n lần, tương đương với:
- Mảng 1 chiều (
array) trong Java/C - List có kích thước cố định trong các ngôn ngữ modern
Ví dụ đơn giản:
01 SCORE-TABLE.
05 SCORE-ITEM OCCURS 5 TIMES.
10 SCORE-VALUE PIC 9(3).
Ý nghĩa:
SCORE-TABLElà group itemSCORE-ITEMlặp lại 5 lần- Mỗi
SCORE-ITEMcó 1 fieldSCORE-VALUE(3 chữ số)
Tổng dung lượng:
SCORE-VALUE= 3 bytesSCORE-ITEM OCCURS 5 TIMES→ 3 * 5 = 15 bytes liên tiếp trong bộ nhớ
2. Truy cập phần tử với index
Truy cập phần tử thứ i dùng cú pháp field-name (index):
MOVE 100 TO SCORE-VALUE (1).
MOVE 80 TO SCORE-VALUE (2).
MOVE 75 TO SCORE-VALUE (3).
Index trong COBOL bắt đầu từ 1, không phải 0.
Bạn có thể kết hợp với PERFORM VARYING:
01 IDX PIC 9(2).
PROCEDURE DIVISION.
PERFORM VARYING IDX FROM 1 BY 1
UNTIL IDX > 5
DISPLAY "SCORE(" IDX ") = " SCORE-VALUE (IDX)
END-PERFORM.
Mapping Java:
int[] score = new int[5];
score[0] = 100;
score[1] = 80;
...
for (int i = 0; i < 5; i++) {
System.out.println("SCORE(" + (i+1) + ") = " + score[i]);
}
Nhớ: SCORE-VALUE(1) ~ score[0] nếu bạn muốn mapping index theo tư duy 0-based.
Group table vs elementary table
OCCURS có thể dùng ở level group hoặc level field:
1. Elementary table – OCCURS trên field
01 SCORE-TABLE.
05 SCORE-VALUE PIC 9(3) OCCURS 5 TIMES.
SCORE-VALUE(1)..SCORE-VALUE(5)là các phần tử numeric- Thích hợp khi mỗi phần tử chỉ có một field
2. Group table – OCCURS trên group
01 STUDENT-TABLE.
05 STUDENT-ITEM OCCURS 100 TIMES.
10 STU-ID PIC 9(5).
10 STU-NAME PIC X(30).
10 STU-AGE PIC 9(3).
-
STUDENT-ITEM(1)là một record gồm 3 field: ID, NAME, AGE -
Truy cập field con:
MOVE 00001 TO STU-ID (1). MOVE "Nguyen Van A" TO STU-NAME (1). MOVE 20 TO STU-AGE (1). -
Phù hợp khi mỗi phần tử có nhiều field – giống mảng struct/class.
So sánh nhanh
| Kiểu OCCURS | Ví dụ | Use case |
|---|---|---|
| Elementary table | SCORE-VALUE PIC 9(3) OCCURS 5 |
Danh sách điểm, số lượng đơn giản |
| Group table | STUDENT-ITEM OCCURS 100 TIMES |
Danh sách record phức tạp (ID, NAME) |
Pattern OCCURS trong RZZBSQLN1: WK_SQL_TBL và WK_PARM_TBL
Trong RZZBSQLN1.PCO, có các khai báo kiểu:
01 WK_SQL_TBL.
03 WK_SQL_ITEM OCCURS 50 TIMES.
05 WK_SQL PIC X(82).
01 WK_PARM_TBL.
03 WK_PARM_ITEM OCCURS 4 TIMES.
05 WK_PARM PIC X(64).
Ý nghĩa:
WK_SQL_TBLlà table chứa tối đa 50 dòng SQLWK_SQL(n)là SQL thứ n đọc được từ file inputWK_PARM_TBLlà table chứa tối đa 4 parameter (ví dụ schema, DB name,...)
Truy cập trong code (giả lập):
COMPUTE WK_SQL_IDX = WK_SQL_IDX + 1.
IF WK_SQL_IDX <= 50
MOVE INP1_SQL TO WK_SQL (WK_SQL_IDX)
END-IF.
Mapping Java:
String[] wkSql = new String[50];
int wkSqlIdx = 0;
wkSqlIdx = wkSqlIdx + 1;
if (wkSqlIdx <= 50) {
wkSql[wkSqlIdx - 1] = inp1Sql; // nếu Java index từ 0
}
Khi học tới bài 010–011 về embedded SQL, bạn sẽ thấy table này được duyệt bằng PERFORM VARYING để build và execute từng SQL.
REDEFINES – Một vùng nhớ, nhiều layout
1. REDEFINES là gì?
REDEFINES cho phép hai (hoặc nhiều) biến chia sẻ cùng một vùng nhớ, nhưng được mô tả với layout khác nhau.
Ví dụ đơn giản với date:
01 SYS-DATE PIC X(8).
01 SYS-DATE-R REDEFINES SYS-DATE.
05 SYS-YYYY PIC X(4).
05 SYS-MM PIC X(2).
05 SYS-DD PIC X(2).
Ý nghĩa:
SYS-DATElà chuỗi 8 ký tự, ví dụ "20260304"SYS-DATE-Rkhông chiếm thêm bộ nhớ, chỉ "nhìn" vùng nhớ đó dưới dạng 3 field con: YYYY, MM, DD
Cách dùng:
ACCEPT SYS-DATE FROM DATE.
DISPLAY SYS-DATE.
DISPLAY "YEAR=" SYS-YYYY " MONTH=" SYS-MM " DAY=" SYS-DD.
Mapping mental model:
- Giống việc dùng
substringtrong Java, nhưng được compiler hỗ trợ layout ngay trong khai báo dữ liệu
String sysDate = getToday(); // "20260304"
String yyyy = sysDate.substring(0, 4);
String mm = sysDate.substring(4, 6);
String dd = sysDate.substring(6, 8);
2. Yêu cầu quan trọng khi dùng REDEFINES
- Tổng độ dài (bytes) của các layout
REDEFINESphải bằng nhau hoặc bạn hiểu rõ rủi ro khi không bằng - Các biến
REDEFINESphải cùng level (thường là level 01 hoặc 05)
Ví dụ an toàn:
01 AMOUNT-FIELD.
05 AMOUNT-RAW PIC X(10).
01 AMOUNT-NUM REDEFINES AMOUNT-FIELD.
05 AMOUNT-SIGN PIC X(1).
05 AMOUNT-VALUE PIC 9(9).
Both = 10 bytes → an toàn.
Pattern REDEFINES với date/time trong batch program
Lấy cảm hứng từ các chương trình như RZZBSQLN1, pattern phổ biến:
01 SYS-DATE2 PIC 9(8).
01 SYS-DATE2-R REDEFINES SYS-DATE2.
05 SYS-DYYYY2 PIC 9(4).
05 SYS-DMM2 PIC 9(2).
05 SYS-DDD2 PIC 9(2).
Sau đó code có thể làm:
ACCEPT SYS-DATE2 FROM DATE.
IF SYS-DYYYY2 < 2000
ADD 19000000 TO SYS-DATE2
ELSE
ADD 20000000 TO SYS-DATE2
END-IF.
Tư duy backend dev:
- Input từ hệ thống có thể trả về date dạng YYMMDD → cần chuyển thành YYYYMMDD
REDEFINESgiúp bạn thao tác trực tiếp trên phần YEAR mà không cần substring thủ công
Mapping Java pseudo-code:
int sysDate2 = acceptDate(); // 8-digit integer
int yyyy = sysDate2 / 10000; // phần năm
if (yyyy < 2000) {
sysDate2 = sysDate2 + 19000000;
} else {
sysDate2 = sysDate2 + 20000000;
}
Trong COBOL, REDEFINES cho phép:
- Vừa làm việc với cả chuỗi 8-digit (
SYS-DATE2) - Vừa thao tác trên từng phần con (
SYS-DYYYY2,SYS-DMM2,SYS-DDD2)
So sánh OCCURS và REDEFINES
| Khía cạnh | OCCURS | REDEFINES |
|---|---|---|
| Mục đích chính | Tạo table/mảng nhiều phần tử | Tạo nhiều "view" khác nhau cho cùng vùng nhớ |
| Kiểu cấu trúc | Lặp lại cùng layout nhiều lần (1 chiều) | Nhiều layout song song, cùng độ dài |
| Mapping hiện đại | Array/List | Struct với union/variant, hoặc parse bằng substring |
| Use case điển hình | Danh sách record tạm, buffer SQL, table | Date/time, record đa format, union type |
| Rủi ro | Tràn index, overflow table | Layout lệch độ dài → truncate/garbage khó debug |
Khi đọc WORKING-STORAGE phức tạp:
- Tìm các block dùng
OCCURS→ đây là table - Tìm các block dùng
REDEFINES→ đây là layout song song, cần hiểu mối quan hệ
Ví dụ tổng hợp: table log và REDEFINES status code
Mục tiêu
- Lưu tối đa 10 dòng log message trong WORKING-STORAGE
- Mỗi dòng gồm: timestamp (8), type (1), message (31)
- Có thêm một layout
REDEFINESđể nhìn type theo dạng condition
Khai báo dữ liệu
WORKING-STORAGE SECTION.
01 LOG-TABLE.
05 LOG-ITEM OCCURS 10 TIMES.
10 LOG-TIME PIC X(8).
10 LOG-TYPE PIC X(1).
10 LOG-MSG PIC X(31).
01 LOG-IDX PIC 9(2) VALUE 0.
01 STATUS-CODE PIC X(2).
01 STATUS-CODE-R REDEFINES STATUS-CODE.
05 STATUS-MAIN PIC X(1).
05 STATUS-SUB PIC X(1).
88 STATUS-OK VALUE "00".
88 STATUS-WARN VALUE "10" THRU "19".
88 STATUS-ERROR VALUE "90" THRU "99".
LOG-TABLElà group table 10 phần tửSTATUS-CODE-Rchia status 2-char thành 2 phần: main/sub- Level 88 (sẽ học sâu hơn ở bài khác) giúp đặt condition name cho các giá trị cụ thể
PROCEDURE DIVISION (rút gọn)
PROCEDURE DIVISION.
MAIN-RTN.
MOVE "00" TO STATUS-CODE
PERFORM ADD-LOG-LINE
MOVE "15" TO STATUS-CODE
PERFORM ADD-LOG-LINE
MOVE "91" TO STATUS-CODE
PERFORM ADD-LOG-LINE
PERFORM PRINT-LOG
STOP RUN.
ADD-LOG-LINE.
IF LOG-IDX < 10
ADD 1 TO LOG-IDX
ACCEPT LOG-TIME (LOG-IDX) FROM TIME
MOVE STATUS-MAIN TO LOG-TYPE (LOG-IDX)
MOVE "Status = " TO LOG-MSG (LOG-IDX)
END-IF
EXIT.
PRINT-LOG.
PERFORM VARYING LOG-IDX FROM 1 BY 1
UNTIL LOG-IDX > 10
IF LOG-TIME (LOG-IDX) NOT = SPACES
DISPLAY LOG-TIME (LOG-IDX)
DISPLAY " TYPE=" LOG-TYPE (LOG-IDX)
DISPLAY " MSG =" LOG-MSG (LOG-IDX)
END-IF
END-PERFORM
EXIT.
Ý tưởng:
STATUS-MAIN(ký tự đầu) có thể đại diện cho loại status: OK/WARN/ERROR- Mỗi lần log, ta chỉ lưu
STATUS-MAINvàoLOG-TYPE
Bài tập thực hành
Bài 1 – Thiết kế table sản phẩm với OCCURS
Yêu cầu:
- Khai báo
PRODUCT-TBLvới tối đa 100 sản phẩm - Mỗi sản phẩm có:
PROD-ID–X(10)PROD-NAME–X(30)PROD-PRICE–9(9)
- Dùng
OCCURS(group table)
Sau đó, viết skeleton PROCEDURE DIVISION:
- Biến
IDX(9(3)) dùng làm index PERFORM VARYING IDX FROM 1 BY 1 UNTIL IDX > PROD-COUNTđể display danh sách sản phẩm
Bài 2 – REDEFINES cho amount + sign
Thiết kế biến lưu số tiền với format sau trong file:
- 1 ký tự sign (
+hoặc-) - 9 chữ số amount (không có dấu chấm thập phân)
Yêu cầu khai báo:
- Biến raw
AMOUNT-RAW10 ký tự - Biến
AMOUNT-SPLIT REDEFINES AMOUNT-RAWgồm:AMT-SIGN–X(1)AMT-VALUE–9(9)
Viết đoạn code nhỏ:
- Gán thử
"+000001000"và"-000000500"vàoAMOUNT-RAW - Hiển thị sign và numeric value tách riêng
Bài 3 – Áp dụng OCCURS như WK_SQL_TBL
Giả sử bạn có file chứa tối đa 20 câu lệnh SQL, mỗi dòng tối đa 100 ký tự.
Yêu cầu:
- Khai báo
SQL-TBLvớiOCCURS 20 TIMES, mỗi phần tử có fieldSQL-TEXT PIC X(100) - Khai báo biến
SQL-IDX PIC 9(2)làm index - Viết skeleton logic:
- Đọc từng dòng từ file input vào
WS-INPUT-LINE - Tăng
SQL-IDX,MOVE WS-INPUT-LINE TO SQL-TEXT (SQL-IDX)nếu chưa vượt quá 20 - Sau khi đọc xong, dùng
PERFORM VARYINGđểDISPLAY SQL-TEXT (i)từ 1 đếnSQL-IDX
- Đọc từng dòng từ file input vào
Bài 4 – Thiết kế REDEFINES cho date/time
Thiết kế cặp biến để xử lý datetime dạng YYYYMMDDHHMISS (14 ký tự):
- Biến raw:
DT-RAW PIC X(14) - Biến
DT-PARTS REDEFINES DT-RAWgồm:DT-YYYY PIC X(4)DT-MM PIC X(2)DT-DD PIC X(2)DT-HH PIC X(2)DT-MI PIC X(2)DT-SS PIC X(2)
Viết đoạn code nhỏ gán giá trị ví dụ "20260304153045" rồi DISPLAY từng phần.
Kết luận
Trong bài 008, bạn đã đi sâu vào WORKING-STORAGE nâng cao với hai công cụ cực kỳ quan trọng:
OCCURS– để khai báo table/mảng, lưu nhiều record tạm thời (ví dụWK_SQL_TBL,WK_PARM_TBL)REDEFINES– để tạo nhiều view/layout cho cùng vùng nhớ (đặc biệt hữu ích với date/time, amount, record đa format)
Hiểu vững hai khái niệm này giúp bạn:
- Đọc được các WORKING-STORAGE block phức tạp trong chương trình COBOL enterprise
- Mapping được cấu trúc COBOL sang array/struct/class trong Java/C#/PL-SQL
- Thiết kế data structure hợp lý cho batch job mới
Ở các bài tiếp theo, chúng ta sẽ:
- Khai thác ACCEPT FROM DATE/TIME và pattern xử lý ngày giờ (bài 009)
- Bắt đầu bước vào thế giới embedded SQL trong COBOL (bài 010–011), nơi các table như
WK_SQL_TBLthật sự phát huy tác dụng khi build và thực thi SQL động.
Khi quay lại đọc RZZBSQLN1.PCO, hãy thử:
- Xác định tất cả block dùng
OCCURS→ đó là các table quan trọng - Xác định tất cả block dùng
REDEFINES→ đó là các layout đặc biệt bạn cần hiểu kỹ
Làm được điều này, bạn đã tiến thêm một bước lớn trên hành trình đọc hiểu và maintain hệ thống COBOL legacy.
