12
Tue, Nov
5 New Articles

How About a SFLSORT Keyword?

RPG
Typography
  • Smaller Small Medium Big Bigger
  • Default Helvetica Segoe Georgia Times

Long ago, subfiles became a main tool in 5250 screen design. There are three basic subfile styles:

  • Page Equals Size
  • Page at a Time
  • Load All

Page Equals Size (PES) subfiles are my favorite. They offer more flexibility than the other styles in that you can do positioning as well as paging both forward and backward. Above all else, PES subfiles provide fast response times for end-users because only one record at a time is loaded.

Page at a Time (PAT) or "load-as-you-go" subfiles are nearly as flexible as PES subfiles. PAT subfiles are loaded one page of records at a time. When the user presses the RollUp or PageDown key, the next page of records is written to the subfile. At this point, the subfile has two pages of records and the end-user can roll back and forward very efficiently.

Load All subfiles are great for small sets of data. Data sets that contain a few hundred to about a thousand or so records can be loaded into the subfile. Load All subfiles load the subfile with all the data and let the system handle the rolling/paging.

There is also the subfile-in-a-window technique, which is widely used to provide a prompt for fields on data entry panels.

All styles of subfile have one thing in common: the lack of the ability to sort the subfile records.

Subfile sort has been needed since about 10 minutes after the first subfile was created. The fact that IBM did not provide this capability initially and has failed to provide it at any point in the last 20+ years is yet another sign that IBM is out of touch with software developers in the OS/400 marketplace.

In a recent informal survey, I asked RPG developers if subfile sort was a priority today. The response was an overwhelming yes! So I've designed a simple interface that IBM could use to provide subfile sort to OS/400 developers.

The idea is to add a SFLSORT keyword to DDS for display files. This keyword supports two parameters. The first parameter identifies the function key used to sort the subfile in ascending order by the field over which the cursor is positioned. The optional second parameter indicates the function key used to sort the subfile in descending order by the field over which the cursor is positioned. If descending sorts are not needed, the second parameter is simply omitted.

To use the subfile sort capability, an end-user moves the display cursor into the field over which the subfile will be sorted. When the end-user presses the subfile sort function key, the system then reorders the data in the subfile accordingly.

An example of this SFLSORT keyword being specified on a typical PAT subfile is illustrated in Figure 1.

.....A*.........R.Format++++................Keywords+++++++++
     A          R DETAIL                    SFL
     A            CUSTNO         7S 0O 12  2
     A            COMPNAME      30A 0O   + 1
     A            YTDSLS        11S 2O   + 1
     A            YTDRTNS        9S 2O   + 1
     A            CURDUE         9S 20   + 2
     
.....A*.........R.Format++++................Keywords+++++++++
     A          HEADER                    SFLCTL(DETAIL)
     A                                      SFLSIZ(14)
     A                                      SFLPAG(12)
     A  33                                  SFLDSP
      32                                  SFLDSPCTL
     N33                                  SFLCLR
     N99                                  SFLEND(*MORE)
                                          SFLSORT(CA05 CA17)

Figure 1: This is how you'd use the SFLSORT keyword in a display file.

The last statement in Figure 1 contains the SFLSORT keyword. Command keys F5 and F17 are used to sort this subfile in either ascending or descending order, respectively. Obviously, there will be a subsequent requirement of sorting by multiple fields, but this simple support is better than what we've had for the last two decades, and I think it's good enough.

A second keyword, SFLSORTFLD, would be used to control which fields in the subfile detail record support sorting. If no SFLSORTFLD keyword is specified or if SFLSORTFLD(*YES) is specified, then the field is a candidate for sorting. If SFLSORTFLD(*NO) is specified, then that field is not a candidate for sorting.

Subfile Sorting Options

Of course, waiting for the SFLSORT keyword is a little like waiting to win the lottery; you have to do something in the meantime or you'll have a lot of challenges in your day-to-day life.

At least half a dozen techniques are being used today to sort the data in a subfile:

  • Load the data to both the subfile and a multiple-occurrence data structure and then sort the data in the data structure based on the desired sort field.
  • Read the data from the source data set and then write it to a work file. Then, use OPNQRYF over that work file with the keyfield being the subfile field to sort the data.
  • Use multiple logical views. Read the one that contains a keyfield over the field in which to sequence the data.
  • Use embedded SQL and dynamically build the SQL statement, including the ORDER BY clause with the PREPARE and DECLARE statements followed by a consistent FETCH statement.
  • Call the QLGSORT API to sort and then read the data from the original data set.
  • Move the data into a runtime array and use the SORTA over the array.

