Subqueries and Query Expressions - The NULLIF Expression (SQL2)
(Page 9 of 12 )
Just as the COALESCE expression is used to eliminate NULL values when they are not desired for processing, sometimes you may need to create NULL values. In many data processing applications (especially older ones that were developed before relational databases were popular), missing data is not represented by NULL values. Instead, some special code value that is otherwise invalid is used to indicate that the data is missing.

Figure 9-12. SQL2 COALESCE expression syntax diagram
For example, suppose that in the sample database, the situation where a salesperson had not yet been assigned a manager was indicated by a zero (0) value in the MANAGER column instead of a NULL value. In some circumstances, you will want to detect this situation within a SQL query and substitute the NULL value for the zero “code.” The NULLIF expression, shown in Figure 9-13, is used for this purpose. When the DBMS encounters a NULLIF expression, it examines the first value (usually a column name) and compares it to the second value (usually the code value used to indicate missing data). If the two values are equal, the expression generates a NULL value. Otherwise, the expression generates the first value.
Here is a query that handles the case where missing office numbers are represented by a zero:
SELECT CITY, SUM(SALES)
FROM OFFICES, SALESREPS
WHERE OFFICE = (NULLIF REP_OFFICE, 0)
GROUP BY CITY
Row-Valued Expressions (SQL2) Although columns and the scalar data values they contain are the atomic building blocks of a relational database, the structuring of columns into rows that represent real-world entities, such as individual offices or customers or orders, is one of the most important features of the relational model. The SQL1 standard, and most mainstream commercial database products, certainly reflect this row/column structure, but they provide very limited capability to actually manipulate rows and groups of rows. Basically, SQL1 operations allow you to insert a row into a table, or to retrieve, update, or delete groups of rows from a database (using the SELECT, UPDATE,or DELETE statements).

