Welcome to the Database Programmer!
Good programming skills do not lead magically to good database skills. Masterful use of the database requires knowledge of the database in its own terms. Step 1 is knowing your table design patterns, and Step 2 is knowing how to fashion efficient queries. Learning how to code good queries can lead to faster performance and better application code.
There is a new entry in this series every Monday morning, and the Complete Table of Contents is here.
Aggregation
You can use a SQL SELECT to aggregate data. Aggregation combines rows together and performs some operation on their combined values. Very common aggregations are COUNT, SUM, and AVG.
The simplest use of aggregations is to examine an entire table and pull out only the aggregations, with no other columns specified. Consider this SQL:
SELECT COUNT(*) as cnt ,SUM(sale_amount) as sum ,AVG(sale_amount) as avg FROM orders
If you have a very small sales order table, say about 7 rows, like this:
ORDER | DATE | STATE | SALE_AMOUNT ------+------------+-------+------------- 1234 | 2007-11-01 | NY | 10.00 1235 | 2007-12-01 | TX | 15.00 1236 | 2008-01-01 | CA | 20.00 1237 | 2008-02-01 | TX | 25.00 1238 | 2008-03-01 | CA | 30.00 1237 | 2008-04-01 | NY | 35.00 1238 | 2008-05-01 | NY | 40.00
Then the simple query above produces a one-row output:
CNT | SUM | AVG -----+------+----- 7 | 175 | 25
Some Notes on The Syntax
When we use COUNT(*) we always put the asterisk inside.
I have used the "as SUM" to specify a column name of the output. Without that I will get whatever the database server decides to call it, which will vary from platform to platform, so it is a good idea to learn to use the "AS" clause. Some folks would frown at using "SUM" as the name, since that is the name of the function and might be confusing, but I think we're all big kids and we can probably handle it.
The WHERE Clause Does What You Think
If you want to get just the sales from New York state, you can put a WHERE clause in:
SELECT COUNT(*) as cnt ,SUM(sale_amount) as sum ,AVG(sale_amount) as avg FROM orders WHERE state = 'NY'
...and you will get only the results for NY:
CNT | SUM | AVG ----+------+---------- 3 | 85 | 28.33333
Notice of course that the average has a repeating decimal. Most databases have a ROUND function of some sort, so I can correct that with:
SELECT COUNT(*) as cnt ,SUM(sale_amount) as sum ,ROUND(AVG(sale_amount),0) as avg FROM orders WHERE state = 'NY'
The Fun Begins With GROUP BY
The query above is fine, but it would be very laborious if you had to issue the query (or write a program to do it) for every possible state. The answer is the GROUP BY clause. The GROUP BY clause says that the aggregations should be performed for the distinct values of a column or columns. It looks like this:
SELECT state, ,COUNT(*) as cnt ,SUM(sale_amount) as sum ,ROUND(AVG(sale_amount),0) as avg FROM orders GROUP BY state
Which gives us this result:
STATE | CNT | SUM | AVG ------+-----+------+---- NY | 3 | 85 | 28 TX | 2 | 40 | 20 CA | 2 | 50 | 25
Note that if you try to include a column that you are not grouping on, such as zip code, most database servers will reject the query because there may be different values of zip code for the same value of state, and they have no way to know which one to pick for a given value of state.
HAVING Clause is Like WHERE after GROUP BY
The HAVING clause lets us put a filter on the results after the aggregation has taken place. If your Sales Manager wants to know which states have an average sale amount of $25.00 or more. Now our query looks like this:
SELECT state, ,COUNT(*) as cnt ,SUM(sale_amount) as sum ,ROUND(AVG(sale_amount),0) as avg FROM orders GROUP BY state HAVING AVG(sale_amount) >= 25
Which gives us this result, notice that Texas is now missing, as they were just not selling big enough orders (sorry 'bout that Rhonda).
STATE | CNT | SUM | AVG ------+-----+------+---- NY | 3 | 85 | 28 CA | 2 | 50 | 25
The Hat Trick: All Three
You can pull some pretty nice results out of a database in a single query if you know how to combine the WHERE, GROUP BY, and HAVING. If you have ever worked with a Sales Manager, you know they constantly want to know strange numbers, so let's say our Sales Manager says, "Can you tell me the average order size by state for all orders greater than 20? And don't bother with any average less 30.00" We say, "Sure, don't walk away, I'll print it out right now."
SELECT state ,COUNT(*) ,SUM(sale_amount) as sum ,ROUND(AVG(sale_amount) as avg FROM orders WHERE sale_amount > 20 GROUP BY state HAVING avg(sale_amount) >= 30
How to Do a Weighted Average
Consider the case of a table that lists test, homework and quiz scores for the students in a certain course. Each particular score is worth a certain percentage of a student's grade, and the teacher wants the computer to calculate each student's file score. If the table looks like:
STUDENT | WEIGHT | SCORE ------------+--------+------- NIRGALAI | 40 | 90 NIRGALAI | 35 | 95 NIRGALAI | 25 | 85 JBOONE | 40 | 80 JBOONE | 35 | 95 JBOONE | 25 | 70 PCLAYBORNE | 40 | 70 PCLAYBORNE | 35 | 80 PCLAYBORNE | 25 | 90
Then we can accomplish this in one pull like so:
SELECT student ,SUM(weight * score) / 100 as final FROM scores GROUP BY student
The nice thing about this query is that it works even if data is missing. If a student missed a test, they automatically get a zero averaged in.
Conclusion: Queries Are Where It's At
The only reason to put data into a database is to take it out again. The modern database has powerful strategies for ensuring the correctness of data going in (the primary key, foreign key and other constraints) and equally powerful tools for pulling the data back out.