MainframeSupports
tip uge 45/2019:

Dette tip er opdateret siden første udgivelse, da en læser har gjort mig opmærksom på, at den orindelige FILTER-MATCH ikke kunne håndtere gentagne forekomster af en tegnstreng fra :WILDCARD i :HAYSTACK. Et eksempel er 'AACB' LIKE '%A_B%', som fejlede i den oprindelige FILTER-MATCH, fordi A optræder to gange, men kun findes een gang af den oprindelige FILTER-MATCH.

For nylig opdagede en af mine kolleger følgende SQL statement i et COBOL program:

SELECT 0
FROM TABLE_WITH_EXACTLY_ONE_ROW
WHERE :HAYSTACK LIKE :WILDCARD

Og vedkommende spurgte mig, hvad foregår der her?? Efter at have sundet mig lidt gik det op for mig, at denne stump SQL undersøger om indholdet af hostvariablen :HAYSTACK matcher wildcard værdien i :WILDCARD. Dette SQL kald kan selvfølgelig spares fuldstændig væk, hvis man koder en stump wildcard kode i sit COBOL program. Dette sparer turen over i DB2 og opslaget på tabellen, som DB2 laver uanset, om det er nødvendigt eller ej.

Jeg søgte derefter på internettet efter en stump COBOL-kode, der kunne erstatte SQL kaldet. Jeg kunne ikke umiddelbart finde noget, der præcist simulerer LIKE, så jeg kastede mig ud i opgaven:

...
       WORKING-STORAGE SECTION.
      *
       01  SQLCODE                    PIC S9(9) BINARY.
       01  FILTER-STRING.
         49  FILTER-LEN               PIC S9(4) BINARY.
         49  FILTER                   PIC X(50).
       01  FILTER-POS                 PIC S9(4) BINARY.
       01  MATCH-POS                  PIC S9(9) BINARY.
       01  MATCH.
         49  MATCH-LENGTH             PIC S9(9) BINARY.
         49  MATCH-DATA               PIC X(50000).
       01  WILDCARD-POS               PIC S9(4) BINARY.
       01  SUB-LEN                    PIC S9(4) BINARY.
       01  PERCENT                    PIC X(1).
       01  PERCENT-POS                PIC S9(4) BINARY.
       01  REST-LEN                   PIC S9(9) BINARY.
       01  RESTART-POS                PIC S9(9) BINARY.
...
       PROCEDURE DIVISION.
           MOVE 54 TO MATCH-LENGTH
           MOVE 'THIS IS A TEST OF SQL LIKE CODED IN COBOL AND IT WORKS'
             TO MATCH-DATA(1:MATCH-LENGTH)
           MOVE 14 TO FILTER-LEN
           MOVE 'THIS%SQL%WORK_'
             TO FILTER(1:FILTER-LEN)

           PERFORM FILTER-MATCH

           IF SQLCODE = 0
             DISPLAY 'IT WAS A MATCH!'
           ELSE
             DISPLAY 'NO MATCH FOUND'
           GOBACK
           .
       FILTER-MATCH.
           MOVE 0 TO SQLCODE
           IF FILTER-LEN NOT = MATCH-LENGTH
           OR FILTER(1:FILTER-LEN) NOT = MATCH-DATA(1:FILTER-LEN)
             IF FILTER(1:FILTER-LEN) = SPACES
             OR MATCH-DATA(1:MATCH-LENGTH) = SPACES
               MOVE 100 TO SQLCODE
             ELSE
               MOVE 'N' TO PERCENT
               MOVE 0 TO RESTART-POS
               MOVE 1 TO FILTER-POS
               MOVE 1 TO MATCH-POS
               PERFORM UNTIL FILTER-POS > FILTER-LEN
                          OR MATCH-POS > MATCH-LENGTH
                          OR SQLCODE = 100
                 MOVE 1 TO SUB-LEN
                 IF FILTER(FILTER-POS:1) = '%'
                   MOVE 'Y' TO PERCENT
                   MOVE FILTER-POS TO PERCENT-POS
                 ELSE
                   IF FILTER(FILTER-POS:1) NOT = '_'
                     IF PERCENT = 'Y'
                       COMPUTE WILDCARD-POS = FILTER-POS + 1
                       PERFORM UNTIL WILDCARD-POS > FILTER-LEN
                                  OR FILTER(WILDCARD-POS:1) = '%' OR '_'
                         ADD 1 TO WILDCARD-POS
                       END-PERFORM
                       COMPUTE SUB-LEN = WILDCARD-POS - FILTER-POS
                       MOVE MATCH-POS TO RESTART-POS
                       COMPUTE REST-LEN = MATCH-LENGTH - MATCH-POS + 1
                       INSPECT
                         MATCH-DATA(MATCH-POS:REST-LEN)
                         TALLYING MATCH-POS
                         FOR CHARACTERS BEFORE
                         FILTER(FILTER-POS:SUB-LEN)
                       IF MATCH-POS = RESTART-POS + REST-LEN
                         MOVE 100 TO SQLCODE
                       END-IF
                     END-IF
                     IF MATCH-DATA(MATCH-POS:SUB-LEN)
                     NOT = FILTER(FILTER-POS:SUB-LEN)
                       IF RESTART-POS NOT = 0
                         MOVE 1 TO SUB-LEN
                         MOVE RESTART-POS TO MATCH-POS
                         COMPUTE FILTER-POS = PERCENT-POS - SUB-LEN
                         MOVE 0 TO RESTART-POS
                       ELSE
                         MOVE 100 TO SQLCODE
                       END-IF
                     END-IF
                     MOVE 'N' TO PERCENT
                   END-IF
                   ADD SUB-LEN TO MATCH-POS
                 END-IF
                 ADD SUB-LEN TO FILTER-POS
               END-PERFORM
               IF PERCENT = 'N' AND SQLCODE = 0
                 IF MATCH-POS = MATCH-LENGTH
                 AND FILTER-POS > FILTER-LEN
                 OR MATCH-POS > MATCH-LENGTH
                 AND FILTER-POS = FILTER-LEN
                 AND FILTER(FILTER-POS:1) NOT = '%'
                   MOVE 100 TO SQLCODE
                 END-IF
               END-IF
             END-IF
           END-IF
           .

Ovenstående kodestumper er sakset fra det program, jeg skrev, som også indeholder kode til at sammenligne SQL LIKE med ovenstående COBOL paragraf. På den måde var det lettere for mig at sikre, at COBOL koden rent faktisk virker korrekt. Der kan måske stadig forekomme fejl, så brug ovenstående kode med forsigtighed. Bemærk brugen af substring expressions. COBOL er rigtig god til at sluge CPU, hvis der bliver arbejdet på den fulde streng. Resultatet af wildcard sammenligningen gemmes i SQLCODE for at være kompatibel med den SQL som paragraffen erstatter.

Efter at have sikret mig, at funktionaliteten er korrekt (det håber jeg virkelig, den er), kørte jeg nogen performance tests. Paragraffen FILTER-MATCH kører 20 til 30 gange hurtigere end at kalde DB2 for at gøre det samme. I det oprindelige program blev SQL kaldet udført millioner af gange dagligt, så det resulterede i en god besparelse.

En sidste vigtig detalje er, at de to strenge ikke matcher, hvis de ikke indeholder wildcards og ikke er lige lange. det betyder for eksempel, at to blanke strenge, der ikke indeholder lige mange blank-tegn, ikke matcher hinanden. Sådan virker COBOL normalt ikke, men det gør SQL LIKE.

Forrige danske tip        Last tip in english        Tip oversigten