1. Como funciona o SQL embutido

Um programa COBOL com SQL passa por duas etapas de compilação antes de virar load module executável:

JCL Fluxo de build — pré-compilação + compilação + link
  Código fonte (.cbl)
        │
        ▼  DSNHPC (Pré-compilador DB2)
        │  ├─ Substitui EXEC SQL...END-EXEC por CALLs internas
        │  ├─ Gera DBRM (Database Request Module) → vai para o BIND
        │  └─ Emite código COBOL modificado (.cbl limpo)
        │
        ▼  IGYCRCTL (Compilador COBOL)
        │  Compila o .cbl limpo → gera objeto .obj
        │
        ▼  IEWL (Linkage Editor)
        │  Liga objeto + DB2 stubs → Load Module no PDS de execução
        │
        ▼  DSNTBIND (BIND)
           Gera Package ou Plan a partir do DBRM
           Otimiza o plano de acesso no catálogo DB2

🦕 Analogia — o tradutor antes do compilador

Imagine que o pré-compilador é um assistente que lê seu código, pega cada bloco EXEC SQL e troca por um CALL para uma rotina DB2. O COBOL resultante não tem mais SQL — só COBOL puro com chamadas. Por isso o compilador COBOL nem sabe que existia SQL: ele compila o código já "traduzido".

Sintaxe de um bloco SQL

COBOL Estrutura de um comando SQL embutido
           EXEC SQL
               SELECT NOME, SALDO
                 INTO :WS-NOME, :WS-SALDO
                 FROM CONTA
                WHERE NR_CONTA = :WS-NR-CONTA
           END-EXEC

             * Regras:
             * - EXEC SQL e END-EXEC nas colunas 7–72
             * - Variáveis COBOL precedidas de : (dois-pontos)
             * - Qualquer SQL válido entre os delimitadores
             * - Comentários COBOL (*) fora do bloco, -- dentro

2. SQLCA — a área de comunicação

O DB2 preenche a estrutura SQLCA (SQL Communication Area) após cada comando SQL. O campo mais importante é o SQLCODE:

SQLCODE Significado Ação típica
0 Sucesso — nenhuma ocorrência especial Continua normalmente
+100 Nenhuma linha encontrada (NOT FOUND) Tratar como "não existe"
+1 a +99 Warning (aviso) — operação concluída com ressalva Avaliar SQLERRD/SQLWARN
negativo Erro — operação NÃO concluída ROLLBACK + abend ou log
COBOL Incluindo a SQLCA no programa
       WORKING-STORAGE SECTION.

           EXEC SQL
               INCLUDE SQLCA
           END-EXEC

             * O pré-compilador expande isso para a estrutura completa:
             * 01 SQLCA.
             *    05 SQLCAID   PIC X(8).         *'SQLCA   '
             *    05 SQLCABC  PIC S9(9) COMP.    *tamanho da SQLCA (136)
             *    05 SQLCODE  PIC S9(9) COMP.    *código de retorno ← o mais usado
             *    05 SQLERRM.                    *mensagem de erro
             *       49 SQLERRML PIC S9(4) COMP.
             *       49 SQLERRMC PIC X(70).
             *    05 SQLERRP  PIC X(8).          *módulo que detectou o erro
             *    05 SQLERRD  OCCURS 6 TIMES
             *                PIC S9(9) COMP.    *SQLERRD(3) = linhas afetadas
             *    05 SQLWARN.
             *       49 SQLWARN0 PIC X.          *'W' se algum warning ocorreu
             *    05 SQLSTATE PIC X(5).          *código ANSI SQL (ex: '02000')

✅ SQLCODE vs SQLSTATE

SQLCODE é IBM-específico e é o que você verá em 99% do código legado. SQLSTATE é o padrão ANSI SQL e portável entre bancos. Em z/OS COBOL, use SQLCODE — é mais descritivo para o DB2 e é o que os DBAs esperam ver nas mensagens de log.

3. DCLGEN — declarando variáveis host

O DCLGEN (Declaration Generator) é um utilitário DB2 que lê a definição de uma tabela no catálogo e gera automaticamente a estrutura COBOL correspondente. Você não declara variáveis host na mão — usa o DCLGEN e faz COPY.

