"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" USING SQL TO PRINT MULTI-COLUMN AGING REPORTS """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" PRODUCT : R:BASE VERSION : 3.1 CATEGORY : PROGRAMMING SUBCATEGORY : TOOLS """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" From Bill Downall, 5411 White Willow Court, Indianapolis, IN 46254- 9633. Bill is a database consultant and educator. You can reach him at 317-297-3810, or through David M. Blocker and Associates, at 617-784- 1919. The January/February 1991 R:BASE EXCHANGE showed you one technique for producing an aged accounts receivable report--or any report that needs to print summaries of several rows horizontally across the page. This article teaches you how to use SQL to do the same thing. SQL Technique Overview """""""""""""""""""""" In this article, you'll use the following SQL techniques to prepare the data: o SUM function (one of several SELECT functions) and logical (IFGT and IFLT) SuperMath functions--you'll use these to create totals of numeric columns or expressions across a group of rows. SUM gets the totals while IFGT and IFLT create the groups (print columns) that will be shown horizontally across the page. The IFGT and IFLT expressions used here are similar to those given in the previous article. o GROUP BY clause of the SELECT command--you'll use GROUP BY to repeat the same calculations for each client account number. o Views--you'll use views to save complex SELECT commands and to give descriptive, meaningful names to the expressions--names that will appear in subsequent SELECT commands or in the output. The example described below uses two views. The first view creates an expression and gives it a column name. The second view names that pseudo-column within an expression. After creating the views, you'll use a single SQL SELECT to create the aging report. Example Table & Data """""""""""""""""""" An aged accounts receivable report shows the ages (in days) of amounts that have been billed but not yet paid. To make comparison with the previous article easy, I based this SQL technique on the same table (ARITEMS) and columns: ACCT (account number), AMOUNT (amount owed), and TDATE (transaction date). Create the database (AGING) at the Create/Modify menu with one table named (ARITEMS), with three columns. CREATE TABLE ARITEMS (ACCT integer, AMOUNT currency, TDATE date) and to load ARITEMS with data. You will create two views as explained below. Step by Step to the Aging Report """""""""""""""""""""""""""""""" STEP 1 ====== Create the first view (AGING). The variable VREPDATE holds the report date (as of date). You must set it before creating the view. For example, you might set it by using this command: SET VAR vrepdate DATE = ('12/31/90') Create the view by using this command: CREATE VIEW aging + ( client, amount, daysout ) AS SELECT + acct, amount, (.vrepdate - tdate) + FROM aritems The view AGING is slightly different from the table ARITEMS. In place of the transaction date column (TDATE), this view creates a new pseudo-column (DAYSOUT), an INTEGER defined as the numbers of days between the report date and transaction date. The expression that defines DAYSOUT uses the variable VREPDATE. You could use .#DATE (the current system date) instead of .VREPDATE, but then you couldn't specify a different report date (ending date or "as of" date). For example, #DATE is inappropriate for the example data. The example rows are all dated between May and December 1990. Therefore, for this example, you need to set VREPDATE to December 31, 1990. The view definition contains a variable (VREPDATE), so before using the view, give VREPDATE an initial data type and value. Test the view by entering this command: SELECT * FROM aging The first few lines of output look like this: client amount daysout ---------- --------------- --------- 101 $73.00 225 102 $146.00 195 103 $219.00 213 104 $292.00 140 101 $22.00 179 STEP 2 ====== Create the second view (AGEGROUP) to create and sum the report columns by using the following command: CREATE VIEW agegroup + (Client, Total, Current_, Over_30, + Over_60, Over_90, Over_120) + AS SELECT Client, SUM (amount), + SUM (IFLT(daysout,30,amount,0)), + SUM (IFGT(daysout,29, + (IFLT(daysout,60,amount,0)),0)), + SUM (IFGT(daysout,59,+ (IFLT(daysout,90,amount,0)),0)), + SUM (IFGT(daysout,89,+ (IFLT(daysout,120,amount,0)),0)), + SUM (IFGT(daysout,119,amount, 0)) + FROM aging GROUP BY client Notice how this view's column structure is similar to that of an aging report. The column names created in the view will appear as column headings in a SELECT command. You can't name a column 30_to_60 because SQL naming conventions require all column names to begin with a letter, and you can't name a column CURRENT because that's a reserved word. The underscore character at the end of CURRENT_ makes it acceptable. Look at the expression in the definition for the CURRENT_ view column: SUM (IFLT(daysout,30,amount,0)) In the view AGING, the column DAYSOUT was defined as the number of days between billing and the report date. Now the SUM(IFLT(...)) expression calculates a zero for any row where DAYSOUT is greater than or equal to 30. If DAYSOUT is less than 30, the value of the AMOUNT column is used for the CURRENT_ column in this second view. Across the entire view, the SELECT function SUM calculates the total. For example, look at this portion: SUM (IFLT(daysout,30,amount,0)) It calculates the sum of the AMOUNT column for those rows that are current (less than 30 days old). If that was the only number you cared about, it would be easier to get with the following WHERE clause: SELECT SUM (amount) FROM aritems + WHERE daysout <= 30 But each SELECT command has only one WHERE clause. In order to build a single SELECT command that shows one sum for current rows and different sums for other age groups, you need to use the IF expressions to do the work of an internal WHERE clause. SELECT uses the IF expressions to process all the rows in the view, putting each one in the correct report column grouping and summing each group. There's a SUM function for each of the five aging periods. Several of them use complex IFGT and IFLT functions. For example, look at this SUM function: SUM (IFGT(daysout,29,+ (IFLT(daysout,60,amount,0)),0)) The third argument of the IFGT function is another IF function. In English, it says, "If DAYSOUT is greater than 30, see if it's also less than 60. If it is, add the value of the AMOUNT column. Otherwise, add zero." STEP 3 ====== To produce the final report, run a short command file similar to AGING.CMD (listed below). It initializes VREPDATE with the "as of" date, and uses a single SELECT based on the second view (AGEGROUP) to print the report shown below. The SELECT uses "=w" width specifiers to format the report columns and "=S" to provide grand totals. The capitalization used for column names in this SELECT is what shows up in the final report. The following AGING.CMD is an example: *( AGING.CMD--produced aging report.) SET VAR vrepdate DATE = ('12/31/90') FILLIN vrepdate=8 USING + 'Enter report date: ' EDIT REVERSE *( Insert commands to select print destination) CLS WRITE ' Aged + Accounts Receivable Report:' .vrepdate WRITE ' ' ; WRITE ' ' SELECT Client=6, Total=10=S, + Current_=10=S, Over_30=10=S, + Over_60=10=S, Over_90=10=S, + Over_120=10=S FROM agegroup CLEAR VAR vrepdate RETURN STEP 4 ====== Run AGING.CMD and create the report. Here's an example of the output: Aged Accounts Receivable Report: 12/31/90 Client Total Current_ Over_30 Over_60 Over_90 Over_120 ------ ---------- --------- --------- --------- --------- ---------- 101 $178.00 $0.00 $0.00 $0.00 $0.00 $178.00 102 $1,581.85 $0.00 $324.85 $0.00 $0.00 $1,257.00 103 $1,466.85 $470.85 $219.00 $0.00 $329.00 $448.00 104 $2,094.85 $908.85 $292.00 $479.00 $123.00 $292.00 ------ ---------- --------- --------- --------- --------- ---------- $5,321.55 $1,379.70 $835.85 $479.00 $452.00 $2,175.00 Further Enhancements """""""""""""""""""" To improve the look of the report, set HEADINGS OFF, and use a few WRITE statements to create your own headings. To suppress printing zero dollar values, create an R:BASE report instead of using the final SELECT. Base the report on AGEGROUP, and use IF expressions to convert the numbers to TEXT values, replacing the $0.00 values with blanks. Locate the TEXT values on detail (D) lines. The view (AGEGROUP) groups the data, and the report prints it. There will be a longer delay before the report starts to print because the view must be assembled. But once printing begins, all the summary totals have already been calculated, so R:BASE has fewer expressions to calculate while the report prints.