Sales Compared to Last Year using Date Math
Hi Howard, I have a question about SQL.
Suppose I have a table containing sales for each day of a year.
SDATE |
SALETOTAL |
11/01/2000 |
5000 |
11/02/2000 |
5500 |
11/02/2001 |
7000 |
11/03/2001 |
5800 |
11/04/2001 |
6000 |
Then I have another table that describes our
accounting year by week
PHYR |
PHWK |
SD |
ED |
2000 |
40 |
2000/11/01 |
2000/11/07 |
2001 |
40 |
2001/11/03 |
2001/11/10 |
Now I want a query that will yield results like
the following, comparing the sales for a day in this year to the
corresponding accounting day from last years week period.
Current Year |
Sale |
Last Year |
Sale |
2001/11/03 |
5800 |
2000/11/01 |
5000 |
2001/11/04 |
6000 |
2000/11/02 |
5500 |
How I can do it in SQL? -- Muhammad
Ok, Muhammad, this is a fun query because we
get to take advantage of AS/400 date math, outer joins and general
goofiness! I am going to include all of the create table and insert
statements so that you can try this example yourself at home.
First, download a free SQLThing from
www.sqlthing.com to execute the following commands, (or use Ops
Navigator or Interactive SQL if you are so inclined)
CREATE TABLE SQLBOOK/HTSALES
(SDATE DATE,
SALETOTAL DECIMAL(10,2),
PRIMARY KEY (sdate));
CREATE TABLE SQLBOOK/HTFISCAL (
PHYR SMALLINT,
PHWK SMALLINT,
SD DATE,
ED DATE,
PRIMARY KEY (PHYR, PHWK),
CHECK (SD<ED));
Notice that I placed a check constraint into
the HTFISCAL table to ensure that the SD is always less than the ED
field when a record is entered. Next, execute the following insert
statements to place test data into your files:
/* Data for the HTFISCAL table */
INSERT INTO SQLBOOK/HTFISCAL VALUES (2000,39,'10/25/2000','10/31/2000');
INSERT INTO SQLBOOK/HTFISCAL VALUES (2001,39,'10/27/2001','11/02/2001');
INSERT INTO SQLBOOK/HTFISCAL VALUES (2000,40,'11/01/2000','11/07/2000');
INSERT INTO SQLBOOK/HTFISCAL VALUES (2001,40,'11/03/2001','11/10/2001');
INSERT INTO SQLBOOK/HTFISCAL VALUES (2000,41,'11/08/2000','11/14/2000');
INSERT INTO SQLBOOK/HTFISCAL VALUES (2001,41,'11/11/2001','11/17/2001');
INSERT INTO SQLBOOK/HTFISCAL VALUES (2000,42,'11/15/2000','11/21/2000');
INSERT INTO SQLBOOK/HTFISCAL VALUES (2001,42,'11/18/2001','11/24/2001');
/* Data for the HTSALES table */
INSERT INTO SQLBOOK/HTSALES VALUES ('11/01/2000',5000);
INSERT INTO SQLBOOK/HTSALES VALUES ('11/02/2000',5500);
INSERT INTO SQLBOOK/HTSALES VALUES ('11/03/2001',5800);
INSERT INTO SQLBOOK/HTSALES VALUES ('11/04/2001',6000);
INSERT INTO SQLBOOK/HTSALES VALUES ('11/02/2001',7000);
Now, we are ready to cook with gasoline!
Consider the following query:
SELECT * FROM SQLBOOK/HTSALES a
INNER JOIN SQLBOOK/HTFISCAL c
ON (a.sdate >= c.sd
AND a.sdate <= c.ed)
If you're at V5R1, you can use this form, if
you prefer.
SELECT * FROM SQLBOOK/HTSALES a
INNER JOIN SQLBOOK/HTFISCAL c
ON (a.sdate BETWEEN c.sd AND c.ed);
This query asks for all of the fields from the
HTFISCAL table and the HTSALES table and joins the two tables where
the SDATE, (the date of the sale), is between the SD, (start date),
and ED, (end date). By executing this statement we now have a
virtual table that consists of the total, date sold, accounting
week, accounting year, accounting start period and accounting end
period. The results should look like this:
SDATE |
SALETOTAL |
PHYR |
PHWK |
SD |
ED |
11/02/2001 |
7000 |
2001 |
39 |
10/27/2001 |
11/02/2001 |
11/01/2000 |
5000 |
2000 |
40 |
11/01/2000 |
11/07/2000 |
11/02/2000 |
5500 |
2000 |
40 |
11/01/2000 |
11/07/2000 |
11/03/2001 |
5800 |
2001 |
40 |
11/03/2001 |
11/10/2001 |
11/04/2001 |
6000 |
2001 |
40 |
11/03/2001 |
11/10/2001 |
Now, we need to know the start date of the week
period for the prior year, so we add another join to the query where
the PHWK values are equal, but the year of the sales period is
PHYR-1:
SELECT SDATE,SALETOTAL,C.SD,D.SD
FROM SQLBOOK/HTSALES a
INNER JOIN SQLBOOK/HTFISCAL c
ON (a.sdate >= c.sd AND a.sdate <= c.ed)
INNER JOIN SQLBOOK/HTFISCAL d
ON (c.phwk = d.phwk AND c.phyr-1 = d.phyr)
V5R1 users may prefer to use BETWEEN in the
join, like this.
SELECT SDATE,SALETOTAL,C.SD,D.SD
FROM SQLBOOK/HTSALES a
INNER JOIN SQLBOOK/HTFISCAL c
ON (a.sdate BETWEEN c.sd AND c.ed)
INNER JOIN SQLBOOK/HTFISCAL d
ON (c.phwk = d.phwk AND c.phyr-1 = d.phyr);
Notice that I am joining the HTFISCAL file to
itself in order to find the previous year start date. The SQL
statement should yield results like the following:
SDATE |
SALETOTAL |
C.SD |
D.SD |
11/02/2001 |
7000 |
10/27/2001 |
10/25/2000 |
11/03/2001 |
5800 |
11/03/2001 |
11/01/2000 |
11/04/2001 |
6000 |
11/03/2001 |
11/01/2000 |
Now, the magic is to use date math to find the
corresponding sales day for the previous year. This is done by
calculating the previous sales date using the following expression:
PreviousSalesDate = (ThisSalesDate – ThisWeekStart) + LastYearStart
Note that this will only work if the difference
between the ThisSalesDate and the ThisWeekStart value is within a
caldendar month or so. This is because the AS/400 system returns
results of date arithmetic in durations. However, for the purposes
of this report, we should never have a start date greater than 7
days from the sales date, so it works. If we did have a period
greater than about a month,
we would have to use a formula to account for the greater durations.
Since, we don’t need to do this, the query can remain this simple.
For more information on durations and date math, check my website,
www.sqlthing.com, or purchase my book, iSeries and AS/400 SQL at
Work.
So, to get the final result, we just join again
to the HTSALES table, but this time on our calculated previous year
sales date in order to yield a table of the correct data:
SELECT a.sdate, a.saletotal,
b.sdate, b.saletotal
FROM SQLBOOK/HTSALES a
INNER JOIN SQLBOOK/HTFISCAL c
ON (a.sdate >= c.sd AND a.sdate <= c.ed)
INNER JOIN SQLBOOK/HTFISCAL d
ON (c.phwk = d.phwk AND c.phyr -1 = d.phyr)
LEFT OUTER JOIN SQLBOOK/HTSALES b
ON (b.sdate = (a.sdate-c.sd) + d.sd)
V5R1 installations may use this form instead.
SELECT a.sdate, a.saletotal,
b.sdate, b.saletotal
FROM SQLBOOK/HTSALES a
INNER JOIN SQLBOOK/HTFISCAL c
ON (a.sdate BETWEEN c.sd AND c.ed)
INNER JOIN SQLBOOK/HTFISCAL d
ON (c.phwk = d.phwk AND c.phyr –1 = d.phyr)
LEFT OUTER JOIN SQLBOOK/HTSALES b
ON (b.sdate = (a.sdate-c.sd) + d.sd);
The above statement uses a LEFT OUTER JOIN so
that the AS/400 will return NULL for any dates and totals that do
not exist in the previous years data. The above statement should
yield the following results:
SDATE |
SALETOTAL |
SDATE |
SALETOTAL |
11/02/2001 |
7000 |
#NULL# |
#NULL# |
11/03/2001 |
5800 |
11/01/2000 |
5000 |
11/04/2001 |
6000 |
11/02/2000 |
5500 |
See, joins and date math can be fun. |