All of these techniques require reloading the subfile data after sorting it. My assumption with a SFLSORT keyword would be that IBM would sort the data in place so the subfile would not necessarily need to be reloaded, thereby saving time, resources, and thousands upon thousands of redundant coding techniques.

With the exception of the first technique, there really isn't much you can do about the performance or flexibility of these techniques. They all provide about the same level of flexibility; the OPNQRYF and SQL approaches are probably the best choice for an application with thousands of records in a subfile. But really, you do have to question whether a subfile containing thousands of records is really useful to an end-user.

What I would like to focus on for subfile sorting is to provide an optimized approach to the first technique: sorting the subfile data that has been mirrored in a multiple-occurrence data structure.

Preparing for Subfile Sort

The first thing you need to do to provide a mirror image of your subfile is to create a multiple-occurrence data structure with the same format as your subfile. This technique is common practice when using embedded SQL and the FETCH statement to fetch multiple records simultaneously.

Data can be loaded directly from the data structure into the subfile by using a loop and the OCCUR opcode. Typically, programmers load both the data structure and the subfile when the subfile is initially loaded and then sort the data in the data structure when requested.

To create a data structure with the same format as a subfile, use the EXTNAME keyword along with its second parameter. The first parameter of EXTNAME is the name of the file on which the data structure is based. The rarely used second parameter of EXTNAME identifies the record format name of the file to use as the data structure's format. In order to format the data structure like the subfile, specify the EXTNAME keyword as follows:


.....DName+++++++++++EUDS.......Length+TDc.Functions++++++++++++++++++ 
     D SFLSORT       E DS                  EXTNAME(WRKCUST : CUSTLIST)

The EXTNAME(WRKCUST : CUSTLIST) identifies WRKCUST as the display file name and CUSTLIST as the format or subfile name. Therefore, the SFLSORT data structure will be of the same format as the CUSTLIST subfile.

Next, you have to accommodate the records that will be stored in the subfile. We all know of the ancient limitation of 9,999 records in a subfile. This limitation has not been relaxed for a number of reasons, including the overhead involved in storing that many records as well as the unrealistic expectation that an end-user can actually use that much data at once. Also, the PES and PAT subfile techniques are much more popular than the Load All technique.

To provide space for all the subfile records, you add the OCCURS keyword to the data structure as follows:


.....DName+++++++++++EUDS.......Length+TDc.Functions++++++++++++++++++ 
     D SFLSORT       E DS                  EXTNAME(WRKCUST : CUSTLIST) 
     D                                     OCCURS(10000) 

You now have space to support up to 10,000 subfile records. Unfortunately, if your subfile is only a few dozen or even a few hundred records, allocating space for 10,000 records is a little like a commuter using a school bus to drive to work each day--lots of space but not very practical.

To avoid unnecessary storage being allocated and unused, you need to add the BASED keyword. This keyword tells the compiler that you will provide the storage for the data structure at runtime, so the compiler avoids generating code that would allocate it automatically for you. Therefore, you use only the storage you actually need.

The following illustrates the use of the BASED keyword on a multiple-occurrence data structure:


 
.....DName+++++++++++EUDS.......Length+TDc.Functions++++++++++++++++++ 
     D SFLSORT       E DS                  EXTNAME(WRKCUST : CUSTLIST) 
     D                                     OCCURS(10000) BASED(pSort) 
 

Now that you've got your data structure set up to mirror the subfile's record format, you need some way to allocate the storage for the data structure.

I've selected the CrtUsrSpcPtr() procedure in the RPG ToolKit because it is the easiest way to do this. This procedure (create user space and return pointer) creates a user space in QTEMP with a random name and returns a pointer to the user space. So, to allocate the space for the data structure, you only need to add one line of code to the Calculation specifications, as follows:


.....C..n01..............OpCode(ex)Extended-factor2+++++++++++++++ 
     C                   eval      pSort = CrtUsrSpcPtr('*') 

The user space created by the CrtUsrSpcPtr() procedure acts as the memory for the multiple-occurrence data structure. It automatically grows as more occurrences are accessed within the data structure.

Sorting the Subfile

Sorting the subfile doesn't occur with today's technology, so the only thing you can do is sort the data structure that you've set up to sort the subfile data. Remember, you would add an occurrence to the data structure in the same way you would write out a subfile detail record, so the data structure mirrors the data in the subfile.