Figure 9-13. SQL2 NULLIF expression syntax diagram
The SQL2 standard goes well beyond these capabilities, allowing you to generally use rows in SQL expressions in much the same way that you can use scalar values. It provides a syntax for constructing rows of data. It allows row-valued subqueries. And it defines row-valued meanings for the SQL comparison operators and other SQL structures.
The Row-Value Constructor (SQL2) SQL2 allows you to specify a row of data values by using a row-value constructor expression, whose syntax is shown in Figure 9-14. In its most common form, the row constructor is a comma-separated list of literal values, or expressions. For example, here is a row-value constructor for a row of data whose structure matches the OFFICES table in the sample database:
Figure 9-14. SQL2 row-value constructor syntax diagram
(23, 'San Diego', 'Western', NULL, DEFAULT, 0.00)
The result of this expression is a single row of data with six columns. The NULL keyword in the fourth column position indicates that the fourth column in the constructed row should contain a NULL (unknown) value. The DEFAULT keyword in the fifth column position indicates that the fifth column in the constructed row should contain the default value for the column. This keyword may appear in a row-value constructor only in certain situations—for example, when the row-value constructor appears in an INSERT statement to add a new row to a table.
When a row constructor is used in the WHERE clause of a SQL statement, column names can also appear as individual data items within the row constructor, or as part of an expression within the row constructor. For example, consider this query:
List the order number, quantity, and amount of all orders for ACI-41002 widgets.
SELECT ORDER_NUM, QTY, AMOUNT
FROM ORDERS
WHERE (MFR, PRODUCT) = ('ACI', '41002')
Under the normal rules of SQL query processing, the WHERE clause is applied to each row of the ORDERS table, one by one. The first row-value constructor in the WHERE clause (to the left of the equal sign) generates a two-column row, containing the manufacturer code and the product number for the current order being considered. The second row-value constructor (to the right of the equal sign) generates a two-column row, containing the (literal) manufacturer code ACI and product number 41002. The equal sign is now comparing two rows of values, not two scalar values. The SQL2 standard defines this type of row-valued comparison for equality, which is processed by comparing, pairwise, each of the columns in the two rows. The result of the comparison is TRUE only if all of the pairwise column comparisons are TRUE. Of course, it’s possible to write the query without the row-value constructors, like this:
List the order number, quantity, and amount of all orders for ACI-41002 widgets.
SELECT ORDER_NUM, QTY, AMOUNT
FROM ORDERS
WHERE (MFR = 'ACI') AND (PRODUCT = '41002')
and in this simple example, the meaning of the query is probably equally clear with either form. However, row-value constructors can be very useful in simplifying the appearance of more complex queries, and they become even more useful when combined with row-valued subqueries.
Row-Valued Subqueries (SQL2) As described throughout the earlier parts of this chapter, the SQL1 standard provides a subquery capability for expressing more complex database queries. The subquery takes the same form as a SQL query (that is, a SELECT statement), but a SQL1 subquery must be scalar-valued—that is, it must produce a single data value as its query results. The value generated by the subquery is then used as part of an expression within the main SQL statement that contains the subquery. This use of subqueries is supported by the major enterprise-class relational database systems today.
The SQL2 standard dramatically expands the subquery facility, including support for row-valued subqueries. A row-valued subquery returns not just a single data item, but a row of data items, which can be used in SQL2 expressions and compared to other rows. For example, suppose you wanted to show the order numbers and dates for all of the orders placed against the highest-priced product in the sample database. A logical way to start building the appropriate SQL query is to find an expression that will give you the identity (manufacturer ID and product ID) of the high-priced product in question. Here is a query that finds the right product:
Find the manufacturer ID and product ID of the product with the highest unit price.
SELECT MFR_ID, PRODUCT_ID
FROM PRODUCTS
WHERE PRICE = (SELECT MAX (PRICE)
FROM PRODUCTS
Ignoring the possibility of a tie for the most expensive product for a moment, this query will generate a single row of query results, consisting of two columns. Using SQL2’s row-valued subquery capability, you can embed this entire query as a subquery within a SELECT statement to retrieve the order information:
List the order numbers and dates of all orders placed for the highest-priced product.
SELECT ORDER_NUM, ORDER_DATE
FROM ORDERS
WHERE (MFR, PRODUCT) = (SELECT MFR_ID, PRODUCT_ID
FROM PRODUCTS
WHERE PRICE = (SELECT MAX(PRICE)
FROM PRODUCTS))
The top-level WHERE clause in this query contains a row-valued comparison. On the left side of the equal sign is a row-value constructor consisting of two column names. Each time the WHERE clause is examined to carry out the top-level query, the value of this row-valued expression is a manufacturer-ID/product-ID pair from a row of the ORDERS table. On the right side of the equal sign is the subquery that generates the identity of the product with the highest dollar value. The result of this subquery is again a row value, with two colu
mns, whose data types match those of the row-valued expression on the left side of the equal sign.
It’s possible to express this query without the row-valued subquery, but the resulting query will be much less straightforward:
List the order numbers and dates of all orders placed for the highest-priced product.
SELECT ORDER_NUM, ORDER_DATE
FROM ORDERS
WHERE (MFR = (SELECT MFR_ID
FROM PRODUCTS
WHERE PRICE = (SELECT MAX(PRICE)
FROM PRODUCTS)))
AND (PRODUCT = (SELECT PRODUCT_ID
FROM PRODUCTS
WHERE PRICE = (SELECT MAX(PRICE)
FROM PRODUCTS)))
Instead of a single row-valued comparison in the WHERE clause, the resulting query has two separate scalar-valued comparisons, one for the manufacturer ID and one for the
product ID. Because the comparison must be split, the lower-level subquery to find the maximum price must be repeated twice as well. Overall, the form of the query using the row-valued expression is a more direct translation of the English-language request, and it’s easier to read and understand.
This article is excerpted from MySQL The Complete Reference by Vikram Vaswani (McGraw-Hill, 2003; ISBN 0072224770). Check it out at your favorite bookstore today. Buy this book now. |
Next: Row-Valued Comparisons (SQL2) >>
More MS SQL Server Articles
More By McGraw-Hill/Osborne