JCL Gerando um DCLGEN via DSNTIAUL
  //GENDCL  JOB ...
  //STEP01  EXEC PGM=IKJEFT01
  //SYSTSPRT DD SYSOUT=*
  //SYSTSIN  DD *
    DSN SYSTEM(DBS1)
    DCLGEN TABLE(SCHEMA1.CONTA)     -
           LIBRARY('HLQ.COPYLIB(DCCONTA)')  -
           ACTION(REPLACE)         -
           LANGUAGE(COBOL)         -
           APOST
    END
  /*
COBOL Resultado gerado pelo DCLGEN — member DCCONTA
      ******************************************************************
      * DCLGEN TABLE(SCHEMA1.CONTA)
      ******************************************************************
           EXEC SQL DECLARE SCHEMA1.CONTA TABLE
               ( NR_CONTA     CHAR(10)         NOT NULL
               , NM_TITULAR   VARCHAR(60)      NOT NULL
               , VL_SALDO     DECIMAL(13,2)    NOT NULL
               , DT_ABERTURA  DATE             NOT NULL
               , CD_STATUS    CHAR(1)          NOT NULL
               , VL_LIMITE    DECIMAL(13,2)             -- nullable
               ) END-EXEC.

      * COBOL DECLARATION FOR TABLE SCHEMA1.CONTA
       01  DCONTA.
           10  NR-CONTA       PIC X(10).
           10  NM-TITULAR.
               49  NM-TITULAR-LEN  PIC S9(4) COMP.
               49  NM-TITULAR-TEXT PIC X(60).
           10  VL-SALDO       PIC S9(13)V99 COMP-3.
           10  DT-ABERTURA    PIC X(10).
           10  CD-STATUS      PIC X(1).
           10  VL-LIMITE      PIC S9(13)V99 COMP-3.
COBOL Usando o DCLGEN no programa via COPY
       WORKING-STORAGE SECTION.
           EXEC SQL INCLUDE SQLCA  END-EXEC

           COPY DCCONTA.   *expande a estrutura DCONTA inteira

⚠️ Nunca declare variáveis host na mão

Digitar à mão os tipos de dados de uma tabela DB2 é pedir para ter divergência entre o que o programa espera e o que está no catálogo. Se a coluna VL_SALDO mudar de DECIMAL(13,2) para DECIMAL(15,2), o DCLGEN regenerado atualiza o copybook e todos os programas que usam COPY DCCONTA capturam a mudança na próxima compilação.

4. SELECT INTO, INSERT, UPDATE, DELETE

Para operações que retornam exatamente uma linha (ou nenhuma), usa-se SELECT INTO diretamente. Para múltiplas linhas, usa-se cursor (seção 6).

COBOL SELECT INTO — lendo uma linha
       PROCEDURE DIVISION.

       2000-BUSCA-CONTA.
           EXEC SQL
               SELECT NR_CONTA
                    , NM_TITULAR
                    , VL_SALDO
                    , CD_STATUS
                 INTO :NR-CONTA
                    , :NM-TITULAR
                    , :VL-SALDO
                    , :CD-STATUS
                 FROM SCHEMA1.CONTA
                WHERE NR_CONTA = :WS-CONTA-PESQ
           END-EXEC

           EVALUATE TRUE
               WHEN SQLCODE = 0
                   PERFORM 3000-PROCESSA-CONTA
               WHEN SQLCODE = +100
                   PERFORM 9100-CONTA-NAO-ENCONTRADA
               WHEN OTHER
                   PERFORM 9900-ERRO-DB2
           END-EVALUATE.
COBOL INSERT, UPDATE e DELETE
       4000-INSERE-CONTA.
           EXEC SQL
               INSERT INTO SCHEMA1.CONTA
                   (NR_CONTA, NM_TITULAR, VL_SALDO, DT_ABERTURA, CD_STATUS)
               VALUES
                   (:NR-CONTA, :NM-TITULAR, :VL-SALDO, CURRENT DATE, 'A')
           END-EXEC
           PERFORM 9000-TRATA-SQLCODE.

       5000-ATUALIZA-SALDO.
           EXEC SQL
               UPDATE SCHEMA1.CONTA
                  SET VL_SALDO = :VL-SALDO
                    , CD_STATUS = :CD-STATUS
                WHERE NR_CONTA = :NR-CONTA
           END-EXEC
           IF SQLCODE = +100
               PERFORM 9100-CONTA-NAO-ENCONTRADA
           ELSE
               PERFORM 9000-TRATA-SQLCODE
           END-IF.

       6000-INATIVA-CONTA.
           EXEC SQL
               DELETE FROM SCHEMA1.CONTA
                WHERE NR_CONTA = :NR-CONTA
                  AND CD_STATUS = 'I'
           END-EXEC
                 * SQLERRD(3) = número de linhas deletadas
           MOVE SQLERRD(3) TO WS-LINHAS-AFETADAS
           PERFORM 9000-TRATA-SQLCODE.

5. Tratando o SQLCODE

Todo comando SQL deve ser seguido de verificação do SQLCODE. Um único SELECT que retorna SQL negativo e não é tratado pode corromper dados ou gerar abend U4038.

COBOL Parágrafo genérico de tratamento DB2
       WORKING-STORAGE SECTION.
       01  WS-SQLCODE-DISP  S9(09) SIGN LEADING SEPARATE.
       01  WS-MSG-ERRO      X(80).

           

       9000-TRATA-SQLCODE.
           IF SQLCODE = 0 OR SQLCODE = +100
               CONTINUE
           ELSE
               MOVE SQLCODE TO WS-SQLCODE-DISP
               STRING 'ERRO DB2 SQLCODE=' DELIMITED SIZE
                      WS-SQLCODE-DISP       DELIMITED SIZE
                      ' MSG='               DELIMITED SIZE
                      SQLERRMC             DELIMITED SIZE
                   INTO WS-MSG-ERRO
               END-STRING
               DISPLAY WS-MSG-ERRO UPON SYSOUT
               EXEC SQL ROLLBACK END-EXEC
               MOVE 16 TO RETURN-CODE
               STOP RUN
           END-IF.

💗 Códigos DB2 mais frequentes no dia a dia

  • -803 — chave duplicada em INSERT (violação de unique index)
  • -805 — Package não encontrado no catálogo (precisa de BIND)
  • -811 — SELECT INTO retornou mais de uma linha (use cursor!)
  • -818 — timestamp mismatch — DBRM e load module de compilações diferentes
  • -904 — recurso indisponível (tabela em status de manutenção)
  • -911 — deadlock ou timeout — transação sofreu ROLLBACK automático
  • -922 — autorização negada (usuário sem GRANT na tabela)

6. Cursores — lendo múltiplas linhas

Quando um SELECT pode retornar mais de uma linha, você precisa de cursor. O ciclo sempre é: DECLARE → OPEN → FETCH em loop → CLOSE.

COBOL Cursor básico — leitura de todas as contas ativas
       WORKING-STORAGE SECTION.
           EXEC SQL INCLUDE SQLCA        END-EXEC
           COPY DCCONTA.
       01  WS-FIM-CURSOR  PIC X VALUE 'N'.

             * DECLARE fica na WORKING-STORAGE (fora da PROCEDURE DIVISION)
           EXEC SQL
               DECLARE CUR-CONTAS CURSOR FOR
               SELECT NR_CONTA, NM_TITULAR, VL_SALDO, CD_STATUS
                 FROM SCHEMA1.CONTA
                WHERE CD_STATUS = 'A'
                ORDER BY NR_CONTA
           END-EXEC

       PROCEDURE DIVISION.

       1000-PROCESSA-CONTAS.
           EXEC SQL
               OPEN CUR-CONTAS
           END-EXEC
           PERFORM 9000-TRATA-SQLCODE

           PERFORM 1100-FETCH-CONTA

           PERFORM 1200-LOOP-CONTAS
               UNTIL WS-FIM-CURSOR = 'S'

           EXEC SQL
               CLOSE CUR-CONTAS
           END-EXEC.

       1100-FETCH-CONTA.
           EXEC SQL
               FETCH CUR-CONTAS
                 INTO :NR-CONTA, :NM-TITULAR, :VL-SALDO, :CD-STATUS
           END-EXEC

           EVALUATE TRUE
               WHEN SQLCODE = 0
                   CONTINUE
               WHEN SQLCODE = +100
                   MOVE 'S' TO WS-FIM-CURSOR
               WHEN OTHER
                   PERFORM 9900-ERRO-DB2
           END-EVALUATE.

       1200-LOOP-CONTAS.
           PERFORM 2000-PROCESSA-LINHA
           PERFORM 1100-FETCH-CONTA.

Cursor FOR UPDATE — atualizar enquanto lê

COBOL FOR UPDATE com WHERE CURRENT OF
           EXEC SQL
               DECLARE CUR-ATUALIZA CURSOR FOR
               SELECT NR_CONTA, VL_SALDO
                 FROM SCHEMA1.CONTA
                WHERE CD_STATUS = 'A'
                  FOR UPDATE OF VL_SALDO   -- bloqueia para atualização
           END-EXEC

           …depois do FETCH…

           EXEC SQL
               UPDATE SCHEMA1.CONTA
                  SET VL_SALDO = :VL-SALDO-NOVO
               WHERE CURRENT OF CUR-ATUALIZA  -- atualiza a linha do cursor
           END-EXEC
           PERFORM 9000-TRATA-SQLCODE.

WITH HOLD — cursor que sobrevive ao COMMIT

COBOL Cursor WITH HOLD — commits parciais sem fechar cursor
           EXEC SQL
               DECLARE CUR-LOTE CURSOR WITH HOLD FOR
               SELECT NR_CONTA FROM SCHEMA1.CONTA
                WHERE DT_VENCIMENTO < CURRENT DATE
           END-EXEC

             * Sem WITH HOLD: COMMIT fecha todos os cursores abertos.
             * Com WITH HOLD: o cursor permanece aberto após COMMIT,
             *               posicionado na próxima linha a ser lida.
             * Útil em jobs de lote que fazem COMMIT a cada N registros
             * para liberar lock sem ter que reabrir o cursor.

           PERFORM 1100-FETCH-LOTE
           PERFORM VARYING WS-CTR FROM 1 BY 1
                   UNTIL WS-FIM-CURSOR = 'S'
               PERFORM 2000-PROCESSA
               IF FUNCTION MOD(WS-CTR, 1000) = 0
                   EXEC SQL COMMIT END-EXEC  *commit a cada 1000
               END-IF
               PERFORM 1100-FETCH-LOTE
           END-PERFORM
           EXEC SQL COMMIT END-EXEC.

7. Indicadores de nulo

Uma coluna DB2 declarada sem NOT NULL pode conter NULL. Se você faz FETCH de um valor NULL sem indicador, o DB2 retorna SQLCODE -305. O indicador é uma variável S9(4) COMP acompanhada do campo host, prefixada com dois-pontos e separada por espaço:

COBOL Indicadores de nulo — declaração e uso
       WORKING-STORAGE SECTION.
       01  IND-VL-LIMITE   S9(4) COMP.
             * -1 = valor é NULL
             *  0 = valor presente, sem truncamento
             * >0 = valor presente, mas string foi truncada (VARCHAR)

       PROCEDURE DIVISION.

           EXEC SQL
               SELECT VL_LIMITE
                 INTO :VL-LIMITE :IND-VL-LIMITE   -- indicador logo depois
                 FROM SCHEMA1.CONTA
                WHERE NR_CONTA = :NR-CONTA
           END-EXEC

           IF SQLCODE = 0
               IF IND-VL-LIMITE = -1
                   MOVE 0 TO VL-LIMITE   *trata NULL como zero
               ELSE
                   CONTINUE               *VL-LIMITE tem valor real
               END-IF
           END-IF

                 * INSERT com NULL explícito via indicador:
           MOVE -1 TO IND-VL-LIMITE
           EXEC SQL
               INSERT INTO SCHEMA1.CONTA (NR_CONTA, VL_LIMITE)
               VALUES (:NR-CONTA, :VL-LIMITE :IND-VL-LIMITE)
           END-EXEC
                 * Como IND-VL-LIMITE = -1, o DB2 grava NULL em VL_LIMITE

8. WHENEVER — tratamento automático

WHENEVER instrui o pré-compilador a inserir automaticamente um GO TO após cada comando SQL para tratar condições específicas. É uma alternativa ao IF SQLCODE … manual depois de cada comando:

COBOL WHENEVER — sintaxe e posicionamento
       WORKING-STORAGE SECTION.
           EXEC SQL INCLUDE SQLCA END-EXEC

       PROCEDURE DIVISION.

       0000-INICIO.
                 * A partir daqui, qualquer SQLCODE negativo desvia para 9900
           EXEC SQL
               WHENEVER SQLERROR GO TO :9900-ERRO-DB2
           END-EXEC

                 * A partir daqui, SQLCODE = +100 desvia para 8000
           EXEC SQL
               WHENEVER NOT FOUND GO TO :8000-FIM-CURSOR
           END-EXEC

           EXEC SQL OPEN CUR-CONTAS END-EXEC
           PERFORM 1100-FETCH
           PERFORM 1200-LOOP UNTIL WS-FIM = 'S'
           EXEC SQL CLOSE CUR-CONTAS END-EXEC
           STOP RUN.

       8000-FIM-CURSOR.
           MOVE 'S' TO WS-FIM.

       9900-ERRO-DB2.
           DISPLAY 'SQLCODE=' SQLCODE
           EXEC SQL ROLLBACK END-EXEC
           STOP RUN.

⚠️ WHENEVER usa GO TO — cuidado com escopo

WHENEVER é textual: ele injeta GO TO depois de cada comando SQL que aparecer após a declaração WHENEVER. Isso inclui OPEN, CLOSE e o próprio FETCH dentro do loop. O GO TO gerado pode pular fora de um PERFORM, causando comportamento inesperado. A maioria dos shops modernos prefere o padrão explícito de checar SQLCODE com EVALUATE após cada comando.

9. Exemplo completo

O programa EXTRATO lê todas as movimentações de uma conta num período, acumula o saldo e grava um arquivo de extrato. Demonstra cursor WITH HOLD, indicador de nulo, commit parcial e tratamento de SQLCODE:

COBOL EXTRATO — extrato de movimentações com DB2
      *----------------------------------------------------------------
      * EXTRATO — gera extrato de movimentações por conta/período
      *----------------------------------------------------------------
       IDENTIFICATION DIVISION.
           PROGRAM-ID. EXTRATO.

       DATA DIVISION.
       WORKING-STORAGE SECTION.

           EXEC SQL INCLUDE SQLCA      END-EXEC
           COPY DCMOVTO.             *DCLGEN da tabela MOVIMENTACAO

       01  WS-PARMS.
           05  WS-CONTA-INI   PIC X(10).
           05  WS-DT-INI      PIC X(10).  *YYYY-MM-DD
           05  WS-DT-FIM      PIC X(10).
       01  WS-TOTAL-DEB   S9(13)V99 COMP-3.
       01  WS-TOTAL-CRE   S9(13)V99 COMP-3.
       01  WS-CTR         S9(09) COMP.
       01  WS-FIM         PIC X VALUE 'N'.
       01  IND-DESCRICAO  S9(4) COMP.  *indicador de nulo p/ descrição

           EXEC SQL
               DECLARE CUR-MOVTO CURSOR WITH HOLD FOR
               SELECT NR_CONTA, DT_MOVTO, TP_MOVTO,
                      VL_MOVTO, DS_HISTORICO
                 FROM SCHEMA1.MOVIMENTACAO
                WHERE NR_CONTA  = :WS-CONTA-INI
                  AND DT_MOVTO BETWEEN :WS-DT-INI AND :WS-DT-FIM
                ORDER BY DT_MOVTO, HR_MOVTO
           END-EXEC

       PROCEDURE DIVISION.

       0000-INICIO.
           INITIALIZE WS-TOTAL-DEB WS-TOTAL-CRE WS-CTR
           MOVE '1234567890' TO WS-CONTA-INI
           MOVE '2026-01-01' TO WS-DT-INI
           MOVE '2026-06-30' TO WS-DT-FIM

           EXEC SQL OPEN CUR-MOVTO END-EXEC
           PERFORM 9000-TRATA-SQLCODE

           PERFORM 1000-FETCH-MOVTO
           PERFORM 1100-LOOP-MOVTO
               UNTIL WS-FIM = 'S'

           EXEC SQL CLOSE CUR-MOVTO END-EXEC
           EXEC SQL COMMIT END-EXEC

           DISPLAY 'TOTAL DEBITOS : '  WS-TOTAL-DEB
           DISPLAY 'TOTAL CREDITOS: '  WS-TOTAL-CRE
           DISPLAY 'REGISTROS     : '  WS-CTR
           STOP RUN.

       1000-FETCH-MOVTO.
           EXEC SQL
               FETCH CUR-MOVTO
                 INTO :NR-CONTA, :DT-MOVTO, :TP-MOVTO,
                      :VL-MOVTO, :DS-HISTORICO :IND-DESCRICAO
           END-EXEC
           EVALUATE TRUE
               WHEN SQLCODE = 0     CONTINUE
               WHEN SQLCODE = +100  MOVE 'S' TO WS-FIM
               WHEN OTHER            PERFORM 9900-ERRO-DB2
           END-EVALUATE.

       1100-LOOP-MOVTO.
           ADD 1 TO WS-CTR
           IF TP-MOVTO = 'D'
               ADD VL-MOVTO TO WS-TOTAL-DEB
           ELSE
               ADD VL-MOVTO TO WS-TOTAL-CRE
           END-IF
           IF IND-DESCRICAO = -1
               MOVE 'SEM HISTORICO' TO DS-HISTORICO-TEXT
           END-IF
           PERFORM 2000-GRAVA-EXTRATO
           IF FUNCTION MOD(WS-CTR, 500) = 0
               EXEC SQL COMMIT END-EXEC
               PERFORM 9000-TRATA-SQLCODE
           END-IF
           PERFORM 1000-FETCH-MOVTO.

       9000-TRATA-SQLCODE.
           IF SQLCODE NOT = 0 AND NOT = +100
               DISPLAY 'SQLCODE=' SQLCODE ' ' SQLERRMC
               EXEC SQL ROLLBACK END-EXEC
               STOP RUN
           END-IF.

       9900-ERRO-DB2.
           DISPLAY 'ERRO FATAL SQLCODE=' SQLCODE
           EXEC SQL ROLLBACK END-EXEC
           STOP RUN.

10. Erros comuns

1. SELECT INTO sem checar SQLCODE +100

COBOLUsar dados de SELECT que não encontrou linha
           EXEC SQL
               SELECT VL_SALDO INTO :VL-SALDO
                 FROM SCHEMA1.CONTA WHERE NR_CONTA = :NR-CONTA
           END-EXEC
                 * ❌ Se SQLCODE = +100, VL-SALDO ainda tem o valor anterior!
           COMPUTE WS-NOVO-SALDO = VL-SALDO + WS-CREDITO
                 * ✅ Cheque SQLCODE ANTES de usar os campos host.

2. SELECT INTO com resultado de múltiplas linhas — SQLCODE -811

COBOLSELECT INTO retornando mais de uma linha
           EXEC SQL
               SELECT VL_SALDO INTO :VL-SALDO
                 FROM SCHEMA1.CONTA
                WHERE CD_STATUS = 'A'   -- pode retornar milhares!
           END-EXEC
                 * SQLCODE -811: "resultado do SELECT INTO é mais de uma linha"
                 * ✅ Se WHERE pode retornar múltiplas linhas → use cursor.

3. Esquecer o indicador de nulo — SQLCODE -305

COBOLColuna nullable sem indicador
           EXEC SQL
               SELECT VL_LIMITE INTO :VL-LIMITE  -- VL_LIMITE é nullable
                 FROM SCHEMA1.CONTA WHERE NR_CONTA = :NR-CONTA
           END-EXEC
                 * SQLCODE -305 se VL_LIMITE contiver NULL no banco
                 * ✅ Sempre use indicador: :VL-LIMITE :IND-VL-LIMITE

4. COMMIT dentro de cursor sem WITH HOLD

COBOLCOMMIT fecha o cursor — SQLCODE -501 no FETCH seguinte
           EXEC SQL OPEN CUR-PROC   END-EXEC
           PERFORM UNTIL WS-FIM = 'S'
               EXEC SQL FETCH CUR-PROC INTO :NR-CONTA END-EXEC
               PERFORM PROCESSA
               EXEC SQL COMMIT END-EXEC      *❌ fecha CUR-PROC!
               *próximo FETCH → SQLCODE -501 (cursor fechado)
           END-PERFORM
                 * ✅ Declare o cursor WITH HOLD para manter aberto após COMMIT.

5. SQLCODE -818 — timestamp mismatch

DiagnósticoCausa e solução do -818
  SQLCODE -818: o timestamp do DBRM não corresponde ao load module.

  Causa: o programa foi pré-compilado e o DBRM resultante foi vinculado
  (BIND), mas depois o fonte foi recompilado sem novo BIND —
  ou vice-versa.

  Solução: refazer o build completo:
    1. Pré-compilação (gera novo DBRM)
    2. Compilação COBOL
    3. Link-edit
    4. BIND do novo DBRM
  Todos os quatro passos devem usar o mesmo código fonte.