Sorting the data structure occurrences, as it turns out, is remarkably easy. There is a C language runtime procedure called QuickSort (QSORT) that will sort an array of elements. Since a multiple-occurrence data structure is similar to an array, it turns out QSORT can also sort data structure occurrences.

The prototype for the QSORT procedure is illustrated below:

     D QuickSort       PR                  ExtProc('qsort') 
     D  szSortDS                       *   VALUE 
     D  NbrElems                     10U 0 VALUE 
     D  SizeOfElem                   10U 0 VALUE 
     D  CompProc                       *   VALUE ProcPtr  

QSORT works by arranging the elements of an array based on the value returned from a user-written procedure. That procedure is specified on the fourth parameter of the call to qsort().

QSORT Parameters

The first parameter is the array or multiple-occurrence data structure that will be sorted. Since this is a C language procedure, you have to pass in the address of the array or data structure using the %ADDR() built-in function.

The second and third parameters indicate the number of elements in the array or data structure and the length of a single element, respectively.

The fourth and final parameter accepts a procedure name (actually the address of a procedure) that is called to do the actual comparing for the QSORT procedure.

This procedure must accept two parameters. Both are pointer data types, passed by value. The procedure must return a 10i0 (int4) value that indicates the results of the comparison. The prototype for this procedure is illustrated below:


 
     D myCompare       PR            10I 0 
     D  pValue1                        *   Value 
     D  pValue2                        *   Value 

The procedure and parameter names are names you create. When the procedure is called, it is passed two parameters. The first parameter is an element of the array or data structure that should be compared with the second parameter, which is also an element of the array or data structure.

The procedure must return 1, -1, or 0. A return value of 1 means the value on the first parameter is greater than the value of the second parameter. A return value of -1 means the value of the first parameter is less than the value of the second parameter. A return value of zero indicates that the two values are equal.

The procedure can be very simple (do the comparison and return the result) or very complex (map the input parameters to data structures and compare individual subfields of the data structure and return the result).

To sort the subfile, you call qsort() and pass to it the data structure and the compare procedure's names, as follows:

C                   callp     QuickSort(%addr(SFLSORT): nElems : %size(SFLSORT) : 
C                                       %paddr('MYCOMPARE')) 

Note that the name of the compare procedure is in all uppercase and enclosed in quotes within the %PADDR() built-in function. The %PARRD() built-in function returns the address to the specified procedure name. Since RPG procedures are, by default, converted to all uppercase when the source is compiled, you need to specify the procedure name in all capital letters.

The only thing you need to keep track of is the current number of elements in the array or data structure. In the example above, you're using the nElems variable to store the current number of elements. After all, if you declare a multiple-occurrence data structure with 10,000 elements and use just 50 of them, sorting the entire data structure would not only return bad results, it would be inefficient.

By specifying the number of current elements, you get a more granular level of control than you do with native RPG IV interfaces, such as the SORTA operation code, which can only sort the entire array.

The final component is the compare procedure. A simple version of this procedure is illustrated in Figure 2.

P myCompare       B                   EXPORT            
D myCompare       PI            10I 0
D  pValue1                        *   Value
D  pValue2                        *   Value

D CompareDS1      DS                  LikeDS(SFLSORT)
D                                     Based(pValue1)
D CompareDS2      DS                  LikeDS(SFLSORT)
D                                     Based(pValue2)

C                   if        CompareDS1.CompName > CompareDS2.CompName 
C                   return    1
C                   elseif    CompareDS1.CompName < CompareDS2.CompName 
C                   return    -1
C                   endif
C                   return    0
P myCompare       E                    

Figure 2: This is the Compare procedure for qsort().

In Figure 2, the MYCOMPARE procedure maps the input parameters to two local data structures, COMPAREDS1 and COMPAREDS2. By mapping the input parameters to these data structures, you can easily access the subfields of the data structures to do the comparison. Note the use of the LIKEDS keyword to create new local data structures with the same format as the SFLSORT data structure. The BASED keywords cause the storage for the two data structures to be that of the data passed to the program as pValue1 and pValue2.

Subfile Sorting: Final Touches

