• The Four Hundred
  • Subscribe
  • Media Kit
  • Contributors
  • About Us
  • Contact
Menu
  • The Four Hundred
  • Subscribe
  • Media Kit
  • Contributors
  • About Us
  • Contact
  • Conditional SQL I/O, Take 2

    March 20, 2017 Ted Holt

    Bob writes, “Hey, Ted! I hope you can teach an old dog a new trick. I am trying to replace the CHAIN operation with SQL. I chain once to a file to read a certain record. If that record is not found, I chain again to retrieve a default record. How can I make SQL do a second read to the same file?”

    This is not a hard thing to do, as SQL has no problem joining more than once to the same table. I’ll show you two methods to retrieve your data. The first method is the easier one, but you can only use it if the table cannot store null values in the columns (fields) that you’re retrieving. (Such is the case in many IBM i shops.) The second method works whether or not the columns can contain null values.

    Let’s set up the situation by defining two physical files – a customer master file (CUSTMAST) and a customer addresses file (CUSTADDR). The former has one record per customer.

    A                                      UNIQUE
    A          R MASTREC
    A            ID             3  0
    A            NAME          24
    A          K ID

    The customer address file may have one or two rows per customer, each distinguished by the address type code.

    A                                      UNIQUE
    A          R ADDRREC
    A            ID             3  0
    A            TYPE           1
    A            ADDRESS1      16
    A            ADDRESS2      16
    A            CITY          16
    A            STATE          2
    A            ZIPCODE       10
    A          K ID
    A          K TYPE

    The address type has the value 1 for a billing address and 2 for a shipping address. To keep this example as simple as possible, assume that all customers have a billing address, but only some customers have a shipping address. If a customer has no shipping address, we ship to the billing address.

    Let’s read the customer master and list the shipping addresses. To simplify the example even more, I omit the city, state, and ZIP (postal) code. (They would be handled in the same way the two address line columns are handled.)

    Here’s the customer address data:

    Here’s some RPG code that is typical of many shops:

    FCustMast  if   e           k disk
    FCustAddr  if   e           k disk
    Fqsysprt   o    f  132        printer
    
    C     AddrKey       klist
    C                   kfld                    ID
    C                   kfld                    AddrType          1
    C
    C                   read      MastRec                                90
    C                   dow       *in90 = *off
    C                   eval      AddrType = '2'
    C     AddrKey       chain     AddrRec                            91
    C                   if        *in91 = *on
    C                   eval      AddrType = '1'
    C     AddrKey       chain     AddrRec                            91
    C                   endif
    C                   if        *in91 = *on
    C                   clear                   Address1
    C                   clear                   Address2
    C                   endif
    C                   except    pline
    C                   read      MastRec                                90
    C                   enddo
    C
    C                   eval      *inlr = *on
    
    Oqsysprt   e            pline
    O                       ID
    O                       Name             +   1
    O                       Address1         +   1
    O                       Address2         +   1

    Notice the two CHAIN operations to CUSTADDR. If the first CHAIN does not find the shipping address, the program chains again to retrieve the billing address. Now, how do we do the same sort of thing using SQL?

    Method 1

    The first method only works if the columns cannot contain null values. Here’s the query:

    select m.ID, m.Name,
           coalesce (s.Address1, b.Address1) as Address1,
           coalesce (s.Address2, b.Address2) as Address2
      from CustMast as m
      left join CustAddr as s
        on (m.ID = s.ID and s.type = '2')
      left join CustAddr as b
        on (m.ID = b.ID and b.type = '1')

    Notice that the customer address table is joined twice. The first join retrieves the shipping address (type 2). The second join retrieves the billing address (type 1). If the shipping address does not exist, the columns from the type 2 row will be null.

    Notice also the COALESCE functions. COALESCE selects the first non-null value from a list. Since the Address1 and Address2 columns cannot store null values, the query selects the billing address if the shipping address is not in the table.

    As I said, this query will not work if null values are permitted in the address fields. Here’s what the query would return if null were allowed and used in address line columns:

    Notice Dostoyevsky. His first address line comes from the shipping record, but the second address line comes from the billing record. We need a way to determine whether the shipping address exists. That turns out to be very easy to do.

    Method 2

    Here are the same customer master and customer address tables defined the modern way.

    create table CustMast 
       ( ID   dec(3)   not null,
         Name varchar(24),             
       primary key (ID))
    
    create table CustAddr  
       ( ID         dec (3)       not null,
         Type       char(1)       not null,
         Address1   varchar(16),           
         Address2   varchar(16),           
         City       varchar(16),
         State      char(2),    
         ZipCode    char(10),   
       primary key (ID, Type))

    The key columns cannot have a null value, but the other columns may. Here’s the same customer address data, but this time with nulls:

    Here’s the query:

    select m.ID, m.Name,
           case when s.ID is not null
                   then s.Address1
                   else b.Address1 end as Address1,
           case when s.ID is not null
                   then s.Address2
                   else b.Address2 end as Address2
      from CustMast as m
      left join CustAddr as s
        on (m.ID = s.ID and s.type = '2')
      left join CustAddr as b
        on (m.ID = b.ID and b.type = '1')

    Instead of COALESCE functions, I use CASE expressions, but notice what the CASE expressions test. To make this work, you must test one of the columns of the primary key from the row that may or may not exist. I chose to test the ID column from the shipping address (s.ID). This test tells me whether the shipping address exists because the only way a column of a primary key can be null is when the join does not find a match.

    Here are the results.

    Notice that Dostoyevsky’s address came out right this time.

    This scenario is similar to a one I wrote about a few years ago, but in that tip, the second join was to a different file and I didn’t consider null values.

    RELATED STORY

    Conditional SQL I/O

    Share this:

    • Reddit
    • Facebook
    • LinkedIn
    • Twitter
    • Email

    Tags: Tags: Guru, IBM i, SQL

    Sponsored by
    Rocket Software

    Meet digital age demands while maximizing your IT investment.

    Future-proof your mission-critical applications with Rocket® Solutions for IBM® i that keep your business ahead of the curve.

    Learn More

    Share this:

    • Reddit
    • Facebook
    • LinkedIn
    • Twitter
    • Email

    One On One With HelpSystems’ Debbie Saugen IBM i Has A Cup Of Coffee With Watson

    2 thoughts on “Conditional SQL I/O, Take 2”

    • Rusty Gadberry says:
      March 20, 2017 at 1:17 pm

      Ted, doing it this way you don’t have to be concerned about nulls.

      select m.ID, m.Name, a.address1, a.address2
      from CustMast as m
      left join table( select address1, address2
      from CustAddr a
      where a.ID = m.ID
      and a.type in (‘1’, ‘2’)
      order by a.ID, a.type desc
      fetch first 1 rows only
      ) as a on 1=1
      where m.ID = someID

      Reply
    • Ted Holt says:
      March 29, 2017 at 10:31 am

      That’s a good idea too, Rusty, although things like “on 1=1” always look strange to me.

      Reply

    Leave a Reply Cancel reply

