In this chapter
Performing Arithmetic Operations
Determining the Order of Evaluation
Extracting a Substring with SUBSTRING()
Changing String Case with UPPER() and LOWER()
Trimming Characters with TRIM()
Finding the Length of a String with CHARACTER_LENGTH()
Finding Substrings with POSITION()
Performing Datetime and Interval Arithmetic
Getting the Current Date and Time
Converting Data Types with CAST()
Evaluating Conditional Values with CASE
Operators and functions let you calculate results derived from column values, system-determined values, constants, and other data. You can perform:
An operator is a symbol or keyword indicating an operation that acts on one or more elements. The elements, called operands, are SQL expressions. Recall from “Tips for SQL Syntax” in Chapter 3 that an expression is any legal combination of symbols and tokens that evaluates to a single value (or null). In the expression price * 2, for example, * is the operator, and price and 2 are its operands.
A function is a built-in, named routine that performs a specialized task. Most functions take parenthesized arguments, which are values you pass to the function that the function then uses to perform its task. Arguments can be column names, literals, nested functions, or more-complex expressions. In UPPER(au_lname), for example, UPPER is the function name, and au_lname is the argument.
You can use operators and functions to create derived columns. A derived column is the result of a calculation and is created with a SELECT-clause expression that is something other than a simple reference to a column. Derived columns don’t become permanent columns in a table; they’re for display and reporting purposes.
The values in a derived column are often computed from values in existing columns, but you can also create a derived column by using a constant expression (such as a string, number, or date) or system value (such as the system time). Listing 5.1 shows a SELECT statement that yields a trivial arithmetic calculation; it needs no FROM clause because it doesn’t retrieve data from a table. Figure 5.1 shows the result.
Listing 5.1A constant expression in a SELECT clause. No FROM clause is needed because I’m not retrieving data from a table. See Figure 5.1 for the result.
SELECT 2 + 3;
Figure 5.1Result of Listing 5.1. This result is a table with one row and one column.
2 + 3
-----
5
Recall from “Tables, Columns, and Rows” in Chapter 2 that closure guarantees that every result is a table, so even this simple result is a table: a 1 × 1 table that contains the value 5. If I retrieve a column along with a constant, then the constant appears in every row of the result (Listing 5.2 and Figure 5.2).
Listing 5.2Here, I’ve retrieved a column and a constant expression. See Figure 5.2 for the result.
SELECT au_id, 2 + 3
FROM authors;
Figure 5.2Result of Listing 5.2. The constant is repeated in each row.
au_id 2 + 3
----- -----
A01 5
A02 5
A03 5
A04 5
A05 5
A06 5
A07 5
Your DBMS will assign the derived column a default name, typically the expression itself as a quoted identifier. You should name derived columns explicitly with an AS clause because system-assigned names can be long, unwieldy, and inconvenient for database applications to refer to; see “Creating Column Aliases with AS” in Chapter 4 (Listing 5.3 and Figure 5.3).
Listing 5.3List the book prices discounted by 10 percent. The derived columns would have DBMS-specific default names if the AS clauses were removed. See Figure 5.3 for the result.
SELECT
title_id,
price,
0.10 AS "Discount",
price * (1 - 0.10) AS "New price"
FROM titles;
Figure 5.3Result of Listing 5.3.
title_id price Discount New price
-------- ----- -------- ---------
T01 21.99 0.10 19.79
T02 19.95 0.10 17.95
T03 39.95 0.10 35.96
T04 12.99 0.10 11.69
T05 6.95 0.10 6.25
T06 19.95 0.10 17.95
T07 23.95 0.10 21.56
T08 10.00 0.10 9.00
T09 13.95 0.10 12.56
T10 NULL 0.10 NULL
T11 7.99 0.10 7.19
T12 12.99 0.10 11.69
T13 29.99 0.10 26.99
Oracle requires a FROM clause in a SELECT statement and so creates the dummy table DUAL automatically to be used for SELECTing a constant expression; search Oracle documentation for DUAL table. To run Listing 5.1, add a FROM clause that selects the constant value from DUAL:
SELECT 2 + 3
FROM DUAL;
Db2 requires a FROM clause in a SELECT statement and so creates the dummy table SYSIBM.SYSDUMMY1 automatically to be used for SELECTing a constant expression; search Db2 documentation for SYSIBM.SYSDUMMY1. To run Listing 5.1, add a FROM clause that selects the constant value from SYSIBM.SYSDUMMY1:
SELECT 2 + 3
FROM SYSIBM.SYSDUMMY1;
In older PostgreSQL versions, convert the floating-point number in Listing 5.3 to DECIMAL; see “Converting Data Types with CAST()” later in this chapter. To run Listing 5.3, change the New price calculation in the SELECT clause to:
price * CAST((1 - 0.10) AS DECIMAL)
A monadic (or unary) arithmetic operator performs a mathematical operation on a single numeric operand to produce a result. The – (negation) operator changes the sign of its operand, and the not-very-useful + (identity) operator leaves its operand unchanged. A dyadic (or binary) arithmetic operator performs a mathematical operation on two numeric operands to produce a result. These operators include the usual ones: + (addition), – (subtraction), * (multiplication), and / (division). Table 5.1 lists SQL’s arithmetic operators (expr is a numeric expression).
Operator | What It Does |
---|---|
–expr | Reverses the sign of expr |
+expr | Leaves expr unchanged |
expr1 + expr2 | Sums expr1 and expr2 |
expr1 – expr2 | Subtracts expr2 from expr1 |
expr1 * expr2 | Multiplies expr1 and expr2 |
expr1 / expr2 | Divides expr1 by expr2 |
To change the sign of a number:
Type -expr
expr is a numeric expression (Listing 5.4 and Figure 5.4).
Listing 5.4The negation operator changes the sign of a number. See Figure 5.4 for the result.
SELECT
title_id,
-advance AS "Advance"
FROM royalties;
Figure 5.4Result of Listing 5.4. Note that zero has no sign (is neither positive nor negative).
title_id Advance
-------- -----------
T01 -10000.00
T02 -1000.00
T03 -15000.00
T04 -20000.00
T05 -100000.00
T06 -20000.00
T07 -1000000.00
T08 0.00
T09 0.00
T10 NULL
T11 -100000.00
T12 -50000.00
T13 -20000.00
To add, subtract, multiply, or divide:
Type expr1 + expr2
to add, expr1 - expr2
to subtract, expr1 * expr2
to multiply, or expr1 / expr2
to divide.
expr1 and expr2 are numeric expressions (Listing 5.5 and Figure 5.5).
Listing 5.5List the biographies by descending revenue (= price × sales). See Figure 5.5 for the result.
SELECT
title_id,
price * sales AS "Revenue"
FROM titles
WHERE type = 'biography'
ORDER BY price * sales DESC;
Figure 5.5Result of Listing 5.5.
title_id Revenue
-------- -----------
T07 35929790.00
T12 1299012.99
T06 225834.00
T10 NULL
Sometimes DBMSs force mathematical closure, so be careful when dividing integers by integers. If an integer dividend is divided by an integer divisor, then the result might be an integer that has any fractional part of the result truncated. You might expect the two derived columns in Listing 5.6 to contain the same values because the column pages (an INTEGER) is divided by two equal constants: 10 (an integer) and 10.0 (a float). Microsoft Access, Oracle, and MySQL return the result you’d expect (Figure 5.6a), but Microsoft SQL Server, Db2, and PostgreSQL truncate the result of an integer division (Figure 5.6b).
Listing 5.6This query’s first derived column divides pages by the integer constant 10, and the second derived column divides pages by the floating-point constant 10.0. In the result, you’d expect identical values to be in both derived columns. See Figures 5.6a and 5.6b for the results.
SELECT
title_id,
pages,
pages/10 AS "pages/10",
pages/10.0 AS "pages/10.0"
FROM titles;
Figure 5.6aResult of Listing 5.6 for Microsoft Access, Oracle, and MySQL. Dividing two integers yields a floating-point number (as you’d expect).
title_id pages pages/10 pages/10.0
-------- ----- -------- ----------
T01 107 10.7 10.7
T02 14 1.4 1.4
T03 1226 122.6 122.6
T04 510 51.0 51.0
T05 201 20.1 20.1
T06 473 47.3 47.3
T07 333 33.3 33.3
T08 86 8.6 8.6
T09 22 2.2 2.2
T10 NULL NULL NULL
T11 826 82.6 82.6
T12 507 50.7 50.7
T13 802 80.2 80.2
Figure 5.6bResult of Listing 5.6 for Microsoft SQL Server, Db2, and PostgreSQL. Dividing two integers yields an integer; the fractional part of the result is discarded (not as you’d expect).
title_id pages pages/10 pages/10.0
-------- ----- -------- ----------
T01 107 10 10.7
T02 14 1 1.4
T03 1226 122 122.6
T04 510 51 51.0
T05 201 20 20.1
T06 473 47 47.3
T07 333 33 33.3
T08 86 8 8.6
T09 22 2 2.2
T10 NULL NULL NULL
T11 826 82 82.6
T12 507 50 50.7
T13 802 80 80.2
Other Operators and Functions
All DBMSs provide plenty of operators and functions in addition to those defined in the SQL standard (or covered in this book). In fact, the standard is playing catch-up—many of the functions introduced in revised standards have existed for years in DBMSs. The earlier standards were so anemic that they left SQL weaker than a desktop calculator. Search your DBMS documentation for operators and functions to find mathematical, statistical, financial, scientific, trigonometric, conversion, string, datetime, bitwise, system, metadata, security, and other categories.
Precedence determines the priority of various operators when more than one operator is used in an expression. Operators with higher precedence are evaluated first. Arithmetic operators (+, –, *, and so on) have higher precedence than comparison operators (<, =, >, and so on), which have higher precedence than logical operators (NOT, AND, OR), so the expression
a or b * c >= d
is equivalent to
a or ((b * c) >= d)
Operators with lower precedence are less binding than those with higher precedence. Table 5.2 lists operator precedences from most to least binding. Operators in the same row have equal precedence.
Operator | Description |
---|---|
+, – | Monadic identity, monadic negation |
*, / | Multiplication, division |
+, – | Addition, subtraction |
=, <>, <, <=, >, >= | Comparison operators |
NOT | Logical NOT |
AND | Logical AND |
OR | Logical OR |
Associativity determines the order of evaluation in an expression when adjacent operators have equal precedence. SQL uses left-to-right associativity.
You don’t need to memorize all this information. You can use parentheses to override precedence and associativity rules (Listing 5.7 and Figure 5.7).
Listing 5.7The first and second columns show how to use parentheses to override precedence rules. The third and fourth columns show how to use parentheses to override associativity rules. See Figure 5.7 for the result.
SELECT
2 + 3 * 4 AS "2+3*4",
(2 + 3) * 4 AS "(2+3)*4",
6 / 2 * 3 AS "6/2*3",
6 / (2 * 3) AS "6/(2*3)";
Figure 5.7Result of Listing 5.7.
2+3*4 (2+3)*4 6/2*3 6/(2*3)
----- ------- ----- -------
14 20 9 1
Table 5.2 is incomplete; it omits some standard (such as IN and EXISTS) and nonstandard (DBMS-specific) operators. To determine the complete order of evaluation that your DBMS uses, search your DBMS documentation for precedence.
To run Listing 5.7 in Oracle, add the clause FROM DUAL
. To run it in Db2, add the clause FROM SYSIBM.SYSDUMMY1
. See the DBMS tip in “Tips for Derived Columns” earlier in this chapter.
Use the operator || to combine, or concatenate, strings. The operator’s important characteristics are:
To concatenate strings:
Type:
string1 || string2
string1 and string2 are the strings to be combined. Each operand is a string expression such as a column that contains character strings, a string literal, or the result of an operation or function that returns a string (Listings 5.8 through 5.11, Figures 5.8 through 5.11).
Listing 5.8List the authors’ first and last names, concatenated into a single column and sorted by last name/first name. See Figure 5.8 for the result.
SELECT au_fname || ' ' || au_lname
AS "Author name"
FROM authors
ORDER BY au_lname ASC, au_fname ASC;
Figure 5.8Result of Listing 5.8.
Author name
-----------------
Sarah Buchman
Wendy Heydemark
Hallie Hull
Klee Hull
Christian Kells
Kellsey
Paddy O'Furniture
Listing 5.9List biography sales by descending sales order. Here, I need to convert sales from an integer to a string. See Figure 5.9 for the result.
SELECT
CAST(sales AS CHAR(7))
|| ' copies sold of title '
|| title_id
AS "Biography sales"
FROM titles
WHERE type = 'biography'
AND sales IS NOT NULL
ORDER BY sales DESC;
Figure 5.9Result of Listing 5.9.
Biography sales
--------------------------------
1500200 copies sold of title T07
100001 copies sold of title T12
11320 copies sold of title T06
Listing 5.10List biographies by descending publication date. Here, I need to convert pubdate from a datetime to a string. See Figure 5.10 for the result.
SELECT
'Title '
|| title_id
|| ' published on '
|| CAST(pubdate AS CHAR(10))
AS "Biography publication dates"
FROM titles
WHERE type = 'biography'
AND pubdate IS NOT NULL
ORDER BY pubdate DESC;
Figure 5.10Result of Listing 5.10.
Biography publication dates
---------------------------------
Title T12 published on 2000-08-31
Title T06 published on 2000-07-31
Title T07 published on 1999-10-01
Listing 5.11List all the authors named Klee Hull. See Figure 5.11 for the result.
SELECT au_id, au_fname, au_lname
FROM authors
WHERE au_fname || ' ' || au_lname
= 'Klee Hull';
Figure 5.11Result of Listing 5.11.
au_id au_fname au_lname
----- -------- --------
A04 Klee Hull
Listing 5.11 shows how to use || in a WHERE clause, but it’s actually bad SQL. The efficient way to express the clause is:
WHERE au_fname = 'Klee'
AND au_lname = 'Hull'
In Microsoft Access, the concatenation operator is +, and the conversion function is Format(string). To run Listings 5.8 through 5.11, change the concatenation and conversion expressions to (Listing 5.8):
au_fname + ' ' + au_lname
and (Listing 5.9):
Format(sales) + ' copies sold of title ' + title_id
and (Listing 5.10):
'Title ' + title_id + ' published on ' + Format(pubdate)
and (Listing 5.11):
au_fname + ' ' + au_lname = 'Klee Hull';
In Microsoft SQL Server, the concatenation operator is +. To run Listings 5.8 through 5.11, change the concatenation expressions to (Listing 5.8):
au_fname + ' ' + au_lname
and (Listing 5.9):
CAST(sales AS CHAR(7)) + ' copies sold of title ' + title_id
and (Listing 5.10):
'Title ' + title_id + ' published on ' + CAST(pubdate AS CHAR(10))
and (Listing 5.11):
au_fname + ' ' + au_lname = 'Klee Hull';
In MySQL, the concatenation function is CONCAT(). The || operator is legal, but it means logical OR in MySQL by default. (Use PIPES_AS_CONCAT mode to treat || as a string-concatenation operator rather than as a synonym for OR.) CONCAT() takes any number of arguments and converts nonstrings to strings as necessary (so CAST() isn’t needed). To run Listings 5.8 through 5.11, change the concatenation expressions to (Listing 5.8):
CONCAT(au_fname, ' ', au_lname)
and (Listing 5.9):
CONCAT(sales, ' copies sold of title ', title_id)
and (Listing 5.10):
CONCAT('Title ', title_id, ' published on ', pubdate)
and (Listing 5.11):
CONCAT(au_fname, ' ', au_lname) = 'Klee Hull';
For string operations, Oracle treats a null as an empty string: 'a' || NULL || 'b' returns 'ab' (not NULL). See the DBMS tip in “Tips for Nulls” in Chapter 3.
Oracle, MySQL, and PostgreSQL convert nonstrings to strings implicitly in concatenations; Listings 5.9 and 5.10 still will run on these DBMSs if you omit CAST(). Search your DBMS documentation for concatenation or conversion.
Microsoft SQL Server, Oracle, Db2, and PostgreSQL also support the CONCAT() function.
Use the function SUBSTRING() to extract part of a string. The function’s important characteristics are:
To extract a substring:
Type:
SUBSTRING(string FROM start [FOR length])
string is the source string from which to extract the substring. string is a string expression such as a column that contains character strings, a string literal, or the result of an operation or function that returns a string. start is an integer that specifies where the substring begins, and length is an integer that specifies the length of the substring (the number of characters to return). start starts counting at 1. If FOR length is omitted, then SUBSTRING() returns all the characters from start to the end of string (Listings 5.12, 5.13, and 5.14, Figures 5.12, 5.13, and 5.14).
Listing 5.12Split the publisher IDs into alphabetic and numeric parts. The alphabetic part of a publisher ID is the first character, and the remaining characters are the numeric part. See Figure 5.12 for the result.
SELECT
pub_id,
SUBSTRING(pub_id FROM 1 FOR 1)
AS "Alpha part",
SUBSTRING(pub_id FROM 2)
AS "Num part"
FROM publishers;
Figure 5.12Result of Listing 5.12.
pub_id Alpha part Num part
------ ---------- --------
P01 P 01
P02 P 02
P03 P 03
P04 P 04
Listing 5.13List the first initial and last name of the authors from New York State and Colorado. See Figure 5.13 for the result.
SELECT
SUBSTRING(au_fname FROM 1 FOR 1)
|| '. '
|| au_lname
AS "Author name",
state
FROM authors
WHERE state IN ('NY', 'CO');
Figure 5.13Result of Listing 5.13.
Author name state
------------ -----
S. Buchman NY
W. Heydemark CO
C. Kells NY
Listing 5.14List the authors whose area code is 415. See Figure 5.14 for the result.
SELECT au_fname, au_lname, phone
FROM authors
WHERE SUBSTRING(phone FROM 1 FOR 3) = '415';
Figure 5.14Result of Listing 5.14.
au_fname au_lname phone
-------- -------- ------------
Hallie Hull 415-549-4278
Klee Hull 415-549-4278
In Microsoft Access, the substring function is Mid(string, start [,length]). Use + to concatenate strings. To run Listings 5.12 through 5.14, change the substring expressions to (Listing 5.12):
Mid(pub_id, 1, 1)
Mid(pub_id, 2)
and (Listing 5.13):
Mid(au_fname, 1, 1) + '. ' + au_lname
and (Listing 5.14):
Mid(phone, 1, 3) = '415'
In Microsoft SQL Server, the substring function is SUBSTRING(string, start, length). Use + to concatenate strings. To run Listings 5.12 through 5.14, change the substring expressions to (Listing 5.12):
SUBSTRING(pub_id, 1, 1)
SUBSTRING(pub_id, 2, LEN(pub_id)-1)
and (Listing 5.13):
SUBSTRING(au_fname, 1, 1) + '. ' + au_lname
and (Listing 5.14):
SUBSTRING(phone, 1, 3) = '415'
In Oracle and Db2, the substring function is SUBSTR(string, start [,length]). To run Listings 5.12 through 5.14, change the substring expressions to (Listing 5.12):
SUBSTR(pub_id, 1, 1)
SUBSTR(pub_id, 2)
and (Listing 5.13):
SUBSTR(au_fname, 1, 1) || '. ' || au_lname
and (Listing 5.14):
SUBSTR(phone, 1, 3) = '415'
In MySQL, use CONCAT() to run Listing 5.13 (see “Concatenating Strings with ||” earlier in this chapter). Change the concatenation expression to:
CONCAT(SUBSTRING(au_fname FROM 1 FOR 1), '. ', au_lname)
For string operations, Oracle treats a null as an empty string: SUBSTR(NULL, 1, 2) returns '' (not NULL). See the DBMS tip in “Tips for Nulls” in Chapter 3.
Your DBMS implicitly might constrain start and length arguments that are too small or too large to sensible values. The substring function silently might replace a negative start with 1 or a too-long length with the length of string, for example. Search your DBMS documentation for substring or substr.
MySQL and PostgreSQL also support the SUBSTR(string, start, length) form of the substring function.
Use the function UPPER() to return a string with lowercase letters converted to uppercase, and use the function LOWER() to return a string with uppercase letters converted to lowercase. The functions’ important characteristics are:
To convert a string to uppercase or lowercase:
To convert a string to uppercase, type:
UPPER(string)
or
To convert a string to lowercase, type:
LOWER(string)
string is a string expression such as a column that contains character strings, a string literal, or the result of an operation or function that returns a string (Listings 5.15 and 5.16, Figures 5.15 and 5.16).
Listing 5.15List the authors’ first names in lowercase and last names in uppercase. See Figure 5.15 for the result.
SELECT
LOWER(au_fname) AS "Lower",
UPPER(au_lname) AS "Upper"
FROM authors;
Figure 5.15Result of Listing 5.15.
Lower Upper
--------- -----------
sarah BUCHMAN
wendy HEYDEMARK
hallie HULL
klee HULL
christian KELLS
KELLSEY
paddy O'FURNITURE
Listing 5.16List the titles that contain the characters MO, regardless of case. All the letters in the LIKE pattern must be uppercase for this query to work. See Figure 5.16 for the result.
SELECT title_name
FROM titles
WHERE UPPER(title_name) LIKE '%MO%';
Figure 5.16Result of Listing 5.16.
title_name
-------------------------
200 Years of German Humor
I Blame My Mother
In Microsoft Access, the upper- and lowercase functions are UCase(string) and LCase(string). To run Listings 5.15 and 5.16, change the case expressions to (Listing 5.15):
LCase(au_fname)
UCase(au_lname)
and (Listing 5.16):
UCase(title_name) LIKE '%MO%'
For string operations, Oracle treats a null as an empty string: UPPER(NULL) and LOWER(NULL) return '' (not NULL). See the DBMS tip in “Tips for Nulls” in Chapter 3.
Your DBMS might provide other string-casing functions to, say, invert case or convert strings to sentence or title case. Search your DBMS documentation for character functions or string functions.
Case-Insensitive Comparisons
In DBMSs that perform case-sensitive WHERE-clause comparisons by default, UPPER() or LOWER() often is used to make case-insensitive comparisons:
WHERE UPPER(au_fname) = 'JOHN'
If you’re sure that your data are clean, then it’s faster to look for only reasonable letter combinations than to use case functions:
WHERE au_fname = 'JOHN'
OR au_fname = 'John'
UPPER() and LOWER() affect characters with diacritical marks (such as accents and umlauts): UPPER('ö') is 'Ö', for example. If your data contain such characters and you’re making case-insensitive comparisons such as
WHERE UPPER(au_fname) = 'JOSÉ'
then make sure that your DBMS doesn’t lose the marks on conversion. UPPER('José') should be 'JOSÉ', not 'JOSE'. See also “Filtering Rows with WHERE” in Chapter 4.
Use the function TRIM() to remove unwanted characters from the ends of a string. The function’s important characteristics are:
To trim spaces from a string:
Type:
TRIM([[LEADING | TRAILING | BOTH] FROM] string)
string is a string expression such as a column that contains character strings, a string literal, or the result of an operation or function that returns a string. Specify LEADING to remove leading spaces, TRAILING to remove trailing spaces, or BOTH to remove leading and trailing spaces. If this specifier is omitted, then BOTH is assumed (Listing 5.17 and Figure 5.17).
Listing 5.17This query strips leading, trailing, and both leading and trailing spaces from the string ' AAA '. The < and > characters show the extent of the trimmed strings. See Figure 5.17 for the result.
SELECT
'<' || ' AAA ' || '>'
AS "Untrimmed",
'<' || TRIM(LEADING FROM ' AAA ') || '>'
AS "Leading",
'<' || TRIM(TRAILING FROM ' AAA ') || '>'
AS "Trailing",
'<' || TRIM(' AAA ') || '>'
AS "Both";
Figure 5.17Result of Listing 5.17.
Untrimmed Leading Trailing Both
--------- --------- --------- -----
< AAA > <AAA > < AAA> <AAA>
To trim characters from a string:
Type:
TRIM([LEADING | TRAILING | BOTH] 'trim_chars' FROM string)
string is the string to trim, and trim_chars is one or more characters to remove from string. Each argument is a string expression such as a column that contains character strings, a string literal, or the result of an operation or function that returns a string. Specify LEADING to remove leading characters, TRAILING to remove trailing characters, or BOTH to remove leading and trailing characters. If this specifier is omitted, then then BOTH is assumed (Listings 5.18 and 5.19, Figures 5.18 and 5.19).
Listing 5.18Strip the leading H from the authors’ last names that begin with H. See Figure 5.18 for the result.
SELECT
au_lname,
TRIM(LEADING 'H' FROM au_lname)
AS "Trimmed name"
FROM authors;
Figure 5.18Result of Listing 5.18.
au_lname Trimmed name
----------- ------------
Buchman Buchman
Heydemark eydemark
Hull ull
Hull ull
Kells Kells
Kellsey Kellsey
O'Furniture O'Furniture
Listing 5.19List the three-character title IDs that start with T1, ignoring leading and trailing spaces. See Figure 5.19 for the result.
SELECT title_id
FROM titles
WHERE TRIM(title_id) LIKE 'T1_';
Figure 5.19Result of Listing 5.19.
title_id
--------
T10
T11
T12
T13
In Listing 5.8 earlier in this chapter, I concatenated authors’ first and last names into a single column. The result, Figure 5.8, contains a single extra space before the author named Kellsey. This space—which separates the first and last names in the other rows—appears because Kellsey has no first name. You can use TRIM() to remove this leading space. Change the concatenation expression in Listing 5.8 to:
TRIM(au_fname || ' ' || au_lname)
In Microsoft Access, the trimming functions are LTrim(string) to trim leading spaces, RTrim(string) to trailing spaces, and Trim(string) to trim both leading and trailing spaces. Use the function Replace(string, find, replacement [, start[, count[, compare]]]) to trim nonspace characters (actually, to replace nonspaces with empty strings). Use + to concatenate strings. To run Listings 5.17 and 5.18, change the trim expressions to (Listing 5.17):
'<' + ' AAA ' + '>'
'<' + LTRIM(' AAA ') + '>'
'<' + RTRIM(' AAA ') + '>'
'<' + TRIM(' AAA ') + '>'
and (Listing 5.18):
Replace(au_lname, 'H', '', 1, 1)
In Microsoft SQL Server, the trimming functions are LTRIM(string) to trim leading spaces and RTRIM(string) to trim trailing spaces. Use + to concatenate strings. To run Listing 5.17, change the trim expressions to:
'<' + ' AAA ' + '>'
'<' + LTRIM(' AAA ') + '>'
'<' + RTRIM(' AAA ') + '>'
'<' + LTRIM(RTRIM(' AAA ')) + '>'
SQL Server’s LTRIM() and RTRIM() functions remove spaces but not arbitrary trim_chars characters. You can nest and chain SQL Server’s CHARINDEX(), LEN(), PATINDEX(), REPLACE(), STUFF(), SUBSTRING(), and other character functions to replicate arbitrary-character trimming. To run Listing 5.18, change the trim expression to:
REPLACE(SUBSTRING(au_lname, 1, 1),'H','') + SUBSTRING(au_lname, 2, LEN(au_lname))
To run Listing 5.19, change the trim expression to:
LTRIM(RTRIM(title_id)) LIKE 'T1_'
In Oracle, add the clause FROM
DUAL
to run Listing 5.17; see the DBMS tip in “Tips for Derived Columns” earlier in this chapter. Oracle forbids multiple characters in trim_chars.
In Db2, the trimming functions are LTRIM(string) to trim leading spaces and RTRIM(string) to trim trailing spaces. To run Listing 5.17, change the trim expressions:
'<' || ' AAA ' || '>'
'<' || LTRIM(' AAA ') || '>'
'<' || RTRIM(' AAA ') || '>'
'<' || LTRIM(RTRIM(' AAA ')) || '>'
You also must add the clause FROM SYSIBM.SYSDUMMY1
to Listing 5.17; see the DBMS tip in “Tips for Derived Columns” earlier in this chapter.
You can nest and chain Db2’s LENGTH(), LOCATE(), POSSTR(), REPLACE(), SUBSTR(), and other character functions to replicate arbitrary-character trimming. To run Listing 5.18, change the trim expression to:
REPLACE(SUBSTR(au_lname, 1, 1),'H','') || SUBSTR(au_lname, 2, LENGTH(au_lname))
To run Listing 5.19, change the trim expression to:
LTRIM(RTRIM(title_id)) LIKE 'T1_'
In MySQL, use CONCAT() to run Listing 5.17 (see “Concatenating Strings with ||” earlier in this chapter). Change the concatenation expressions to:
CONCAT('<',' AAA ','>')
CONCAT('<', TRIM(LEADING FROM ' AAA '), '>')
CONCAT('<', TRIM(TRAILING FROM ' AAA '), '>')
CONCAT('<',TRIM(' AAA '),'>')
For string operations, Oracle treats a null as an empty string: TRIM(NULL) returns '' (not NULL). See the DBMS tip in “Tips for Nulls” in Chapter 3.
Your DBMS might provide padding functions to add spaces or other characters to strings. The Oracle and PostgreSQL padding functions are LPAD() and RPAD(), for example. Search your DBMS documentation for character functions or string functions.
Use the function CHARACTER_LENGTH() to return the number of characters in a string. The function’s important characteristics are:
To find the length of a string:
Type:
CHARACTER_LENGTH(string)
string is a string expression such as a column that contains character strings, a string literal, or the result of an operation or function that returns a string (Listings 5.20 and 5.21, Figures 5.20 and 5.21).
Listing 5.20List the lengths of the authors’ first names. See Figure 5.20 for the result.
SELECT
au_fname,
CHARACTER_LENGTH(au_fname) AS "Len"
FROM authors;
Figure 5.20Result of Listing 5.20.
au_fname Len
--------- ---
Sarah 5
Wendy 5
Hallie 6
Klee 4
Christian 9
0
Paddy 5
Listing 5.21List the books whose titles contain fewer than 30 characters, sorted by ascending title length. See Figure 5.21 for the result.
SELECT
title_name,
CHARACTER_LENGTH(title_name) AS "Len"
FROM titles
WHERE CHARACTER_LENGTH(title_name) < 30
ORDER BY CHARACTER_LENGTH(title_name) ASC;
Figure 5.21Result of Listing 5.21.
title_name Len
----------------------------- ---
1977! 5
Kiss My Boo-Boo 15
How About Never? 16
I Blame My Mother 17
Exchange of Platitudes 22
200 Years of German Humor 25
Spontaneous, Not Annoying 25
But I Did It Unconsciously 26
Not Without My Faberge Egg 26
Just Wait Until After School 28
Ask Your System Administrator 29
In Microsoft Access and Microsoft SQL Server, the string-length function is LEN(string). To run Listings 5.20 and 5.21, change the length expressions to (Listing 5.20):
LEN(au_fname)
and (Listing 5.21):
LEN(title_name)
In Oracle and Db2, the string-length function is LENGTH(string). To run Listings 5.20 and 5.21, change the length expressions to (Listing 5.20):
LENGTH(au_fname)
and (Listing 5.21):
LENGTH(title_name)
Bit- and byte-count functions vary by DBMS. Microsoft Access has Len(). Microsoft SQL Server has DATALENGTH(). Oracle has LENGTHB(). Db2 has LENGTH(). MySQL has BIT_COUNT() and OCTET_LENGTH(). PostgreSQL has BIT_LENGTH() and OCTET_LENGTH().
For string operations, Oracle treats a null as an empty string: LENGTH('') returns NULL (not 0). Listing 5.20 will show 1 (not 0) in the next-to-last row because the author’s first name is ' ' (a space) in the Oracle database. For more information, see the DBMS tip in “Tips for Nulls” in Chapter 3.
Use the function POSITION() to locate a particular substring within a given string. The function’s important characteristics are:
To find a substring:
Type:
POSITION(substring IN string)
substring is the string to search for, and string is the string to search. Each argument is a string expression such as a column that contains character strings, a string literal, or the result of an operation or function that returns a string. POSITION() returns the lowest (integer) position in string in which substring occurs, or zero if substring isn’t found (Listings 5.22 and 5.23, Figures 5.22 and 5.23).
Listing 5.22List the position of the substring e in the authors’ first names and the position of the substring ma in the authors’ last names. See Figure 5.22 for the result.
SELECT
au_fname,
POSITION('e' IN au_fname) AS "Pos e",
au_lname,
POSITION('ma' IN au_lname) AS "Pos ma"
FROM authors;
Figure 5.22Result of Listing 5.22.
au_fname Pos e au_lname Pos ma
--------- ----- ----------- ------
Sarah 0 Buchman 5
Wendy 2 Heydemark 6
Hallie 6 Hull 0
Klee 3 Hull 0
Christian 0 Kells 0
0 Kellsey 0
Paddy 0 O'Furniture 0
Listing 5.23List the books whose titles contain the letter u somewhere within the first 10 characters, sorted by descending position of the u. See Figure 5.23 for the result.
SELECT
title_name,
POSITION('u' IN title_name) AS "Pos"
FROM titles
WHERE POSITION('u' IN title_name)
BETWEEN 1 AND 10
ORDER BY POSITION('u' IN title_name) DESC;
Figure 5.23Result of Listing 5.23.
title_name Pos
----------------------------- ---
Not Without My Faberge Egg 10
Spontaneous, Not Annoying 10
How About Never? 8
Ask Your System Administrator 7
But I Did It Unconsciously 2
Just Wait Until After School 2
The SQL standard also defines the function OVERLAY() to replace substrings. The syntax is:
OVERLAY(string PLACING substring FROM start_position [FOR length])
For example, OVERLAY('Txxxxas' PLACING 'hom' FROM 2 FOR 4) is 'Thomas'. The equivalent functions in the DBMSs are REPLACE() (Microsoft Access, Microsoft SQL Server, Db2, and MySQL), REGEXP_REPLACE() (Oracle), and OVERLAY() (PostgreSQL).
In Microsoft Access, the position function is InStr(start_position, string, substring). To run Listings 5.22 and 5.23, change the position expressions to (Listing 5.22):
InStr(1, au_fname, 'e')
InStr(1, au_lname, 'ma')
and (Listing 5.23):
InStr(1, title_name, 'u')
In Microsoft SQL Server, the position function is CHARINDEX(substring, string). To run Listings 5.22 and 5.23, change the position expressions to (Listing 5.22):
CHARINDEX('e', au_fname)
CHARINDEX('ma', au_lname)
and (Listing 5.23):
CHARINDEX('u', title_name)
In Oracle, the position function is INSTR(string, substring). To run Listings 5.22 and 5.23, change the position expressions to (Listing 5.22):
INSTR(au_fname, 'e')
INSTR(au_lname, 'ma')
and (Listing 5.23):
INSTR(title_name, 'u')
In Db2, the position function is POSSTR(string, substring). To run Listings 5.22 and 5.23, change the position expressions to (Listing 5.22):
POSSTR(au_fname, 'e')
POSSTR(au_lname, 'ma')
and (Listing 5.23):
POSSTR(title_name, 'u')
For string operations, Oracle treats a null as an empty string: INSTR('', substring) returns NULL (not 0). See the DBMS tip in “Tips for Nulls” in Chapter 3.
You can nest and chain substring and position functions to find substring occurrences beyond the first occurrence, but DBMSs provide enhanced position functions to do that. Microsoft Access has InStr(). Microsoft SQL Server has CHARINDEX(). Oracle has INSTR(). Db2 has LOCATE(). MySQL has LOCATE().
DBMS compliance with standard SQL datetime and interval operators and functions is spotty because DBMSs usually provide their own extended (nonstandard) operators and functions that perform date and time arithmetic. For information about datetime and interval data types, see “Datetime Types” and “Interval Types” in Chapter 3.
Use the same operators introduced in “Performing Arithmetic Operations” earlier in this chapter to perform datetime and interval arithmetic. The common temporal operations are:
Some operations are undefined; adding two dates makes no sense, for example. Table 5.3 lists the valid SQL operators involving datetimes and intervals. The “Operator Overloading” section explains why you can use the same operator to perform different operations.
Operation | Result |
---|---|
Datetime – Datetime | Interval |
Datetime + Interval | Datetime |
Datetime – Interval | Datetime |
Interval + Datetime | Datetime |
Interval + Interval | Interval |
Interval – Interval | Interval |
Interval * Numeric | Interval |
Interval / Numeric | Interval |
Numeric * Interval | Interval |
Operator Overloading
Recall that the +, –, *, and / operators also are used for numeric operations and that Microsoft DBMSs use + for string concatenation as well. Operator overloading is the assignment of more than one function to a particular operator. The operation performed depends on the data types of the operands involved. Here, the +, –, *, and / operators behave differently with numbers than they do with datetimes and intervals (as well as strings, in the case of Microsoft Access and Microsoft SQL Server). Your DBMS might overload other operators and functions as well.
Function overloading is the assignment of more than one behavior to a particular function, depending on the data types of the arguments involved. The MySQL CONCAT() function (see the DBMS tip in “Tips for Concatenating Strings” earlier in this chapter), for example, takes nonstring as well as string arguments. Nonstrings cause CONCAT() to perform additional conversions that it doesn’t need to perform on strings.
The function EXTRACT() isolates a single field of a datetime or interval and returns it as a number. EXTRACT() typically is used in comparison expressions or for formatting results.
To extract part of a datetime or interval:
Type:
EXTRACT(field FROM datetime_or_interval)
field is the part of datetime_or_interval to return. field is YEAR, MONTH, DAY, HOUR, MINUTE, SECOND, TIMEZONE_HOUR, or TIMEZONE_MINUTE (refer to Table 3.14 in Chapter 3). datetime_or_interval is a datetime or interval expression such as a column that contains datetime or interval values, a datetime or interval literal, or the result of an operation or function that returns a datetime or interval. If field is SECOND, then EXTRACT() returns a NUMERIC value; otherwise, it returns an INTEGER (Listing 5.24 and Figure 5.24).
Listing 5.24List the books published in the first half of the years 2001 and 2002, sorted by descending publication date. See Figure 5.24 for the result.
SELECT
title_id,
pubdate
FROM titles
WHERE EXTRACT(YEAR FROM pubdate)
BETWEEN 2001 AND 2002
AND EXTRACT(MONTH FROM pubdate)
BETWEEN 1 AND 6
ORDER BY pubdate DESC;
Figure 5.24Result of Listing 5.24.
title_id pubdate
-------- ----------
T09 2002-05-31
T08 2001-06-01
T05 2001-01-01
In Microsoft Access and Microsoft SQL Server, the extraction function is DATEPART(datepart, date). To run Listing 5.24, change the extraction expressions to:
DATEPART("yyyy", pubdate)
DATEPART("m", pubdate)
Oracle, MySQL, and PostgreSQL accept different or additional values for the field argument of EXTRACT().
Instead of EXTRACT(), Db2 extracts parts by using individual functions such as DAY(), HOUR(), and SECOND(). To run Listing 5.24, change the extraction expressions to:
YEAR(pubdate)
MONTH(pubdate)
In addition to (or instead of) the standard arithmetic operators, DBMSs provide functions that add intervals to dates. Some examples: DATEDIFF() in Microsoft Access and Microsoft SQL Server, ADD_MONTHS() in Oracle, and DATE_ADD() and DATE_SUB() in MySQL.
Complex date and time arithmetic is so common in SQL programming that all DBMSs provide lots of temporal extensions. Search your DBMS documentation for date and time functions or datetime functions.
Use the functions CURRENT_DATE, CURRENT_TIME, and CURRENT_TIMESTAMP to get the current date and time from the system clock of the particular computer where the DBMS is running.
To get the current date and time:
To get the current date, type:
CURRENT_DATE
or
To get the current time, type:
CURRENT_TIME
or
To get the current timestamp, type:
CURRENT_TIMESTAMP
CURRENT_DATE returns a DATE, CURRENT_TIME returns a TIME, and CURRENT_TIMESTAMP returns a TIMESTAMP; see “Datetime Types” in Chapter 3 (Listings 5.25 and 5.26, Figures 5.25 and 5.26).
Listing 5.25Print the current date, time, and timestamp. See Figure 5.25 for the result.
SELECT
CURRENT_DATE AS "Date",
CURRENT_TIME AS "Time",
CURRENT_TIMESTAMP AS "Timestamp";
Figure 5.25Result of Listing 5.25.
Date Time Timestamp
---------- -------- -------------------
2002-03-10 10:09:24 2002-03-10 10:09:24
Listing 5.26List the books whose publication date falls within 90 days of the current date or is unknown, sorted by descending publication date (refer to Figure 5.25 for the “current” date of this query). See Figure 5.26 for the result.
SELECT title_id, pubdate
FROM titles
WHERE pubdate
BETWEEN CURRENT_TIMESTAMP - INTERVAL 90 DAY
AND CURRENT_TIMESTAMP + INTERVAL 90 DAY
OR pubdate IS NULL
ORDER BY pubdate DESC;
Figure 5.26Result of Listing 5.26.
title_id pubdate
-------- ----------
T09 2002-05-31
T10 NULL
In Microsoft Access, the datetime system functions are Date(), Time(), and Now(). To run Listing 5.25, change the datetime expressions to:
Date() AS "Date"
Time() AS "Time"
Now() AS "Timestamp"
To run Listing 5.26, change the BETWEEN clause to:
BETWEEN NOW() - 90
AND NOW() + 90
In Microsoft SQL Server, the datetime system function is CURRENT_TIMESTAMP (or its synonym, GETDATE()). CURRENT_DATE and CURRENT_TIME aren’t supported. To run Listing 5.25, omit the CURRENT_DATE and CURRENT_TIME expressions. To run Listing 5.26, change the BETWEEN clause to:
BETWEEN CURRENT_TIMESTAMP - 90
AND CURRENT_TIMESTAMP + 90
In Oracle, the datetime system function is SYSDATE. Oracle 9i and later versions support CURRENT_DATE and CURRENT_TIMESTAMP (but not CURRENT_TIME). Listing 5.25 also requires the clause FROM DUAL
; see the DBMS tip in “Tips for Derived Columns” earlier in this chapter. To run Listing 5.25, change the statement to:
SELECT SYSDATE AS "Date"
FROM DUAL;
SYSDATE returns the system date and time but doesn’t display the time unless formatted to do so with the function TO_CHAR():
SELECT TO_CHAR(SYSDATE, 'YYYY-MM-DD HH24:MI:SS')
FROM DUAL;
To run Listing 5.26, change the BETWEEN clause to:
BETWEEN SYSDATE - 90
AND SYSDATE + 90
To run Listing 5.25 in Db2, add the clause FROM SYSIBM.SYSDUMMY1
; see the DBMS tip in “Tips for Derived Columns” earlier in this chapter. To run Listing 5.26, change the WHERE clause to:
BETWEEN CURRENT_DATE - 90 DAYS
AND CURRENT_DATE + 90 DAYS
To run Listing 5.26 in PostgreSQL, change the WHERE clause to:
BETWEEN CURRENT_TIMESTAMP - 90
AND CURRENT_TIMESTAMP + 90
For information about datetime system functions, search your DBMS documentation for date and time functions or system functions.
Use the function CURRENT_USER to identify the active user within the database server.
To get the current user:
Type:
CURRENT_USER
(Listing 5.27 and Figure 5.27).
Listing 5.27Print the current user. See Figure 5.27 for the result.
SELECT CURRENT_USER AS "User";
Figure 5.27Result of Listing 5.27.
User
------
jsmith
To run Listing 5.27 in Microsoft Access, change the statement to:
SELECT CurrentUser AS "User";
To run Listing 5.27 in Oracle, change the statement to:
SELECT USER AS "User"
FROM DUAL;
To run Listing 5.27 in Db2, change the statement to:
SELECT CURRENT_USER AS "User"
FROM SYSIBM.SYSDUMMY1;
To run Listing 5.27 in MySQL, change the statement to:
SELECT USER() AS "User";
Microsoft SQL Server supports SESSION_USER and SYSTEM_USER. MySQL supports SESSION_USER() and SYSTEM_USER(). Oracle’s SYS_CONTEXT() returns a session’s user attributes. Db2 supports SESSION_USER and SYSTEM_USER. PostgreSQL supports SESSION_USER.
For information about user system functions, search your DBMS documentation for user or system functions.
In many situations, your DBMS will convert, or cast, data types automatically. It might allow you to use numbers and dates in character expressions such as concatenation, for example, or it will promote numbers automatically in mixed arithmetic expressions (see “Tips for Arithmetic Operations” earlier in this chapter). Use the function CAST() to convert an expression of one data type to another data type when your DBMS doesn’t perform the conversion automatically. For information about data types, see “Data Types” in Chapter 3. The function’s important characteristics are:
To convert one data type to another:
Type:
CAST(expr AS data_type)
expr is the expression to convert, and data_type is the target data type. data_type is one of the data types described in Chapter 3 and can include length, precision, or scale arguments where applicable. Acceptable data_type values include CHAR(10), VARCHAR(25), NUMERIC(5,2), INTEGER, FLOAT, and DATE, for example. An error occurs if the data type or value of expr is incompatible with data_type (Listings 5.28 and 5.29, Figures 5.28a, 5.28b, and 5.29).
Listing 5.28Convert the book prices from the DECIMAL data type to INTEGER and CHAR(8) data types. The < and > characters show the extent of the CHAR(8) strings. Your result will be either Figure 5.28a or 5.28b, depending on whether your DBMS truncates or rounds integers.
SELECT
price
AS "price(DECIMAL)",
CAST(price AS INTEGER)
AS "price(INTEGER)",
'<' || CAST(price AS CHAR(8)) || '>'
AS "price(CHAR(8))"
FROM titles;
Figure 5.28aResult of Listing 5.28. You’ll get this result if your DBMS truncates decimal numbers to convert them to integers.
price(DECIMAL) price(INTEGER) price(CHAR(8))
-------------- -------------- --------------
21.99 21 <21.99 >
19.95 19 <19.95 >
39.95 39 <39.95 >
12.99 12 <12.99 >
6.95 6 <6.95 >
19.95 19 <19.95 >
23.95 23 <23.95 >
10.00 10 <10.00 >
13.95 13 <13.95 >
NULL NULL NULL
7.99 7 <7.99 >
12.99 12 <12.99 >
29.99 29 <29.99 >
Figure 5.28bResult of Listing 5.28. You’ll get this result if your DBMS rounds decimal numbers to convert them to integers.
price(DECIMAL) price(INTEGER) price(CHAR(8))
-------------- -------------- --------------
21.99 22 <21.99 >
19.95 20 <19.95 >
39.95 40 <39.95 >
12.99 13 <12.99 >
6.95 7 <6.95 >
19.95 20 <19.95 >
23.95 24 <23.95 >
10.00 10 <10.00 >
13.95 14 <13.95 >
NULL NULL NULL
7.99 8 <7.99 >
12.99 13 <12.99 >
29.99 30 <29.99 >
Listing 5.29List history and biography book sales with a portion of the book title, sorted by descending sales. The CHAR(20) conversion shortens the title to make the result more readable. See Figure 5.29 for the result.
SELECT
CAST(sales AS CHAR(8))
|| ' copies sold of '
|| CAST(title_name AS CHAR(20))
AS "History and biography sales"
FROM titles
WHERE sales IS NOT NULL
AND type IN ('history', 'biography')
ORDER BY sales DESC;
Figure 5.29Result of Listing 5.29.
History and biography sales
--------------------------------------------
1500200 copies sold of I Blame My Mother
100001 copies sold of Spontaneous, Not Ann
11320 copies sold of How About Never?
10467 copies sold of What Are The Civilia
9566 copies sold of 200 Years of German
566 copies sold of 1977!
Microsoft Access has a family of type-conversion functions rather than a single CAST() function: CStr(expr), CInt(expr), and CDec(expr) convert expr to a string, integer, and decimal number, for example. You can use Space(number) to add spaces to strings and Left(string, length) to truncate strings. Use + to concatenate strings. To run Listings 5.28 and 5.29, change the cast expressions to (Listing 5.28):
CInt(price)
'<' + CStr(price) + '>'
and (Listing 5.29):
CStr(sales) + Space(8 - Len(CStr(sales))) + ' copies sold of ' + Left(title_name, 20)
In Microsoft SQL Server, use + to concatenate strings (Listing 5.28):
'<' + CAST(price AS CHAR(8)) + '>'
and (Listing 5.29):
CAST(sales AS CHAR(8)) + ' copies sold of ' + CAST(title_name AS CHAR(20))
Oracle doesn’t allow character conversions to CHAR(length) if length is shorter than the source string. Instead, use SUBSTR() to truncate strings; see the DBMS tip in “Tips for Substrings” earlier in this chapter. To run Listing 5.29, change the CAST() expression to:
CAST(sales AS CHAR(8)) || ' copies sold of ' || SUBSTR(title_name, 1, 20)
In MySQL, use SIGNED instead of INTEGER for data_type, and use CONCAT() to concatenate strings. To run Listings 5.28 and 5.29, change the CAST() expressions to (Listing 5.28):
CAST(price AS SIGNED)
CONCAT('<', CAST(price AS CHAR(8)), '>')
and (Listing 5.29):
CONCAT(CAST(sales AS CHAR(8)), ' copies sold of ', CAST(title_name AS CHAR(20)))
For cast operations, Oracle treats an empty string as a null: CAST('' AS CHAR) returns NULL (not ''). See the DBMS tip in “Tips for Nulls” in Chapter 3.
In older PostgreSQL versions, to compare a value in a NUMERIC or DECIMAL column with a real (floating-point) number, you must convert the real number to NUMERIC or DECIMAL explicitly. The following statement, for example, fails in older PostgreSQL versions because the data type of the column price is DECIMAL(5,2):
SELECT price
FROM titles
WHERE price < 20.00;
This statement fixes the problem:
SELECT price
FROM titles
WHERE price < CAST(20.00 AS DECIMAL);
DBMSs have additional conversion and formatting functions. Some examples: CONVERT() in Microsoft SQL Server and MySQL; TO_CHAR(), TO_DATE(), TO_TIMESTAMP(), and TO_NUMBER() in Oracle and PostgreSQL; and TO_CHAR() and TO_DATE() in Db2. Search your DBMS documentation for conversion, cast, or formatting functions.
The CASE expression and its shorthand equivalents, COALESCE() and NULLIF(), let you take actions based on a condition’s truth value (true, false, or unknown). The CASE expression’s important characteristics are:
To use a simple CASE expression:
Type:
CASE comparison_value
WHEN value1 THEN result1
WHEN value2 THEN result2
...
WHEN valueN THEN resultN
[ELSE default_result]
END
value1, value2,..., valueN are expressions. result1, result2,..., resultN are expressions returned when the corresponding value matches the expression comparison_value. All expressions must be of the same type or must be implicitly convertible to the same type.
Each value is compared to comparison_value in order. First, value1 is compared. If it matches comparison_value, then result1 is returned; otherwise, value2 is compared to comparison_value. If value2 matches comparison_value, then result2 is returned, and so on. If no matches occur, then default_result is returned. If ELSE default_result is omitted, then ELSE NULL is assumed (Listing 5.30 and Figure 5.30).
Listing 5.30Raise the price of history books by 10 percent and psychology books by 20 percent, and leave the prices of other books unchanged. See Figure 5.30 for the result.
SELECT
title_id,
type,
price,
CASE type
WHEN 'history'
THEN price * 1.10
WHEN 'psychology'
THEN price * 1.20
ELSE price
END
AS "New price"
FROM titles
ORDER BY type ASC, title_id ASC;
Figure 5.30Result of Listing 5.30.
title_id type price New price
-------- ---------- ----- ---------
T06 biography 19.95 19.95
T07 biography 23.95 23.95
T10 biography NULL NULL
T12 biography 12.99 12.99
T08 children 10.00 10.00
T09 children 13.95 13.95
T03 computer 39.95 39.95
T01 history 21.99 24.19
T02 history 19.95 21.95
T13 history 29.99 32.99
T04 psychology 12.99 15.59
T05 psychology 6.95 8.34
T11 psychology 7.99 9.59
To use a searched CASE expression:
Type:
CASE
WHEN condition1 THEN result1
WHEN condition2 THEN result2
...
WHEN conditionN THEN resultN
[ELSE default_result]
END
condition1, condition2,..., conditionN are search conditions. (Search conditions have one or more logical expressions, with multiple expressions linked by AND or OR; see “Filtering Rows with WHERE” in Chapter 4.) result1, result2,..., resultN are expressions returned when the corresponding condition evaluates to true. All expressions must be of the same type or must be implicitly convertible to the same type.
Each condition is evaluated in order. First, condition1 is evaluated. If it’s true, then result1 is returned; otherwise, condition2 is evaluated. If condition2 is true, then result2 is returned, and so on. If no conditions are true, then default_result is returned. If ELSE default_result is omitted, then ELSE NULL is assumed (Listing 5.31 and Figure 5.31).
Listing 5.31List the books categorized by different sales ranges, sorted by ascending sales. See Figure 5.31 for the result.
SELECT
title_id,
CASE
WHEN sales IS NULL
THEN 'Unknown'
WHEN sales <= 1000
THEN 'Not more than 1,000'
WHEN sales <= 10000
THEN 'Between 1,001 and 10,000'
WHEN sales <= 100000
THEN 'Between 10,001 and 100,000'
WHEN sales <= 1000000
THEN 'Between 100,001 and 1,000,000'
ELSE 'Over 1,000,000'
END
AS "Sales category"
FROM titles
ORDER BY sales ASC;
Figure 5.31Result of Listing 5.31.
title_id Sales category
-------- -----------------------------
T10 Unknown
T01 Not more than 1,000
T08 Between 1,001 and 10,000
T09 Between 1,001 and 10,000
T02 Between 1,001 and 10,000
T13 Between 10,001 and 100,000
T06 Between 10,001 and 100,000
T04 Between 10,001 and 100,000
T03 Between 10,001 and 100,000
T11 Between 10,001 and 100,000
T12 Between 100,001 and 1,000,000
T05 Between 100,001 and 1,000,000
T07 Over 1,000,000
This CASE expression can help you prevent division-by-zero errors:
CASE
WHEN n <> 0 THEN expr/n
ELSE NULL
END
You can use CASE to omit identical function calls.
WHERE some_function(col1) = 10
OR some_function(col1) = 20
is equivalent to
WHERE 1 =
CASE some_function(col1)
WHEN 10 THEN 1
WHEN 20 THEN 1
END
Some DBMS optimizers will run the CASE form faster.
The simple CASE expression is just shorthand for this searched CASE expression:
CASE
WHEN comparison_value = value1 THEN result1
WHEN comparison_value = value2 THEN result2
...
WHEN comparison_value = valueN THEN resultN
[ELSE default_result]
END
Microsoft Access doesn’t support CASE; instead, use the function Switch(condition1, result1, condition2, result2,...). To run Listings 5.30 and 5.31, change the CASE expressions to (Listing 5.30):
Switch(
type IS NULL, NULL,
type = 'history', price * 1.10,
type = 'psychology', price * 1.20,
type IN ('biography', 'children', 'computer'), price)
and (Listing 5.31):
Switch(
sales IS NULL,
'Unknown',
sales <= 1000,
'Not more than 1,000',
sales <= 10000,
'Between 1,001 and 10,000',
sales <= 100000,
'Between 10,001 and 100,000',
sales <= 1000000,
'Between 100,001 and 1,000,000',
sales > 1000000, 'Over 1,000,000')
Oracle 9i and later will run Listings 5.30 and 5.31. To run Listing 5.30 in Oracle 8i and earlier, translate the simple CASE expression to a searched CASE expression, or use the function DECODE(comparison_value, value1, result1, value2, result2,..., default_result):
DECODE(type,
NULL, NULL,
'history', price * 1.10,
'psychology', price * 1.20,
price)
In older PostgreSQL versions, convert the floating-point numbers in Listing 5.30 to DECIMAL; see “Converting Data Types with CAST()” earlier in this chapter. To run Listing 5.30, change the new-price calculations in the CASE expression to:
price * CAST((1.10) AS DECIMAL)
price * CAST((1.20) AS DECIMAL)
The function COALESCE() returns the first non-null expression among its arguments. COALESCE() often is used to display a specific value instead of a null in a result, which is helpful if your users find nulls confusing. COALESCE() is just shorthand for a common form of the searched CASE expression.
COALESCE(expr1, expr2, expr3)
is equivalent to:
CASE
WHEN expr1 IS NOT NULL THEN expr1
WHEN expr2 IS NOT NULL THEN expr2
ELSE expr3
END
To return the first non-null value:
Type:
COALESCE(expr1, expr2,...)
expr1, expr2,..., represent one or more comma-separated expressions. All expressions must be of the same type or must be implicitly convertible to the same type. Each expression is evaluated in order (left to right) until one evaluates to non-null and is returned. If all the expressions are null, then COALESCE() returns null (Listing 5.32 and Figure 5.32).
Listing 5.32List the publishers’ locations. If the state is null, then print N/A. See Figure 5.32 for the result.
SELECT
pub_id,
city,
COALESCE(state, 'N/A') AS "state",
country
FROM publishers;
Figure 5.32Result of Listing 5.32.
pub_id city state country
------ ------------- ----- -------
P01 New York NY USA
P02 San Francisco CA USA
P03 Hamburg N/A Germany
P04 Berkeley CA USA
Microsoft Access doesn’t support COALESCE(); instead, use the function Switch(). To run Listing 5.32, change the COALESCE() expression to:
Switch(
state IS NOT NULL, state,
state IS NULL, 'N/A')
Oracle 9i and later will run Listing 5.32. Oracle 8i and earlier don’t support COALESCE(); instead, use the function NVL(expr1, expr2). NVL() takes only two expressions; use CASE for three or more expressions. To run Listing 5.32 in Oracle 8i and earlier, change the COALESCE() expression to:
NVL(state, 'N/A')
The function NULLIF() compares two expressions and returns null if they are equal or the first expression otherwise. NULLIF() typically is used to convert a user-defined missing, unknown, or inapplicable value to null.
Rather than use a null, some people prefer to represent a missing value with, say, the number −1 or −99, or the string ‘N/A’, ‘Unknown’, or ‘Missing’. DBMSs have clear rules for operations that involve nulls, so it’s sometimes desirable to convert user-defined missing values to nulls. If you want to calculate the average of the values in a column, for example, then you’d get the wrong answer if you had −1 values intermingled with the real, non-missing values. Instead, you can use NULLIF() to convert the −1 values to nulls, which your DBMS will ignore during calculations.
NULLIF() is just shorthand for a common form of the searched CASE expression.
NULLIF(expr1, expr2)
is equivalent to:
CASE
WHEN expr1 = expr2 THEN NULL
ELSE expr1
END
To return a null if two expressions are equivalent:
Type:
NULLIF(expr1, expr2)
expr1 and expr2 are expressions. NULLIF() compares expr1 and expr2. If they are equal, then the function returns null. If they’re unequal, then the function returns expr1. You can’t specify the literal NULL for expr1 (Listing 5.33 and Figure 5.33).
Listing 5.33In the table titles, the column contract contains zero if no book contract exists. This query changes the value zero to null. Nonzero values aren’t affected. See Figure 5.33 for the result.
SELECT
title_id,
contract,
NULLIF(contract, 0) AS "Null contract"
FROM titles;
Figure 5.33Result of Listing 5.33.
title_id contract Null contract
-------- -------- -------------
T01 1 1
T02 1 1
T03 1 1
T04 1 1
T05 1 1
T06 1 1
T07 1 1
T08 1 1
T09 1 1
T10 0 NULL
T11 1 1
T12 1 1
T13 1 1
Microsoft Access doesn’t support NULLIF(); instead, use the expression iif(expr1 = expr2, NULL, expr1). To run Listing 5.33, change the NULLIF() expression to:
iif(contract = 0, NULL, contract)
Oracle 9i and later will run Listing 5.33. Oracle 8i and earlier don’t support NULLIF(); instead, use CASE. To run Listing 5.33 in Oracle 8i and earlier, change the NULLIF() expression to:
CASE
WHEN contract = 0 THEN NULL
ELSE contract
END
Avoiding Division by Zero
Suppose you want to calculate the male–female ratios for various school clubs, but you discover that the following query fails and issues a divide-by-zero error when it tries to calculate ratio for the Lord of the Rings Club, which has no women:
SELECT
club_id, males, females,
males/females AS ratio
FROM school_clubs;
You can use NULLIF to avoid division by zero. Rewrite the query as:
SELECT
club_id, males, females,
males/NULLIF(females,0) AS ratio
FROM school_clubs;
Any number divided by NULL gives NULL, and no error is generated.