To allow sorting by an individual field within a subfile, you simply store the field name over which you would like to sort the subfile in a global variable. Then, in the MYCOMPARE procedure, you check the value of that field and select the appropriate field for comparison. For example, Figure 3 contains a complete RPG IV program for doing subfile sort. This code illustrates sample routines for sorting a subfile. There's no need to illustrate a complete working program.

     OPTION(*NODEBUGIO:*SRCSTMT) 
      /IF DEFINED(*CRTBNDRPG)
     BNDDIR('QC2LE' : 'TOOLKIT/TOOLKIT')
     DFTACTGRP(*NO)  ACTGRP('COZZI')
      /ENDIF

     FCustMast  IF   E           DISK    USROPN
     FSFLSORT   CF   E             WORKSTN SFILE(DETAIL: RRN)
     F                                     INFDS(WSDS)
     

     D WSDS            DS
     D  FKey                          1A   Overlay(WSDS:369)

     D SFLSORTDS     E DS                  EXTNAME(SFLSORT : DETAIL)
     D                                     OCCURS(10000) BASED(pSortSpace)

     D m_bAscend       S              1N   Inz(*OFF)
     D m_szSortField   S             10A   Inz

     D nElemCount      S             10I 0 Inz(0)
     D pSortSpace      S               *   

      /COPY TOOLKIT/QCPYSRC,space

     D QuickSort       PR                  ExtProc('qsort')
     D  szSortDS                       *   VALUE
     D  NbrElems                     10U 0 VALUE
     D  SizeOfElem                   10U 0 VALUE
     D  CompProc                       *   VALUE ProcPtr 

     D SortBy          PR       
     D  pSortDS                        *   VALUE
     D  nElems                       10I 0 Const
     D  nElemSize                    10I 0 Const
     D  szField                      10A   Const                                
     D  szSeq                         1A   Const OPTIONS(*NOPASS)

     D myCompare       PR            10I 0
     D  pValue1                        *   Value
     D  pValue2                        *   Value
     
     C                   eval      *INLR = *ON
      **  Call the RPG ToolKit routine to create a user space
      **  in QTEMP and return its pointer.
.....C..n01..............OpCode(ex)Extended-factor2+++++++++++++++
     C                   eval      pSortSpace = CrtUsrSpacePtr()

     C                   write     header
     C     1             occur     SFLSORTDS
     C                   READ      CUSTREC       

     C                   DOW       NOT %EOF
     C                   eval      nElemCount = nElemCount + 1
     C     nElemCount    occur     SFLSORTDS
     C                   eval      name = CompName
     C                   eval      ytdbal = YTDSales - YTDCredits
     C                   add       1             RRN
     C                   write     detail
     C                   read      CustRec
     C                   enddo

     C                   dou       FKey = F3
     C                   eval      *IN33 = (RRN > 0)
     C                   exfmt     Header
     C                   select
     C                   When      FKey = F5
       // Sort Ascending                   
     C                   Callp     SortBy(%addr(sflsortDS) : nElemCount : 
     C                                    %size(sflsortds) : sortfld : 'A')
     C                   Exsr      ReloadSFL
     C                   When      FKey = F17
       // Sort Descending
     C                   Callp     SortBy(%addr(sflsortDS) : nElemCount : 
     C                                    %size(sflsortds) : sortfld : 'D')
     C                   Exsr      ReloadSFL
     C                   endsl
     C                   enddo

     CSR   ReLoadSFL     BegSR           
     C                   if        nElemCount <= 0
     C                   LeaveSR
     C                   endif
     C                   eval      RRN = 0
     C                   eval      *IN33 = *OFF
     C                   write     Header
     C                   for       RRN = 1 to nElemCount
     C     RRN           Occur     SFLSortDS
     C                   Write     Detail
     C                   endfor
     CSR   EndReLoadSFL  EndSR

     P SortBy          B                   
     D SortBy          PI       
     D  pSortDS                        *   VALUE
     D  nElems                       10I 0 Const
     D  nElemSize                    10I 0 Const
     D  szField                      10A   Const                                
     D  szSeq                         1A   Const OPTIONS(*NOPASS)
 
     C                   if        %Parms >= 5
     C                   eval      m_bAscend = (szSeq = 'A')
     C                   else
     C                   eval      m_bAscend = *ON
     C                   endif

     C                   eval      m_szSortField = szField

     C                   callp     QuickSort(pSortDS: nElems : nElemSize : 
     C                                       %paddr('MYCOMPARE'))
     C                                       
     C                   return
     P SortBy          E
     
     P myCompare       B                   EXPORT                
     D myCompare       PI            10I 0
     D  pValue1                        *   Value
     D  pValue2                        *   Value

     D szCompDS1       DS                  LikeDS(SFLSORTDS)
     D                                     Based(pValue1)
     D szCompDS2       DS                  LikeDS(SFLSORTDS)
     D                                     Based(pValue2)
     C                   Select
     C                   When      m_szSortField = 'CUSTNO' or 
     C                               m_szSortField = ' '
     C                   if        szCompDS1.CustNo > szCompDS2.CustNo
     C                   return    1
     C                   elseif    szCompDS1.CustNo < szCompDS2.CustNo
     C                   return    -1
     C                   else
     C                   return    0
     C                   endif
     
     C                   When      m_szSortField = 'NAME'
     C                   if        szCompDS1.Name > szCompDS2.Name 
     C                   return    1
     C                   elseif    szCompDS1.Name < szCompDS2.Name 
     C                   return    -1
     C                   else
     C                   return    0
     C                   endif

     C                   When      m_szSortField = 'YTDSALES'
     C                   if        szCompDS1.YtdSales > szCompDS2.YtdSales 
     C                   return    1
     C                   elseif    szCompDS1.YtdSales < szCompDS2.YtdSales 
     C                   return    -1
     C                   else
     C                   return    0
     C                   endif
     
     C                   When      m_szSortField = 'YTDCREDITS'
     C                   if        szCompDS1.YtdCredits > szCompDS2.YtdCredits 
     C                   return    1
     C                   elseif    szCompDS1.YtdCredits < szCompDS2.YtdCredits 
     C                   return    -1
     C                   else
     C                   return    0
     C                   endif
     
     C                   When      m_szSortField = 'YTDBALANCE'
     C                   if        szCompDS1.YtdBalance > szCompDS2.YtdBalance 
     C                   return    1
     C                   elseif    szCompDS1.YtdBalance < szCompDS2.YtdBalance 
     C                   return    -1
     C                   else
     C                   return    0
     C                   endif
     C                   endsl

     C                   return    0
     P myCompare       E 