TFH Volume: 27 Issue: 18

This Issue Sponsored By

  • BCD Software
  • Profound Logic Software
  • COMMON
  • Linoma Software
  • Northeast User Groups Conference

Table of Contents

  • The Lowdown On Pricing For The Power S812 Mini
  • IBM i Has A Cup Of Coffee With Watson
  • Conditional SQL I/O, Take 2
  • One On One With HelpSystems’ Debbie Saugen
  • The Dark Side Of Automated Systems Is Bright

Content archive

  • The Four Hundred
  • Four Hundred Stuff
  • Four Hundred Guru

Recent Posts

  • POWERUp 2025 –Your Source For IBM i 7.6 Information
  • Maxava Consulting Services Does More Than HA/DR Project Management – A Lot More
  • Guru: Creating An SQL Stored Procedure That Returns A Result Set
  • As I See It: At Any Cost
  • IBM i PTF Guide, Volume 27, Number 19
  • IBM Unveils Manzan, A New Open Source Event Monitor For IBM i
  • Say Goodbye To Downtime: Update Your Database Without Taking Your Business Offline
  • i-Rays Brings Observability To IBM i Performance Problems
  • Another Non-TR “Technology Refresh” Happens With IBM i TR6
  • IBM i PTF Guide, Volume 27, Number 18

Subscribe

To get news from IT Jungle sent to your inbox every week, subscribe to our newsletter.

Pages

  • About Us
  • Contact
  • Contributors
  • Four Hundred Monitor
  • IBM i PTF Guide
  • Media Kit
  • Subscribe

Search

Copyright © 2025 IT Jungle