Figure 3: This RPG IV code does subfile sorting.

Bob Cozzi has been programming in RPG since 1978. Since then, he has written many articles and several books, including The Modern RPG Language--the most widely used RPG reference manual in the world. Bob is also a very popular speaker at industry events such as RPG World and is the author of his own Web site and of the RPG ToolKit, an add-on library for RPG IV programmers.

BOB COZZI

Bob Cozzi is a programmer/consultant, writer/author, and software developer. His popular RPG xTools add-on subprocedure library for RPG IV is fast becoming a standard with RPG developers. His book The Modern RPG Language has been the most widely used RPG programming book for more than a decade. He, along with others, speaks at and produces the highly popular RPG World conference for RPG programmers.


MC Press books written by Robert Cozzi available now on the MC Press Bookstore.

RPG TnT RPG TnT
Get this jam-packed resource of quick, easy-to-implement RPG tips!
List Price $65.00

Now On Sale

The Modern RPG IV Language The Modern RPG IV Language
Cozzi on everything RPG! What more could you want?
List Price $99.95

Now On Sale

BLOG COMMENTS POWERED BY DISQUS

LATEST COMMENTS

Support MC Press Online

$

Book Reviews

Resource Center

  • SB Profound WC 5536 Have you been wondering about Node.js? Our free Node.js Webinar Series takes you from total beginner to creating a fully-functional IBM i Node.js business application. You can find Part 1 here. In Part 2 of our free Node.js Webinar Series, Brian May teaches you the different tooling options available for writing code, debugging, and using Git for version control. Brian will briefly discuss the different tools available, and demonstrate his preferred setup for Node development on IBM i or any platform. Attend this webinar to learn:

  • SB Profound WP 5539More than ever, there is a demand for IT to deliver innovation. Your IBM i has been an essential part of your business operations for years. However, your organization may struggle to maintain the current system and implement new projects. The thousands of customers we've worked with and surveyed state that expectations regarding the digital footprint and vision of the company are not aligned with the current IT environment.

  • SB HelpSystems ROBOT Generic IBM announced the E1080 servers using the latest Power10 processor in September 2021. The most powerful processor from IBM to date, Power10 is designed to handle the demands of doing business in today’s high-tech atmosphere, including running cloud applications, supporting big data, and managing AI workloads. But what does Power10 mean for your data center? In this recorded webinar, IBMers Dan Sundt and Dylan Boday join IBM Power Champion Tom Huntington for a discussion on why Power10 technology is the right strategic investment if you run IBM i, AIX, or Linux. In this action-packed hour, Tom will share trends from the IBM i and AIX user communities while Dan and Dylan dive into the tech specs for key hardware, including:

  • Magic MarkTRY the one package that solves all your document design and printing challenges on all your platforms. Produce bar code labels, electronic forms, ad hoc reports, and RFID tags – without programming! MarkMagic is the only document design and print solution that combines report writing, WYSIWYG label and forms design, and conditional printing in one integrated product. Make sure your data survives when catastrophe hits. Request your trial now!  Request Now.

  • SB HelpSystems ROBOT GenericForms of ransomware has been around for over 30 years, and with more and more organizations suffering attacks each year, it continues to endure. What has made ransomware such a durable threat and what is the best way to combat it? In order to prevent ransomware, organizations must first understand how it works.

  • SB HelpSystems ROBOT GenericIT security is a top priority for businesses around the world, but most IBM i pros don’t know where to begin—and most cybersecurity experts don’t know IBM i. In this session, Robin Tatam explores the business impact of lax IBM i security, the top vulnerabilities putting IBM i at risk, and the steps you can take to protect your organization. If you’re looking to avoid unexpected downtime or corrupted data, you don’t want to miss this session.

  • SB HelpSystems ROBOT GenericCan you trust all of your users all of the time? A typical end user receives 16 malicious emails each month, but only 17 percent of these phishing campaigns are reported to IT. Once an attack is underway, most organizations won’t discover the breach until six months later. A staggering amount of damage can occur in that time. Despite these risks, 93 percent of organizations are leaving their IBM i systems vulnerable to cybercrime. In this on-demand webinar, IBM i security experts Robin Tatam and Sandi Moore will reveal:

  • FORTRA Disaster protection is vital to every business. Yet, it often consists of patched together procedures that are prone to error. From automatic backups to data encryption to media management, Robot automates the routine (yet often complex) tasks of iSeries backup and recovery, saving you time and money and making the process safer and more reliable. Automate your backups with the Robot Backup and Recovery Solution. Key features include:

  • FORTRAManaging messages on your IBM i can be more than a full-time job if you have to do it manually. Messages need a response and resources must be monitored—often over multiple systems and across platforms. How can you be sure you won’t miss important system events? Automate your message center with the Robot Message Management Solution. Key features include:

  • FORTRAThe thought of printing, distributing, and storing iSeries reports manually may reduce you to tears. Paper and labor costs associated with report generation can spiral out of control. Mountains of paper threaten to swamp your files. Robot automates report bursting, distribution, bundling, and archiving, and offers secure, selective online report viewing. Manage your reports with the Robot Report Management Solution. Key features include:

  • FORTRAFor over 30 years, Robot has been a leader in systems management for IBM i. With batch job creation and scheduling at its core, the Robot Job Scheduling Solution reduces the opportunity for human error and helps you maintain service levels, automating even the biggest, most complex runbooks. Manage your job schedule with the Robot Job Scheduling Solution. Key features include:

  • LANSA Business users want new applications now. Market and regulatory pressures require faster application updates and delivery into production. Your IBM i developers may be approaching retirement, and you see no sure way to fill their positions with experienced developers. In addition, you may be caught between maintaining your existing applications and the uncertainty of moving to something new.

  • LANSAWhen it comes to creating your business applications, there are hundreds of coding platforms and programming languages to choose from. These options range from very complex traditional programming languages to Low-Code platforms where sometimes no traditional coding experience is needed. Download our whitepaper, The Power of Writing Code in a Low-Code Solution, and:

  • LANSASupply Chain is becoming increasingly complex and unpredictable. From raw materials for manufacturing to food supply chains, the journey from source to production to delivery to consumers is marred with inefficiencies, manual processes, shortages, recalls, counterfeits, and scandals. In this webinar, we discuss how:

  • The MC Resource Centers bring you the widest selection of white papers, trial software, and on-demand webcasts for you to choose from. >> Review the list of White Papers, Trial Software or On-Demand Webcast at the MC Press Resource Center. >> Add the items to yru Cart and complet he checkout process and submit

  • Profound Logic Have you been wondering about Node.js? Our free Node.js Webinar Series takes you from total beginner to creating a fully-functional IBM i Node.js business application.

  • SB Profound WC 5536Join us for this hour-long webcast that will explore:

  • Fortra IT managers hoping to find new IBM i talent are discovering that the pool of experienced RPG programmers and operators or administrators with intimate knowledge of the operating system and the applications that run on it is small. This begs the question: How will you manage the platform that supports such a big part of your business? This guide offers strategies and software suggestions to help you plan IT staffing and resources and smooth the transition after your AS/400 talent retires. Read on to learn: