SQL Technology
SQL Technology
ECKHARDT
FRIEDLI
JAMES
LEBLANC
MICHAELS
NEWTON
PETERSON
7 rows selected.
Code Listing 2: Query that displays all department locations in lowercase letters
SQL> select name, LOWER(location) "Lowercase Department Location"
2 from department
3 order by location;
NAME Lowercase Department Location
Accounting los angeles
Payroll new york
2 rows selected.
Listings 3 and 4 demonstrate the INITCAP function. The query in Listing 3 uses INITCAP to
convert certain first and last names from being stored in all lowercase in the EMPLOYEE table to
being displayed with initial capital letters. The INITCAP function capitalizes the first letter of a
string and lowercases the remainder of the string, as demonstrated by the query in Listing 4. That
query also shows that the input parameter for an INITCAP function can consist of a character
functions application to a string or a database column that stores alphanumeric data. Specifically,
the query applies the UPPER function to the LAST_NAME column of the EMPLOYEE table for
certain employees. The UPPER function is said to be nested inside the INITCAP function. The
SQL 101
http://www.oracle.com/technetwork/issue-archive/2012/12-jul/o42sql-1603138.html[8/30/2014 12:37:18 PM]
Oracle Database server applies nested functions in order, from innermost function to outermost
function. In Listing 4, the UPPER function converts the values peterson and leblanc to
PETERSON and LEBLANC. Then the INITCAP function converts those uppercase values to
Peterson and Leblanc.
Code Listing 3: Query that shows certain names converted and with the initial letter capitalized
SQL> set lines 32000
SQL> select first_name, last_name, INITCAP(first_name) "First Name",
INITCAP(last_name) "Last Name"
2 from employee
3 where employee_id in (6569, 6570);
FIRST_NAME LAST_NAME First Name Last Name
michael peterson Michael Peterson
mark leblanc Mark Leblanc
2 rows selected.
Code Listing 4: Query that demonstrates the INITCAP function
SQL> select INITCAP('eMPLOYEE lAST nAMES') "INITCAP Literal",
INITCAP(UPPER(last_name)) "Converted Employee Last Name"
2 from employee
3 where employee_id in (6569, 6570);
INITCAP Literal Converted Employee Last Name
Employee Last Names Peterson
Employee Last Names Leblanc
2 rows selected.
Padding Your Results
To pad something is to add to it. The LPAD and RPAD functions enable you to pad your
character-data results by repeating a character, space, or symbol to the left or right of any string.
LPAD pads to the left of a string; RPAD pads to the right.
Listing 5 demonstrates the power of the RPAD and LPAD functions. Note that each takes three
input parameters: the column name or string literal you want to pad; the length to which the string
should be padded; and the character, space, or symbol (the filler) to pad with. For example, the
query in Listing 5 specifies that the department name should be right-padded to a total length of
15 with the . filler character. If any department name is exactly 15 characters or longer, no filler
character will be added. Because Accounting is 10 characters long, the RPAD function adds five
filler characters to its right. The query also specifies that the location should be left-padded to a
total length of 15. Because LOS ANGELES is 11 characters long, counting the space, the LPAD
function adds four filler characters to its left.
Code Listing 5: Query that applies the RPAD and the LPAD functions
SQL> select RPAD(name, 15, '.') department, LPAD(location, 15, '.')
location
2 from department;
DEPARTMENT LOCATION
Accounting..... ....LOS ANGELES
Payroll........ .......NEW YORK
2 rows selected.
The Helpful Dual
Oracle Database provides a single-row, single-column table called DUAL that is useful for many
purposes, not the least of which is learning about Oracle functions. DUAL is an Oracle system
table owned by the SYS user, not the SQL_101 schema. Many Oracle system tables are made
available to all users via public synonyms. Synonyms will be discussed in subsequent articles in
this series.
The DUAL table contains no data thats useful in and of itself. (It has one row with one column
called the DUMMY columnthat contains the value X.) You can use DUAL to try out functions
that work on string literals and, as youll see in subsequent articles in this series, on number
literals and even on todays date.
The following demonstrates the single-row, single-column output of a SELECT statement executed
against the DUAL table:
SQL> select *
2 from dual;
D
-
X
1 row selected.
To display the current date, you can query the DUAL table as follows:
SQL> select sysdate
2 from dual;
SQL 101
http://www.oracle.com/technetwork/issue-archive/2012/12-jul/o42sql-1603138.html[8/30/2014 12:37:18 PM]
SYSDATE
18-APR-12
1 row selected.
And finally, the following example shows how you can practice any function in the SELECT clause
of a SQL statement, using the DUAL table:
SQL> select rpad('Melanie', 10, '*') Melanie, lpad('Caffrey', 10, '.')
Caffrey
2 from dual;
MELANIE CAFFREY
Melanie*** ...Caffrey
1 row selected.
Note that functions work even though there is no usable data in DUAL. In the preceding examples,
the SYSDATE function displays the current date and time of the operating system hosting the
database, and the RPAD and LPAD functions add padding to my name.
Stringing Strings Together
Sometimes it makes sense to combine certain strings, such as the FIRST_NAME and
LAST_NAME values from the EMPLOYEE table, in the result set display. You can use
concatenation to accomplish this taskwith either the CONCAT function, illustrated in Listing 6, or
the (more commonly used) concatenation operator || (two pipe characters), illustrated in Listing 7.
Code Listing 6: Query that demonstrates the CONCAT function
SQL> select CONCAT(first_name, last_name) employee_name
2 from employee
3 order by employee_name;
EMPLOYEE_NAME
BetsyJames
DonaldNewton
EmilyEckhardt
FrancesNewton
MatthewMichaels
RogerFriedli
markleblanc
michaelpeterson
8 rows selected.
Code Listing 7: Query that demonstrates the concatenation operator, ||
SQL> select first_name||' '||last_name employee_name
2 from employee
3 order by employee_name;
EMPLOYEE_NAME
Betsy James
Donald Newton
Emily Eckhardt
Frances Newton
Matthew Michaels
Roger Friedli
mark leblanc
michael peterson
8 rows selected.
The CONCAT function takes two parameters and concatenates them. You can also nest multiple
CONCAT function calls, as shown in Listing 8. The queries in Listings 7 and 8 concatenate literal
strings with column data values. (I prefer the concatenation operator, because it has unlimited
input parameters and makes the concatenated output more readable.)
Code Listing 8: Query that demonstrates nested CONCAT calls
SQL> select CONCAT(first_name, CONCAT(' ', last_name)) employee_name
2 from employee
3 order by employee_name;
EMPLOYEE_NAME
Betsy James
Donald Newton
Emily Eckhardt
Frances Newton
Matthew Michaels
Roger Friedli
mark leblanc
michael peterson
8 rows selected.
Giving Your Data a Trim
Sometimes you want to remove unwanted spaces or characters from data when you display it. For
example, data inserted into a table column via a form application might include extraneous
characters or spacespreceding and/or following the actual data valuethat the form input field
doesnt trim.
SQL 101
http://www.oracle.com/technetwork/issue-archive/2012/12-jul/o42sql-1603138.html[8/30/2014 12:37:18 PM]
Next Steps
READ more about relational database
design and concepts
Listing 9 shows a query that trims extra spaces from string values. The TRIM function in Listing 9
takes two parameters. The first parameter is the character, symbol, or space (delimited by single
quotes) to be removed. The second parameter specifies the string literal or column value to be
trimmed. The TRIM function supports three keywords: LEADING, TRAILING, and BOTH. The
example in Listing 9 uses the TRAILING keyword to right-trim the FIRST_NAME value. The TRIM
function applied to the LAST_NAME value specifies the LEADING keyword to left-trim the spaces
from that value. And, as you can see, the spaces to the right of the LAST_NAME value remain
and are included in the output.
Code Listing 9: Query that trims extra spaces
SQL> select '''' ||TRIM(TRAILING ' ' FROM 'Ashton ') || ''''
first_name,
'''' || TRIM(LEADING ' ' FROM ' Cinder ') || '''' last_name
2 from dual;
FIRST_NA LAST_NAME
'Ashton' 'Cinder '
1 row selected.
Compare the output in Listing 9 with that in Listing 10, which trims the rightmost extra spaces from
the LAST_NAME value. When no keyword is specified, the default behavior for the TRIM function
is to trim leading as well as trailing characters. The older RTRIM and LTRIM functions are
available for backward compatibility.
Code Listing 10: Query that trims extra spaces, including rightmost extra spaces
SQL> select '''' || TRIM(TRAILING ' ' FROM 'Ashton ') || ''''
first_name,
'''' || TRIM(' Cinder ') || '''' last_name
2 from dual;
FIRST_NA LAST_NAM
'Ashton' 'Cinder'
1 row selected.
Searching for Strings Within Strings
When you need to search column values for similar string pattern values, you can do so with the
INSTR character function. INSTRwhich stands for in stringreturns the position of a substring
within a string value. Listing 11 demonstrates the INSTR function applied to the LAST_NAME
column of the EMPLOYEE table to locate all occurrences of the ton substring. As you can see,
the INSTR function takes as input the literal or column value you want to search, followed by the
substring pattern to search for. In Listing 11, the INSTR function finds the ton pattern in only two
column data valuesboth of them Newtonand returns 4 as their position. Because it did not find
the search string in any other values, the output for those values is 0.
Code Listing 11: Query that demonstrates the INSTR character function
SQL> select last_name, INSTR(last_name, 'ton') ton_starting_point
2 from employee
3 order by last_name;
LAST_NAME TON_STARTING_POINT
Eckhardt 0
Friedli 0
James 0
Michaels 0
Newton 4
Newton 4
leblanc 0
peterson 0
8 rows selected.
Two additional parametersstarting position and occurrenceare optional. The starting position
specifies the character in the string from which to begin your search. The default behavior is for
the search to begin at the first characterotherwise known as character position 1. The
occurrence parameter lets you specify which occurrence of the substring youd like to find. For
example, the word Mississippi includes two occurrences of the issi substring. To search for the
starting-position location of the second occurrence of this pattern, you must provide the INSTR
function with an occurrence parameter of 2:
SQL> select INSTR('Mississippi', 'issi', 1, 2)
2 from dual;
INSTR('MISSISSIPPI','ISSI',1,2)
5
1 row selected.
Extracting Strings from Strings
Sometimes you need to extract a portion of a string
for your desired output. The SUBSTR (for substring)
character function can assist you with this task.
Listing 12 shows a query that uses the SUBSTR
SQL 101
http://www.oracle.com/technetwork/issue-archive/2012/12-jul/o42sql-1603138.html[8/30/2014 12:37:18 PM]
Oracle Database Concepts 11g
Release 2 (11.2)
Oracle Database SQL Language
Reference 11g Release 1 (11.1)
Oracle SQL Developer Users Guide
Release 3.1
READ SQL 101 Parts 1-5
DOWNLOAD the script for this article
function to extract the first three characters of every
LAST_NAME value from the EMPLOYEE table. The
SUBSTR function takes two required parameters
and one optional input parameter. The first
parameter is the literal or column value on which you
want the SUBSTR function to operate. The second
parameter is the position of the starting character for
the substring, and the optional third parameter is the
number of characters to be included in the substring.
If the third parameter is not specified, the SUBSTR
function will return the remainder of the string.
Code Listing 12: Query that demonstrates the
SUBSTR character function
SQL> select last_name, SUBSTR(last_name, 1, 3)
2 from employee
3 order by last_name;
LAST_NAME SUB
Eckhardt Eck
Friedli Fri
James Jam
Michaels Mic
Newton New
Newton New
leblanc leb
peterson pet
8 rows selected.
Listing 13 demonstrates the SUBSTR and INSTR functions working together to display the portion
of every LAST_NAME value from the EMPLOYEE table that contains the ton substring. In this
example, the output from the INSTR function provides the value for the input parameter that
specifies the position for the SUBSTR functions starting character. In the LAST_NAME values in
which the substring ton is not found, the entire LAST_NAME value is returned, for two reasons:
SUBSTR treats a starting position of 0 the same as a starting position of 1 (that is, as the first
position in the string), and because the query omits the optional length parameter, the full
remainder of the string is returned.
Code Listing 13: Query that demonstrates the INSTR and SUBSTR character functions
SQL> select last_name, INSTR(last_name, 'ton') ton_position,
SUBSTR(last_name,
INSTR(last_name, 'ton')) substring_ton
2 from employee
3 order by last_name;
LAST_NAME TON_POSITION SUBSTRING_TON
Eckhardt 0 Eckhardt
Friedli 0 Friedli
James 0 James
Michaels 0 Michaels
Newton 4 ton
Newton 4 ton
leblanc 0 leblanc
peterson 0 peterson
8 rows selected.
When Size Matters
Occasionally you need to determine a strings lengthfor example, to determine the maximum
number of characters a form entry field should permit. Listing 14 shows a query that uses the
LENGTH function to display the length of all FIRST_NAME values from the EMPLOYEE table.
Code Listing 14: Query that demonstrates the LENGTH function
SQL> select first_name, LENGTH(first_name) length
2 from employee
3 order by length desc, first_name;
FIRST_NAME LENGTH
Frances 7
Matthew 7
michael 7
Donald 6
Betsy 5
Emily 5
Roger 5
mark 4
8 rows selected.
Character functions can also be placed in WHERE and ORDER BY clauses, as illustrated in
Listings 15 and 16.
In Listing 15, the total length of those employee first and last names concatenated together,
separated by a single space, is calculated with the LENGTH function. And only values that are
more than 15 characters long are returned in the result set. In Listing 16, the WHERE clause uses
the INSTR function nested inside the SUBSTR function to return only those employees whose last
names contain the substring tonthe resultant employee first and last name values are
concatenated and separated with a space. Finally, the querys ORDER BY clause sorts the
concatenated first and last name values from the SELECT list by character length in descending
order, by using the LENGTH character function.
SQL 101
http://www.oracle.com/technetwork/issue-archive/2012/12-jul/o42sql-1603138.html[8/30/2014 12:37:18 PM]
Code Listing 15: Query that demonstrates a function in a WHERE clause
SQL> select first_name||' '||last_name employee_name
2 from employee
3 where LENGTH(first_name||' '||last_name) > 15
4 order by employee_name;
EMPLOYEE_NAME
Matthew Michaels
michael peterson
2 rows selected.
Code Listing 16: Query that demonstrates functions in a WHERE and an ORDER BY clause
SQL> select first_name||' '||last_name employee_name
2 from employee
3 where SUBSTR(last_name, INSTR(last_name, 'ton')) = 'ton'
4 order by LENGTH(employee_name) desc;
EMPLOYEE_NAME
Frances Newton
Donald Newton
2 rows selected.
Conclusion
This article has shown you how character functions can be used in SELECT statements to
manipulate the ways data is displayed. Youve seen how to convert data values to uppercase,
lowercase, and mixed cases and how to search for strings within strings. Youve also seen how to
pad and trim data and how to specify a strings total length. By no means does this article provide
an exhaustive list of the Oracle character functions. Review the documentation for more details:
bit.ly/HZUBC5.
The next installment of SQL 101 will discuss number functions and other miscellaneous functions.
Melanie Caffrey is a senior development manager at Oracle. She is a
coauthor of Expert PL/SQL Practices for Oracle Developers and DBAs
(Apress, 2011) and Expert Oracle Practices: Oracle Database
Administration from the Oak Table (Apress, 2010).
Send us your comments
SQL 101: From Floor to Ceiling and Other Functional Cases
http://www.oracle.com/technetwork/issue-archive/2012/12-sep/o52sql-1735910.html[8/30/2014 12:38:24 PM]
As Published In
September/October 2012
TECHNOLOGY: SQL 101
From Floor to Ceiling and Other
Functional Cases
By Melanie Caffrey
Part 7 in a series on the basics of the relational database and SQL
Part 6 in this series, A Function of Character (Oracle Magazine, July/August 2012), introduced
SQL character functions (also known as string functions or text functions) and showed how your
queries can use them to modify the appearance of character result set data. Similarly, you can
use SQL number functions to manipulate numerical data so that it displays differently from how it
is stored in the database. This article introduces you to some of the more commonly used SQL
number functions, along with some useful miscellaneous other functions.
To try out the examples in this and subsequent articles in the series, you need access to an
Oracle Database instance. If necessary, download and install an Oracle Database edition for your
operating system. I recommend installing Oracle Database, Express Edition 11g Release 2.
If you install the Oracle Database software, choose the installation option that enables you to
create and configure a database. A new database, including sample user accounts and their
associated schemas, will be created for you. (Note that SQL_101 is the user account youll use for
the examples in this series; its also the schema in which youll create database tables and other
objects.) When the installation process prompts you to specify schema passwords, enter and
confirm passwords for SYS and SYSTEM and make a note of them.
Finallywhether you installed the database software from scratch or have access to an existing
Oracle Database instancedownload, unzip, and execute the SQL script to create the tables for
the SQL_101 schema that are required for this articles examples. (View the script in a text editor
for execution instructions.) Some of the examples also use the DUAL table. Recall that DUAL is
an Oracle system table owned by the SYS user, not the SQL_101 schema. DUAL contains no
meaningful data itself, but it is useful to query it as a way to experiment with functions that work on
literals.
A Nice Round Number
One frequently used number function, ROUND, enables you to round a numeric value that is
returned in a result set. For example, the simple query in Listing 1 uses this function to apply
conventional rounding to two numbers. One number is rounded down, and the other is rounded
up.
Code Listing 1: Using ROUND function to round one number up and another number down
SQL> set feedback on
SQL> select ROUND(7534.1238, 2), ROUND(99672.8591, 2)
2 from dual;
ROUND(7534.1238,2) ROUND(99672.8591,2)
7534.12 99672.86
1 row selected.
Number functions require input parameters that are numericeither a column with a NUMBER
datatype or a numeric literal. ROUND takes two parameters, one required and one optional. The
required parameter is the numeric value to be rounded. The optional parameter is an integer that
indicates the rounding precisionthat is, how many places to the right (indicated by a positive
integer) or left (indicated by a negative integer) of the decimal point the numeric value should be
rounded to. The query in Listing 1 applies the ROUND number function to two numeric literal
values. Both numbers are rounded to two digits to the right of the decimal point.
If you omit the second parameter, the ROUND function will round the numeric value to the nearest
whole number, as shown in Listing 2. The query in Listing 3 demonstrates that if you pass the
optional parameter a negative integer, the ROUND function will round the numeric value on the left
side of the decimal point.
Code Listing 2: Using ROUND function to round numeric values to whole numbers
SQL> select ROUND(7534.1238), ROUND(99672.8591)
2 from dual;
ROUND(7534.1238) ROUND(99672.8591)
7534 99673
1 row selected.
Code Listing 3: Using ROUND function to round numeric values to the left of the decimal point
SQL> select ROUND(7534.1238, -1), ROUND(99672.8591, -3)
2 from dual;
ROUND(7534.1238,-1) ROUND(99672.8591,-3)
7530 100000
SQL 101: From Floor to Ceiling and Other Functional Cases
http://www.oracle.com/technetwork/issue-archive/2012/12-sep/o52sql-1735910.html[8/30/2014 12:38:24 PM]
1 row selected.
Cutting Your Data Off
The TRUNC function returns a numeric value truncated to a certain number of decimal places.
Like the ROUND function, it takes one required parameter and one optional parameter. The
required parameter is the number to be truncated. The optional parameter is a positive or a
negative integer. A positive integer specifies how many decimal places to truncate to. Listing 4
shows how the TRUNC function behaves when it is passed a positive value for the optional
parameter. Note that the query simply truncates the returned values, leaving off any digits beyond
two digits to the right of the decimal point.
Code Listing 4: Using TRUNC function to cut off digits to the right of the decimal point
SQL> select TRUNC(7534.1238, 2), TRUNC(99672.8591, 2)
2 from dual;
TRUNC(7534.1238,2) TRUNC(99672.8591,2)
7534.12 99672.85
1 row selected.
If you omit the optional parameter, the returned value will be truncated to zero decimal places, as
shown in Listing 5. When you use a negative integer for the optional parameter, as shown in
Listing 6, you are specifying how many digits to the left of the decimal point should be changed to
0 in the displayed results.
Code Listing 5: Using TRUNC function to truncate numeric values to whole numbers
SQL> select TRUNC(7534.1238), TRUNC(99672.8591)
2 from dual;
TRUNC(7534.1238) TRUNC(99672.8591)
7534 99672
1 row selected.
Code Listing 6: Using TRUNC function to truncate numeric values to the left of the decimal point
SQL> select TRUNC(7534.1238, -1), TRUNC(99672.8591, -3)
2 from dual;
TRUNC(7534.1238,-1) TRUNC(99672.8591,-3)
7530 99000
1 row selected.
Code Listing 5: Using TRUNC function to truncate numeric values to whole numbers
SQL> select TRUNC(7534.1238), TRUNC(99672.8591)
2 from dual;
TRUNC(7534.1238) TRUNC(99672.8591)
7534 99672
1 row selected.
Top to Bottom
Similar to ROUND and TRUNC are the FLOOR and CEIL number functions. The FLOOR function
determines the largest integer less than (or equal to) a particular numeric value. Conversely, the
CEIL function determines the smallest integer greater than (or equal to) a particular numeric value.
FLOOR and CEIL (unlike ROUND and TRUNC) do not take an optional parameter for precision,
because their output is always an integer. When all four of these functions are applied to a
positive number, as illustrated in Listing 7, FLOOR behaves similarly to TRUNC with no optional
parameter specified, and CEIL behaves similarly to ROUND with no optional parameter specified.
Note, however, that FLOOR behaves like ROUND and CEIL behaves like TRUNC when these
functions are applied to a negative number.
Code Listing 7: Using and comparing ROUND, CEIL, TRUNC, and FLOOR functions
SQL> select ROUND(99672.8591), CEIL(99672.8591), TRUNC(99672.8591), FLOOR(99672.8591)
2 from dual;
ROUND(99672.8591) CEIL(99672.8591) TRUNC(99672.8591) FLOOR(99672.8591)
99673 99673 99672 99672
1 row selected.
Arithmetic and Its Remains
The four arithmetic operators (+, , *, and /for addition, subtraction, multiplication, and division)
can be used in SQL statements and combined with one another and any of the number functions.
SQL 101: From Floor to Ceiling and Other Functional Cases
http://www.oracle.com/technetwork/issue-archive/2012/12-sep/o52sql-1735910.html[8/30/2014 12:38:24 PM]
Listing 8 shows a query from the EMPLOYEE table that reports each employees annual base
salary, a calculated 3 percent bonus per salary value, and the weekly salary value (including
bonus) for each employee.
Code Listing 8: Arithmetic operators in combination with the ROUND number function
SQL> select first_name, last_name, salary, salary*.03 "BONUS",
ROUND((salary/52)+((salary*.03)/52)) "Weekly Sal w/Bonus"
2 from employee
3 order by salary desc, last_name;
FIRST_NAME LAST_NAME SALARY BONUS Weekly Sal w/Bonus
Emily Eckhardt 100000 3000 1981
michael peterson 90000 2700 1783
Donald Newton 80000 2400 1585
Frances Newton 75000 2250 1486
Matthew Michaels 70000 2100 1387
mark leblanc 65000 1950 1288
Roger Friedli 60000 1800 1188
Betsy James 60000 1800 1188
8 rows selected.
Two number functions, MOD and REMAINDER, can both be used to calculate the remainder of a
value divided by another value. Both functions require two parameters: the value to be divided and
the divisor. The MOD function uses the FLOOR function in its computation logic, and the
REMAINDER function uses ROUND. For this reason, the values returned from the two functions
can differ, as shown in Listing 9.
Code Listing 9: Differences between the MOD and REMAINDER function results
SQL> select MOD(49, 18) modulus, REMAINDER(49, 18) remaining
2 from dual;
MODULUS REMAINING
13 -5
1 row selected.
Replacing the Unknown with the Known
Recall that a null value in a table is the absence of a value. Null values cannot be compared with,
or computed with, one another. However, you can substitute a non-null value for a NULL value by
applying the NVL miscellaneous function to the NULL. The NVL function requires two input
parameters: the expression (a column value, literal value, or computed result) to be tested for
nullity and the expression to substitute for any NULL expressions in the results.
For example, Listing 10 shows a query that lists each employee alongside the EMPLOYEE ID
value of that persons manager. For the employees with no value for MANAGERthat is, those
whose MANAGER values are NULL in the databasethe results display 0 as the managers
EMPLOYEE ID. This occurs because the query applies the NVL function to each MANAGER
value, substituting 0 for any NULL.
Code Listing 10: Substitute a NULL value for MANAGER with a value of 0
SQL> select employee_id, last_name, first_name, NVL(manager, 0) manager
2 from employee
3 order by manager, last_name, first_name;
EMPLOYEE_ID LAST_NAME FIRST_NAME MANAGER
28 Eckhardt Emily 0
37 Newton Frances 0
6569 peterson michael 0
6567 Friedli Roger 28
6568 James Betsy 28
7895 Michaels Matthew 28
1234 Newton Donald 28
6570 leblanc mark 6569
8 rows selected.
As you can also see in Listing 10, the original value of the tested expression is returned if it is not
NULL.
In the Listing 10 example, a returned value of 0 might not tell you as clearly as youd like that
certain employee records have no assigned manager value. Instead, you might prefer to return
text that states this fact explicitly.
Listing 11 shows a query that tries to replace each NULL value with a more descriptive text literal.
The query returns an error, however, because the NVL function requires the substitution value to
be convertible to the datatype of the comparison value. However, you can obtain the textual
output in a few ways. Listing 12 demonstrates one of them: the inclusion of a datatype conversion
function, TO_CHAR. Datatype conversion functions will be discussed in detail in subsequent
articles in this series.
Code Listing 11: Attempt to replace a returned NULL value with a text value
SQL> select employee_id, last_name, first_name, NVL(manager,
'Has no manager') manager
2 from employee
3 order by manager, last_name, first_name;
select employee_id, last_name, first_name, NVL(manager, 'Has no manager') manager
*
SQL 101: From Floor to Ceiling and Other Functional Cases
http://www.oracle.com/technetwork/issue-archive/2012/12-sep/o52sql-1735910.html[8/30/2014 12:38:24 PM]
Next Steps
READ SQL 101, Parts 16
READ more about relational database
design and concepts
Oracle Database Concepts 11g
Release 1 (11.2)
Oracle Database SQL Language
Reference 11g Release 1 (11.1)
Oracle SQL Developer Users Guide
Release 3.1
number and substitution functions
bit.ly/MgvEzi
bit.ly/LN8F0d
DOWNLOAD the script for this article
ERROR at line 1:
ORA-01722: invalid number
Code Listing 12: Replace a returned NULL value with a text value by using TO_CHAR
SQL> select employee_id, last_name, first_name, NVL(TO_CHAR(manager),
'Has no manager') manager
2 from employee
3 order by manager, last_name, first_name;
EMPLOYEE_ID LAST_NAME FIRST_NAME MANAGER
6567 Friedli Roger 28
6568 James Betsy 28
7895 Michaels Matthew 28
1234 Newton Donald 28
6570 leblanc mark 6569
28 Eckhardt Emily Has no manager
37 Newton Frances Has no manager
6569 peterson michael Has no manager
8 rows selected.
Adding More Detail with DECODE
Sometimes a simple substitution function such as NVL doesnt provide all the choices you require.
The DECODE function uses if-then-else logic to give you more than one possible substitution
choice.
The syntax for the DECODE function starts with an input expression. It compares that expression
with a comparison value. If the two values match (this is the if-then portion of the DECODE
logic), the DECODE function returns the substitution value. If the two values do not match, the
input expression will be compared with the next available comparison value. If another comparison
value is not provided, the optional default substitution value (the else portion of the DECODE
logic) will be returned. Listing 13 demonstrates the syntax for the DECODE function. It also
demonstrates how DECODE functions can be nested inside one another.
Code Listing 13: DECODE substitution function
SQL> select employee_id, first_name, last_name, DECODE(manager, 28,
'Emily Eckhardt', 6569, 'Michael Peterson', DECODE(employee_id, 28,
'Is Emily', 6569, 'Is Michael', 'Neither Emily nor Michael')) manager
2 from employee
3 order by manager, last_name, first_name;
EMPLOYEE_ID LAST_NAME FIRST_NAME MANAGER
6567 Roger Friedli Emily Eckhardt
6568 Betsy James Emily Eckhardt
7895 Matthew Michaels Emily Eckhardt
1234 Donald Newton Emily Eckhardt
28 Emily Eckhardt Is Emily
6569 michael peterson Is Michael
6570 mark leblanc Michael Peterson
37 Frances Newton Neither Emily nor Michael
8 rows selected.
The query in Listing 13, like the one in Listing 12, substitutes a textual value for the actual
MANAGER value for each employee record. Note, though, that with DECODE, you can repeat the
test and substitute valuesthat is, repeat the if-then logicas much as you require. Another
difference from NVL is that DECODE can test for conditions other than nullity; for example, the
query in Listing 13 tests whether a particular value exists.
If you do want to test for nullity with DECODE, you can write a query such as
SELECT DECODE(manager, NULL, 'Has no Manager', manager) FROM employee;
In this example, if the value obtained from the
MANAGER column is NULL, the Has no manager
string will be returned. Otherwise, the non-null
manager value will be returned. You might be
wondering why this statement does not return an
error, given that the MANAGER value is of a
different datatype than the string that would be
returned if the MANAGER value were NULL. The
reason is implicit datatype conversion. Oracle
Database implicitly converts a number to a string in
situations like this example. (It does notand
cannotconvert a string to a number.) However, it is
not good practice to allow Oracle Database to
perform implicit datatype conversions. If you need a
datatype conversion, you should always perform a
call to a datatype conversion function explicitly.
A Case for Comparative Searches
Although the DECODE function is more powerful
than the NVL function, it cannot be (easily) used for
comparisons other than equality (and inequality.) A
searched CASE expression can not only be used in
place of the DECODE function but can also be used more easily for greater-than or less-than
comparisons.
SQL 101: From Floor to Ceiling and Other Functional Cases
http://www.oracle.com/technetwork/issue-archive/2012/12-sep/o52sql-1735910.html[8/30/2014 12:38:24 PM]
Listing 14 shows a query that uses a searched CASE expression. As you can see, the searched
CASE expression starts with the CASE keyword and ends with the END keyword. Each WHEN
clause tests a condition. If a condition is true, the CASE expression will return the value specified
in the associated THEN clause. Like the DECODE functions ELSE condition, the default ELSE
condition in the searched CASE expression is optional. CASE expressions can be used in
WHERE clauses, as shown in Listing 15. They can even be nested, as shown in Listing 16.
Code Listing 14: Searched CASE expression in a less-than comparison
SQL> select employee_id, first_name, last_name, salary,
2 CASE WHEN manager = 28 THEN 'Emily is the manager. No bonus this year.'
3 WHEN salary < 80000 THEN 'Bonus this year.'
4 ELSE 'No bonus this year.'
5 END "Bonus?"
6 from employee
7 order by last_name, first_name;
EMPLOYEE_ID FIRST_NAME LAST_NAME SALARY Bonus?
28 Emily Eckhardt 100000 No bonus this year.
6567 Roger Friedli 60000 Emily is the manager. No bonus this
year.
6568 Betsy James 60000 Emily is the manager. No bonus this
year.
7895 Matthew Michaels 70000 Emily is the manager. No bonus this
year.
1234 Donald Newton 80000 Emily is the manager. No bonus this
year.
37 Frances Newton 75000 Bonus this year.
6570 mark leblanc 65000 Bonus this year.
6569 michael peterson 90000 No bonus this year.
8 rows selected.
Code Listing 15: Searched CASE expression in a WHERE clause
SQL> select employee_id, first_name, last_name, salary
2 from employee
3 where salary + CASE
4 WHEN ROUND((salary/52)+((salary*.03)/52)) > 1500
5 THEN 0
6 WHEN ROUND((salary/52)+((salary*.03)/52)) < 1300
7 THEN 500
8 ELSE 200
9 END > 75000
10 order by last_name, first_name;
EMPLOYEE_ID FIRST_NAME LAST_NAME SALARY
28 Emily Eckhardt 100000
1234 Donald Newton 80000
37 Frances Newton 75000
6569 michael peterson 90000
4 rows selected.
Code Listing 16: Nested searched CASE expressions
SQL> select employee_id, first_name, last_name,
2 CASE manager WHEN 28 THEN 'Emily Eckhardt'
3 WHEN 6569 THEN 'Michael Peterson'
4 ELSE
5 CASE employee_id WHEN 28 THEN 'Is Emily'
6 WHEN 6569 THEN 'Is Michael'
7 ELSE 'Neither Emily nor Michael'
8 END
9 END manager
10 from employee
11 order by manager, last_name, first_name;
EMPLOYEE_ID LAST_NAME FIRST_NAME MANAGER
6567 Roger Friedli Emily Eckhardt
6568 Betsy James Emily Eckhardt
7895 Matthew Michaels Emily Eckhardt
1234 Donald Newton Emily Eckhardt
28 Emily Eckhardt Is Emily
6569 michael peterson Is Michael
6570 mark leblanc Michael Peterson
37 Frances Newton Neither Emily nor Michael
8 rows selected.
Conclusion
This article has shown you a few of the more common number functions and how you can use
them to manipulate the way your data displays. Youve seen how to round numeric data values up
and down and how to truncate them. You now know how the ROUND and TRUNC number
functions behave, in comparison to FLOOR and CEIL. Youve also seen that the MOD and
REMAINDER number functions can return different values because of the type of computation
each one uses. Last but not least, you understand the power and differences of substitution
functions such as NVL, DECODE, and searched CASE expressions.
This article has by no means provided an exhaustive list of the Oracle number and miscellaneous
substitution functions. Review the documentation at bit.ly/MgvEzi and bit.ly/LN8F0d for more
information.
The next installment of SQL 101 will discuss date and datatype conversion functions.
Melanie Caffrey is a senior development manager at Oracle. She is a
coauthor of Expert PL/SQL Practices for Oracle Developers and DBAs
SQL 101: From Floor to Ceiling and Other Functional Cases
http://www.oracle.com/technetwork/issue-archive/2012/12-sep/o52sql-1735910.html[8/30/2014 12:38:24 PM]
(Apress, 2011) and Expert Oracle Practices: Oracle Database
Administration from the Oak Table (Apress, 2010).
Send us your comments
SQL 101: Selecting a Type That Is Right for You
http://www.oracle.com/technetwork/issue-archive/2012/12-nov/o62sql-1867727.html[8/30/2014 12:40:05 PM]
As Published In
November/December 2012
TECHNOLOGY: SQL 101
Selecting a Type That Is Right for
You
By Melanie Caffrey
Part 8 in a series on the basics of the relational database and SQL
Part 7 in this series, From Floor to Ceiling and Other Functional Cases (Oracle Magazine,
September/October 2012), introduced common SQL number functions and showed how your
queries can use them to modify the appearance of numeric result set data. It also introduced SQL
substitution functions and showed how you can use them to manipulate result set data to convey
more-meaningful results. Similarly, you can use SQL date functions and datatype conversion
functions to manipulate data so that it displays differently from how it is stored in the database.
This article introduces you to some of the more commonly used SQL date functions, along with
some useful datatype conversion functions.
To try out the examples in this series, you need access to an Oracle Database instance. If
necessary, download and install an Oracle Database edition for your operating system. I
recommend installing Oracle Database, Express Edition 11g Release 2.
If you install the Oracle Database software, choose the installation option that enables you to
create and configure a database. A new database, including sample user accounts and their
associated schemas, will be created for you. (Note that SQL_101 is the user account to use for
the examples in this series; its also the schema in which youll create database tables and other
objects.) When the installation process prompts you to specify schema passwords, enter and
confirm passwords for SYS and SYSTEM and make a note of them.
Finallywhether you installed the database software from scratch or have access to an existing
Oracle Database instancedownload, unzip, and execute the SQL script to create the tables for
the SQL_101 schema that is required for this articles examples. (View the script in a text editor for
execution instructions.) Some of the examples also use the DUAL table. Recall that DUAL is an
Oracle system table owned by the SYS user, not the SQL_101 schema. DUAL contains no
meaningful data itself, but it is useful to query it as a way to experiment with functions that work on
literals.
The Perfect Format for Your Date
The DATE datatype is stored in Oracle Database in an internal format that consists of both date
and time information: the century, year, month, day, hour, minute, and second. For input and
output of dates, every Oracle Database instance has a default date format model (also called a
mask) that is set by the NLS_DATE_FORMAT initialization parameter. (Initialization parameters
determine the default settings for Oracle Database instances. Users who have appropriate
permissions can change some of these parameters on a per-database, per-instance, or per-
session basis.) When you first query the data stored in a table column with a DATE datatype,
Oracle Database displays it with a format mask of either DD-MON-YYYY or DD-MON-RR,
depending on which is set as the default.
The RR format mask, which represents a two-digit year, was introduced to deal with end-of-
century issues such as the Y2K problem. With RR, a two-digit year can refer to a year in the
previous, current, or next centurydepending on the current year and the two-digit year specified
in the query. Table 1 shows the relationship between the current year, the range of two-digit year
combinations, and the corresponding century referred to as a result.
Last Two Digits of Current
Year
Two-Digit Year Specified in
Query
Century Referred
To
Between 00 and 49 Between 00 and 49 Current
Between 00 and 49 Between 50 and 99 Previous
Between 50 and 99 Between 00 and 49 Next
Between 50 and 99 Between 50 and 99 Current
Table 1: Relationship among current year, two-digit year specified, and the century referred to as
a result
For example, the last two digits of the current year (2012) are 12, which falls between 00 and 49.
A SQL query issued during 2012 that specifies an RR year value of 15, therefore, refers to the
year ending in 15 (2015) in the current century (the twenty-first), because 15 is between 0 and 49.
A query issued in 2012 that specifies an RR year value of 98 refers to the year ending in 98
(1998) in the previous century (the twentieth), because 98 is between 50 and 99.
The query in Listing 1 uses the EMPLOYEE table in the sample schema for this article. The query
displays employees sorted from most recent to least recent date of hire. As you can see, the hire
date data is displayed in DD-MON-RR format. For example, it shows that Roger Friedli was hired
on 16-MAY-07. To change the way this data is displayed, you use the TO_CHAR conversion
function in conjunction with a format model of your choosing. (You had a brief introduction to
SQL 101: Selecting a Type That Is Right for You
http://www.oracle.com/technetwork/issue-archive/2012/12-nov/o62sql-1867727.html[8/30/2014 12:40:05 PM]
TO_CHAR in the last installment, where you saw that it can be used to convert a number to a text
string.)
Code Listing 1: Display date data in the Oracle Database default date format
SQL> set feedback on
SQL> select first_name, last_name, hire_date
2 from employee
3 order by hire_date desc, last_name, first_name;
FIRST_NAME LAST_NAME HIRE_DATE
Theresa Wong 27-FEB-10
Thomas Jeffrey 27-FEB-10
mark leblanc 06-MAR-09
michael peterson 03-NOV-08
Roger Friedli 16-MAY-07
Betsy James 16-MAY-07
Matthew Michaels 16-MAY-07
Donald Newton 24-SEP-06
Frances Newton 14-SEP-05
Emily Eckhardt 07-JUL-04
10 rows selected.
The query in Listing 2 modifies the way the date data from Listing 1 is displayed. To convert data
of DATE datatype to a specific date format model, TO_CHAR takes one required parameter and
one optional parameter. The required parameter is data of DATE datatype from a column,
expression, or literal. The optional parameter is a textual format-mask representation of the date to
be displayed. In Listing 2, the default format mask of DD-MON-RR is changed to display as
YYYY-MM-DD.
Code Listing 2: Display date data in a different format by using TO_CHAR with a format mask
SQL> select first_name, last_name, TO_CHAR(hire_date, 'YYYY-MM-DD')
hire_date
2 from employee
3 order by hire_date desc, last_name, first_name;
FIRST_NAME LAST_NAME HIRE_DATE
Thomas Jeffrey 2010-02-27
Theresa Wong 2010-02-27
mark leblanc 2009-03-06
michael peterson 2008-11-03
Roger Friedli 2007-05-16
Betsy James 2007-05-16
Matthew Michaels 2007-05-16
Donald Newton 2006-09-24
Frances Newton 2005-09-14
Emily Eckhardt 2004-07-07
10 rows selected.
Listing 3 demonstrates that the second parameter for TO_CHAR is optional. If it is left off, the
format mask of the date data returned will simply be the default format mask. Note also that the
datatype of the date returned is VARCHAR2. The output from Listing 3 is sorted by HIRE_DATE in
descending order, but in character, not date, descending order. So, be aware that when you apply
the TO_CHAR conversion function, your data is returned as character strings; you should plan and
sort accordingly.
Code Listing 3: Default date format mask is used when optional parameter is not provided
SQL> select first_name, last_name, TO_CHAR(hire_date)
hire_date_formatted
2 from employee
3 order by hire_date_formatted desc, last_name, first_name;
FIRST_NAME LAST_NAME HIRE_DATE
Thomas Jeffrey 27-FEB-10
Theresa Wong 27-FEB-10
Donald Newton 24-SEP-06
Roger Friedli 16-MAY-07
Betsy James 16-MAY-07
Matthew Michaels 16-MAY-07
Frances Newton 14-SEP-05
Emily Eckhardt 07-JUL-04
mark leblanc 06-MAR-09
michael peterson 03-NOV-08
10 rows selected.
Dates with Strings Attached
Just as you can convert a date to a string, you can convert a string literal to a date. The resulting
expression can be compared with any other columns data of DATE datatype or another date
expression. You perform the conversion by applying the TO_DATE conversion function to a text
string, as shown in Listing 4. The query in Listing 4 not only returns all employees whose
HIRE_DATE value is found to be greater than the date value 01-JAN-2008; it also demonstrates
that the TO_DATE conversion function can be used in WHERE clauses as well as SELECT lists.
The TO_DATE function is applied to the string literal 01-JAN-2008, with a format mask that helps
the database interpret the supplied literal as a date.
Code Listing 4: Use the TO_DATE conversion function in a WHERE clause
SQL> select first_name, last_name, TO_CHAR(hire_date, 'DD-MON-YYYY')
hire_date
2 from employee
3 where hire_date > TO_DATE('01-JAN-2008', 'DD-MON-YYYY')
4 order by hire_date desc, last_name, first_name;
FIRST_NAME LAST_NAME HIRE_DATE
Thomas Jeffrey 27-FEB-2010
Theresa Wong 27-FEB-2010
mark leblanc 06-MAR-2009
michael peterson 03-NOV-2008
4 rows selected.
SQL 101: Selecting a Type That Is Right for You
http://www.oracle.com/technetwork/issue-archive/2012/12-nov/o62sql-1867727.html[8/30/2014 12:40:05 PM]
When you provide a format mask to the TO_DATE function, the mask you choose must be the
same as the one used in the string literal you supply. If the two do not agree, you will receive an
error message similar to the one shown in Listing 5. When you convert a text literal, it is good
practice to use the TO_DATE conversion function and explicitly specify an appropriate format
mask. This way, your statement can be interpreted independently of any database, instance, or
session default date settings.
Code Listing 5: Error when the format mask does not match the provided string literal
SQL> select first_name, last_name, TO_CHAR(hire_date, 'DD-MON-YYYY')
hire_date
2 from employee
3 where hire_date > TO_DATE('01-JAN-2008', 'MM/DD/RR')
4 order by hire_date desc, last_name, first_name;
where hire_date > TO_DATE('01-JAN-2008', 'MM/DD/RR')
*
ERROR at line 3:
ORA-01858: a non-numeric character was found where a numeric was
expected
Oracle Database will perform implicit date conversion where it can, if (and only if) the literal is
already in the default date format. However, I do not recommend that you allow it to do so,
because your code will be more fragile and less likely to perform well long-term. Listing 6 shows a
query that relies on the default date format in Oracle Database and its ability to perform implicit
date conversion on a string literal. Compare the result in Listing 6 with that in Listing 7, which also
attempts to perform an implicit date conversion. The query in Listing 7 fails because the database
cannot interpret the date format mask of the literal value being compared with the values in the
HIRE_DATE column of the EMPLOYEE table.
Code Listing 6: Implicit date conversion (not recommended) returns a result set
SQL> select first_name, last_name, TO_CHAR(hire_date, 'DD-MON-YYYY')
hire_date
2 from employee
3 where hire_date > '01-JAN-2008'
4 order by hire_date desc, last_name, first_name;
FIRST_NAME LAST_NAME HIRE_DATE
Thomas Jeffrey 27-FEB-2010
Theresa Wong 27-FEB-2010
mark leblanc 06-MAR-2009
michael peterson 03-NOV-2008
4 rows selected.
Code Listing 7: Attempted implicit date conversion fails
SQL> select first_name, last_name, TO_CHAR(hire_date, 'DD-MON-YYYY')
hire_date
2 from employee
3 where hire_date > '01/01/2008'
4 order by hire_date desc, last_name, first_name;
where hire_date > '01/01/2008'
*
ERROR at line 3:
ORA-01843: not a valid month
Because the default date format can be changed, it is best not to allow your queries to rely on an
expected default format. Instead, always use the TO_DATE function on date string literals. One
way to find out which default date format your current session is using is to execute the query
shown in Listing 8. The SYS_CONTEXT function can be used by any session (and, therefore, any
user) to see current session attributes.
Code Listing 8: Find the default date format for your current session
SQL> select sys_context ('USERENV', 'NLS_DATE_FORMAT')
2 from dual;
SYS_CONTEXT('USERENV','NLS_DATE_FORMAT')
DD-MON-RR
1 row selected.
Taking Time with Your Dates
Recall that the Oracle DATE datatype includes a time component. You can either ignore the time
component, as the examples in this article have done so far, or you can include it for display or
comparison purposes. Listing 9 shows a query that includes the time component from each
HIRE_DATE value for every employee listed in the EMPLOYEE table. Note that all the employee
records except the one for Theresa Wong show a time value of 12:00:00. If you do not include a
time when inserting a value into a column with a DATE datatype, the time will default to midnight
(12:00:00 a.m. or 00:00:00 military time). To display or compare a date value in military time, use
the HH24 format mask instead of HH.
Code Listing 9: Display the time component of a value with a DATE datatype
SQL> set lines 32000
SQL> select first_name, last_name, TO_CHAR(hire_date, 'DD-MON-YYYY HH:MI:SS')
hire_date
2 from employee
3 order by hire_date desc, last_name, first_name;
FIRST_NAME LAST_NAME HIRE_DATE
Thomas Jeffrey 27-FEB-2010 12:00:00
Theresa Wong 27-FEB-2010 09:02:45
Donald Newton 24-SEP-2006 12:00:00
Roger Friedli 16-MAY-2007 12:00:00
Betsy James 16-MAY-2007 12:00:00
Matthew Michaels 16-MAY-2007 12:00:00
Frances Newton 14-SEP-2005 12:00:00
SQL 101: Selecting a Type That Is Right for You
http://www.oracle.com/technetwork/issue-archive/2012/12-nov/o62sql-1867727.html[8/30/2014 12:40:05 PM]
Next Steps
READ SQL 101, Parts 17
LEARN more about date and datatype
conversion functions
bit.ly/PR7GQh
bit.ly/NOgf01
READ more about
relational database design and concepts
Oracle Database Concepts 11g Release 2 (11.2
Oracle Database SQL Language Reference 11g
Release 1 (11.1)
Oracle SQL Developer Users Guide Release
3.1
Oracle Database development essentials
Oracle Database 2 Day Developers Guide 11g
Release 2 (11.2)
DOWNLOAD the sample script for this article
Emily Eckhardt 07-JUL-2004 12:00:00
mark leblanc 06-MAR-2009 12:00:00
michael peterson 03-NOV-2008 12:00:00
10 rows selected.
Unless you know the exact time of the date values on which youd like to filteror unless all the
time portions for your date values are already set to midnightusing date values in your WHERE
clauses can produce unexpected results. Consider the query in Listing 10. You know from the
results in the previous listings that two employees were hired on February 27, 2010, yet only one
is returned in Listing 10s result set. The reason is that the TO_DATE function in the WHERE
clause does not specify an exact time, so Oracle Database assumes that the time is midnight and
returns only those records that contain the specified date value and midnight as the time
component.
Code Listing 10: WHERE clause using TO_DATE might not capture all possible values
SQL> select first_name, last_name, TO_CHAR(hire_date, 'DD-MON-YYYY HH:MI:SS')
hire_date
2 from employee
3 where hire_date = TO_DATE('27-FEB-2010', 'DD-MON-YYYY')
4 order by last_name, first_name;
FIRST_NAME LAST_NAME HIRE_DATE
Thomas Jeffrey 27-FEB-2010 12:00:00
1 row selected.
Cutting Your Date Short
When you would like to be able to filter on
a certain date but do not want to have to
include each individual time component,
you can use a couple of different
methods. One method is to include the
TRUNC function (introduced in the
previous installment in this series). It, like
the TO_CHAR function, works not only on
numbers but also on date values. The
TRUNC function helps cut off the time
portion of a date if no optional format
parameter is passed to it. This can be
useful for date comparison purposes.
Listing 11 shows a revised version of the
query from Listing 10. As you can see,
eliminating the time portion of the values
in the HIRE_DATE column enables the
comparison against the date value 27-
FEB-2010 to retrieve all records with a
HIRE_DATE value of 27-FEB-2010,
irrespective of the time. The truncated
HIRE_DATE value is made into a date
only value to be compared with the
corresponding date only value returned
from the result of applying the TO_DATE function on the literal string 27-FEB-2010 with a date-
only format.
Code Listing 11: Truncate the time from a DATE value to return all records for a particular day
SQL> select first_name, last_name, TO_CHAR(hire_date, 'DD-MON-YYYY HH:MI:SS')
hire_date
2 from employee
3 where TRUNC(hire_date) = TO_DATE('27-FEB-2010', 'DD-MON-YYYY')
4 order by last_name, first_name;
FIRST_NAME LAST_NAME HIRE_DATE
Thomas Jeffrey 27-FEB-2010 12:00:00
Theresa Wong 27-FEB-2010 09:02:45
2 rows selected.
Be aware, however, that you might sacrifice performance by applying a function to your table
column values in a WHERE clause. Indexes (used to assist with data access efficiencyand not
discussed in this series) can improve query performance in certain situations. Applying a function
to a table column has the effect of ensuring that an index on the column might never be used.
Also, this function would be applied to every value in that column for every row. Both actions are
extreme performance inhibitors. Therefore, another method you can use is to specify a date range
outside of the date(s) you would actually prefer to filter on. The query in Listing 12 retrieves the
same result set as the query in Listing 11. The difference between the two is that the query in
Listing 12 does not apply a function to the HIRE_DATE column data. Instead, it chooses a range
just outside of the desired date(s) and encloses the filtered date data inside this range of values.
Code Listing 12: Date range that returns records for a particular day
SQL> select first_name, last_name, TO_CHAR(hire_date, 'DD-MON-YYYY HH:MI:SS')
hire_date
2 from employee
3 where hire_date >= TO_DATE('27-FEB-2010', 'DD-MON-YYYY')
4 and hire_date < TO_DATE('28-FEB-2010', 'DD-MON-YYYY')
5 order by last_name, first_name;
FIRST_NAME LAST_NAME HIRE_DATE
Thomas Jeffrey 27-FEB-2010 12:00:00
Theresa Wong 27-FEB-2010 09:02:45
SQL 101: Selecting a Type That Is Right for You
http://www.oracle.com/technetwork/issue-archive/2012/12-nov/o62sql-1867727.html[8/30/2014 12:40:05 PM]
2 rows selected.
A System for Getting Your Dates Right
You will often need to perform date arithmetic. A useful built-in function (one already built into
Oracle Database) is SYSDATE. This function returns the current date and time that are set on the
operating system of the computer on which the database resides. It takes no parameters. Listing
13 shows an example of using the SYSDATE function to return and display the current date and
time.
Code Listing 13: The SYSDATE function
SQL> select SYSDATE, TO_CHAR(SYSDATE, 'DD-MON-YYYY HH24:MI:SS') sysdate_with_time
2 from dual;
SYSDATE SYSDATE_WITH_TIME
08-AUG-12 08-AUG-2012 14:25:08
1 row selected.
SYSDATE can be extremely useful in date arithmetic. Listing 14 shows how many days are left in
2012 from the current date (August 8, 2012, in the example). Note that if the SYSDATE value
were not truncated, the returned DAYS_TILL_2013 value would include some fraction of the
SYSDATE value (to account for the time component). Because it is truncated, however, the entire
current date is subtracted from January 1, 2013, to arrive at the result of 146 days left in the year.
Listing 15 uses SYSDATE and date arithmetic (using a date function called
MONTHS_BETWEEN) against the HIRE_DATE column of the EMPLOYEE table, to show the
number of years of service for each employee.
Code Listing 14: SYSDATE used in date arithmetic
SQL> select SYSDATE, (TO_DATE('01-JAN-2013', 'DD-MON-YYYY') -
TRUNC(SYSDATE))
Days_till_2013
2 from dual;
SYSDATE DAYS_TILL_2013
08-AUG-12 146
1 row selected.
Code Listing 15: SYSDATE and date arithmetic combined with DATE data
SQL> select substr(last_name, 1, 10) last_name, substr(first_name, 1,
10)
first_name, hire_date, ROUND(MONTHS_BETWEEN(TRUNC(SYSDATE), TRUNC(HIRE_
DATE))/12, 2) YEARS_OF_SERVICE
2 from employee
3 order by years_of_service desc, last_name, first_name;
LAST_NAME FIRST_NAME HIRE_DATE YEARS_OF_SERVICE
Eckhardt Emily 07-JUL-04 8.09
Newton Frances 14-SEP-05 6.9
Newton Donald 24-SEP-06 5.88
Friedli Roger 16-MAY-07 5.23
James Betsy 16-MAY-07 5.23
Michaels Matthew 16-MAY-07 5.23
peterson michael 03-NOV-08 3.77
leblanc mark 06-MAR-09 3.42
Jeffrey Thomas 27-FEB-10 2.45
Wong Theresa 27-FEB-10 2.45
10 rows selected.
Another method for performing date arithmetic is to use the BETWEEN operator, as demonstrated
by the query in Listing 16. Be aware, however, that the BETWEEN operator uses the midnight (or
00:00:00) time component of the upper-range value in a date-range comparison. To include all
possible values for the date specified in the upper range of the date comparison, ensure that the
date includes the full time component of your upper range. In the example in Listing 16, an upper-
range date value of 27-FEB-2010 23:59:59 would have allowed both employee records with a
HIRE_DATE value of 27-FEB-2010 to be included in the result set.
Code Listing 16: BETWEEN operator uses midnight in a date range comparison
SQL> select last_name, first_name, hire_date
2 from employee
3 where hire_date BETWEEN TO_DATE('26-FEB-2010', 'DD-MON-YYYY')
4 AND TO_DATE('27-FEB-2010', 'DD-MON-YYYY');
FIRST_NAME LAST_NAME HIRE_DATE
Jeffrey Thomas 27-FEB-10
1 row selected.
Conclusion
This article has shown you a few of the most common date functions and how they can be used
to manipulate the way data is displayed. Youve seen how to use the TO_CHAR and TO_DATE
conversion functions and have learned the differences between them. You now know that dates
all contain a time component that can be used or truncated according to your needs. Youve been
introduced to the SYSDATE function and date arithmetic. Last but not least, you now know the
pitfalls to be aware of when you use DATE comparisons in WHERE clauses with TO_DATE and
BETWEENand what you can do to avoid unexpected results. By no means has this article
provided an exhaustive list of the Oracle Database date and datatype conversion functions. You
can review the documentation for more details at bit.ly/PR7GQh and bit.ly/NOgf01. The next
installment of SQL 101 will discuss aggregate functions.
SQL 101: Selecting a Type That Is Right for You
http://www.oracle.com/technetwork/issue-archive/2012/12-nov/o62sql-1867727.html[8/30/2014 12:40:05 PM]
Melanie Caffrey is a senior development manager at Oracle. She is a
coauthor of Expert PL/SQL Practices for Oracle Developers and DBAs
(Apress, 2011) and Expert Oracle Practices: Oracle Database
Administration from the Oak Table (Apress, 2010).
Send us your comments
SQL 101: Having Sums, Averages, and Other Grouped Data
http://www.oracle.com/technetwork/issue-archive/2013/13-jan/o13sql-1886636.html[8/30/2014 12:41:49 PM]
As Published In
January/February 2013
Next Steps
READ SQL 101, Parts 18
READ more about
relational database design and
concepts
Oracle Database Concepts 11g
Release 2 (11.2)
Oracle Database SQL Language
Reference 11g Release 1 (11.1)
Oracle SQL Developer Users Guide
Release 3.1
aggregate functions
DOWNLOAD the sample script for
this article
TECHNOLOGY: SQL 101
Having Sums, Averages, and Other
Grouped Data
By Melanie Caffrey
Part 9 in a series on the basics of the relational database and SQL
Part 8 in this series, Selecting a Type That Is Right for You (Oracle Magazine,
November/December 2012), introduced common SQL date functions and showed how your
queries can use them to modify the appearance of date result set data. It also introduced the
SYSDATE function and date arithmetic and showed how they can be used to manipulate result
set data to convey more-meaningful results. So far, all of the functions discussed in this series
operate on single-row results. Aggregate functions (also called group functions) operate on
multiple rows, enabling you to manipulate data so that it displays differently from how it is stored in
the database. This article introduces you to some of the more commonly used SQL group
functions, along with the GROUP BY and HAVING clauses.
To try out the examples in this series, you need
access to an Oracle Database instance. If
necessary, download and install an Oracle Database
edition for your operating system from. I recommend
installing Oracle Database, Express Edition 11g
Release 2. If you install the Oracle Database
software, choose the installation option that enables
you to create and configure a database. A new
database, including sample user accounts and their
associated schemas, will be created for you. (Note
that SQL_101 is the user account to use for the
examples in this series; its also the schema in which
youll create database tables and other objects.)
When the installation process prompts you to specify
schema passwords, enter and confirm passwords
for SYS and SYSTEM and make a note of them.
Finallywhether you installed the database software
from scratch or have access to an existing Oracle
Database instancedownload, unzip, and execute
the SQL script to create the tables for the SQL_101
schema that are required for this articles examples. (View the script in a text editor for execution
instructions.)
The Sum of All Rows
All aggregate functions group data to ultimately produce a single result value. Because aggregate
functions operate on multiple row values, you can use them to generate summary data such as
totals. For example, you can answer budget planning questions such as What is the total allotted
amount of annual salary paid to all employees? The query in Listing 1 demonstrates use of the
SUM aggregate function to answer this question. It adds together all the values in the EMPLOYEE
tables SALARY column, resulting in the total value 970000.
Code Listing 1: Display the sum of all salary values in the EMPLOYEE table
SQL> set feedback on
SQL> select SUM(salary) from employee;
SUM(SALARY)
970000
1 row selected.
When You Strive for Average
Another example of a business question you can answer by using an aggregate function is, What
is currently the average annual salary for all employees? Like the query in Listing 1, the query in
Listing 2 applies an aggregate function to the EMPLOYEE tables SALARY column. The AVG
function in Listing 2 sums up the salary values and then divides the total by the number of
employee records with non-null salary values. With the total of 970000 paid annually divided by 10
employees, the average annual salary value is 97000.
Code Listing 2: Compute the average salary value across all non-null salary values
SQL> select AVG(salary) from employee;
AVG(SALARY)
97000
1 row selected.
The EMPLOYEE table holds 11 records, but the average-salary computation in Listing 2 considers
only 10 records. This happens because the AVG aggregate function ignores null values. (In the
EMPLOYEE table, the null salary value is for the employee Lori Dovichi.) To substitute a non-null
value for any null values, you can nest a NVL function call (introduced in Part 7 of this series)
inside the call to the AVG function, as demonstrated in Listing 3. The average salary value
returned in Listing 3 is lower than the value returned in Listing 2, because the null salary value for
Lori Dovichi has been replaced with a 0 and evaluated along with all other non-null salary values.
SQL 101: Having Sums, Averages, and Other Grouped Data
http://www.oracle.com/technetwork/issue-archive/2013/13-jan/o13sql-1886636.html[8/30/2014 12:41:49 PM]
Substitute non-null values for null values only when it makes sense to do so from a business
perspective.
Code Listing 3: Substitute a non-null value for any null values
SQL> select AVG(NVL(salary, 0)) avg_salary
2 from employee;
AVG_SALARY
88181.8182
1 row selected.
Keeping Count
As you know from previous articles in this series, the SQL*Plus set feedback on command
displays a count of records that satisfy your query criteria. This method works well when the
database quickly returns a small number of records that can display easily on your screen. But its
unwieldy when you evaluate hundreds, thousands, or millions of records, because you must wait
for all the records in the result set to be fetched from the database and returned to your client. A
more efficient alternative is to use the COUNT aggregate function, demonstrated in Listing 4.
Code Listing 4: Obtain a count of all employees by using COUNT(*)
SQL> select COUNT(*)
2 from employee;
COUNT(*)
11
1 row selected.
The COUNT aggregate function counts the number of records that satisfy the query condition. The
query in Listing 4 uses COUNT(*)which returns the count of all rows that satisfy the query
conditionto obtain a count of all the records in the EMPLOYEE table. COUNT(*) does not ignore
null values, whereas COUNT with a column input does. By comparing Listings 4 and 5, you can
see that the returned value is the same whether you count all columns in the EMPLOYEE table
with COUNT(*) or count just the primary key column with COUNT(employee_id).
Code Listing 5: Obtain a count of all employees by applying COUNT to the primary key column
SQL> select COUNT(employee_id)
2 from employee;
COUNT(EMPLOYEE_ID)
11
1 row selected.
However, contrast a call to COUNT(*) or COUNT(employee_id) with a call to COUNT(manager),
demonstrated in Listing 6. In the EMPLOYEE table, 5 of the 11 records have no value for the
MANAGER column, so they are not included in the single count value that is returned.
Code Listing 6: Apply COUNT to a column that contains null values
SQL> select COUNT(manager)
2 from employee;
COUNT(MANAGER)
7
1 row selected.
The query in Listing 7 demonstrates that a call to COUNT(*) or COUNT(column_name) returns a
result value of 0 if no rows match the query condition. The query requests a count of all rows and
a count of all manager values for all employee records with a hire date matching the current days
system date, SYSDATE. Because no one was hired on the date the query was run, a count value
of 0 is returned.
Code Listing 7: COUNT(*) and COUNT(column_name) both return 0 when no rows match
SQL> select COUNT(*), COUNT(manager)
2 from employee
3 where hire_date > TRUNC(SYSDATE);
COUNT(*) COUNT(MANAGER)
0 0
1 row selected.
If your goal is to determine a count of distinct values, you can combine the COUNT aggregate
function with the DISTINCT keyword. Listing 8 shows a query that determines the count of
DISTINCT or UNIQUE (a keyword you can use instead of DISTINCT) MANAGER column values in
the EMPLOYEE table. A count of 3 is returned. The null value that exists in the MANAGER
column for several employees is not included in the count of distinct manager values by the call to
the COUNT aggregate function.
Code Listing 8: COUNT and DISTINCT obtain a count of distinct values
SQL> select COUNT(DISTINCT manager) num_distinct_managers
2 from employee;
NUM_DISTINCT_MANAGERS
3
1 row selected.
Maximizing and Minimizing
You can certainly locate the maximum and minimum values within a set of row values youve
SQL 101: Having Sums, Averages, and Other Grouped Data
http://www.oracle.com/technetwork/issue-archive/2013/13-jan/o13sql-1886636.html[8/30/2014 12:41:49 PM]
fetched with a well-ordered SQL statement. But if your result set is voluminous and all you want is
the maximum or the minimum result, you dont want to scroll to the top or the bottom of the result
set to see it. You can use the MIN and MAX aggregate functions instead. The query in Listing 9
uses them to display the EMPLOYEE tables maximum and minimum salary values.
Code Listing 9: MAX and MIN obtain maximum and minimum column values
SQL> select MAX(salary), MIN(salary)
2 from employee;
MAX(SALARY) MIN(SALARY)
300000 60000
1 row selected.
One by One and Group by Group
So far the examples in this article have discussed aggregate functions working on all rows for a
particular aggregation criterion. But you might want to do further categorizations and aggregations
within your data. The GROUP BY clause enables you to collect data across multiple records and
group the results by one or more columns. Aggregate functions and GROUP BY clauses are used
in tandem to determine and return an aggregate value for every group. For example, the query in
Listing 10 obtains a count of employees in each department.
In Listing 10, note that one employee has not been assigned to a department in the EMPLOYEE
table and that person is included as a group in the results. Note also that the query uses an
ORDER BY clause. Although the GROUP BY clause groups data, it does not sort the results in
any particular order. The query in Listing 11 shows the query from Listing 10 without the ORDER
BY clause.
Code Listing 10: GROUP BY creates grouped categorizations
SQL> select COUNT(employee_id), department_id
2 from employee
3 GROUP BY department_id
4 ORDER BY department_id;
COUNT(EMPLOYEE_ID) DEPARTMENT_ID
6 10
2 20
2 30
1
4 rows selected.
Code Listing 11: No ORDER BY clause with GROUP BY clause
SQL> select COUNT(employee_id), department_id
2 from employee
3 GROUP BY department_id;
COUNT(EMPLOYEE_ID) DEPARTMENT_ID
2 30
1
2 20
6 10
4 rows selected.
When a GROUP BY clause is followed by an ORDER BY clause, the columns listed in the
ORDER BY clause must also be included in the SELECT list. The query in Listing 12
demonstrates the error that will occur if the SELECT list column list and the ORDER BY column list
do not match. Similarly, an error will occur if you do not use GROUP BY for every column in the
SELECT list that is not part of an aggregation operation, as shown by the query in Listing 13. This
query is the same as the query in Listing 12, minus the GROUP BY clause.
Code Listing 12: Error when ORDER BY clause column list doesnt also appear in the SELECT
list
SQL> select COUNT(employee_id), department_id
2 from employee
3 GROUP BY department_id
4 ORDER BY hire_date DESC;
ORDER BY hire_date DESC
*
ERROR at line 4:
ORA-00979: not a GROUP BY expression
Code Listing 13: Error when GROUP BY does not list required column
SQL> select COUNT(employee_id), department_id
2 from employee
3 ORDER BY department_id;
select COUNT(employee_id), department_id
*
ERROR at line 1:
ORA-00937: not a single-group group function
The GROUP BY clause is necessary if your intent is to return multiple groups. And your intent to
return multiple groups is determined by the inclusion in the SELECT list of any column that is not
part of an aggregation operation. In the query in Listing 13, that column is DEPARTMENT_ID in
the EMPLOYEE table. A query that uses aggregate functions and no GROUP BY clause always
returns exactly one row, even if the table you query contains no rows at the time you query it.
Having the Last Word
Just as a SELECT list can use a WHERE clause to filter the result set to include only records that
meet certain criteria, the GROUP BY clause can use a similar clause to filter groups. The HAVING
clause works with the GROUP BY clause to limit the results to groups that meet the criteria you
specify. Listing 14 expands on the query in Listing 10. Inclusion of the HAVING clause in this
query eliminates any groups with fewer than two employees from the result set. As you can see,
the group with no assigned department is not returned in Listing 14s result set, because that
SQL 101: Having Sums, Averages, and Other Grouped Data
http://www.oracle.com/technetwork/issue-archive/2013/13-jan/o13sql-1886636.html[8/30/2014 12:41:49 PM]
group contains only one employee.
Code Listing 14: HAVING clause filters groups
SQL> select COUNT(employee_id), department_id
2 from employee
3 GROUP BY department_id
4 HAVING COUNT(employee_id) > 1
5 ORDER BY department_id;
COUNT(EMPLOYEE_ID) DEPARTMENT_ID
6 10
2 20
2 30
3 rows selected.
The HAVING clause works primarily on aggregate function columns, whereas the WHERE clause
works on columns and other expressions without an aggregation operation. I say primarily
because the HAVING clause can use multiple operators in its filtering operation. For example, the
query in Listing 15 displays a count of employees-per-department-and-salary groups that satisfy
one of two criteria, only the first of which uses an aggregate function:
The department has two or more employees.
The salaries of the employees in the department are less than 100000.
Code Listing 15: HAVING clause can use multiple operators
SQL> select COUNT(employee_id), department_id, salary
2 from employee
3 GROUP BY department_id, salary
4 HAVING (COUNT(employee_id) > 1
5 OR salary < 100000)
6 ORDER BY department_id, salary desc;
COUNT(EMPLOYEE_ID) DEPARTMENT_ID SALARY
1 10 80000
1 10 70000
2 10 60000
1 20 90000
1 20 65000
1 30 70000
1 75000
7 rows selected.
Odds and Ends
Although every column included in the SELECT list must also be listed in a GROUP BY clause,
this restriction doesnt apply to number and string literals, constant expressions (expressions that
do not use column values), and functions such as SYSDATE. Listing 16 shows a query that, for
demonstration purposes, expands on the query in Listing 15. It includes a literal, a constant
expression, and the SYSDATE function in the SELECT list, but it doesnt need to list these items in
the GROUP BY or ORDER BY clause.
Code Listing 16: Literals, expressions, and functions not listed in GROUP BY or ORDER BY
SQL> select COUNT(employee_id), department_id, salary,
2 SYSDATE, String Literal, 42*37 Expression
3 from employee
4 GROUP BY department_id, salary
5 HAVING (COUNT(employee_id) > 1
6 OR salary < 100000)
7 ORDER BY department_id, salary desc;
COUNT(EMPLOYEE_ID) DEPARTMENT_ID SALARY SYSDATE STRINGLITERAL'
EXPRESSION
155000
1 row selected.
Conclusion and Anticipation
This article has shown you a few of the most common aggregate functions and how you can use
them to manipulate how your data is displayed. Youve seen how to use single-group aggregate
SQL 101: Having Sums, Averages, and Other Grouped Data
http://www.oracle.com/technetwork/issue-archive/2013/13-jan/o13sql-1886636.html[8/30/2014 12:41:49 PM]
functions such as MAX, MIN, and AVG as well as multigroup functions such as COUNT and SUM.
You now know how these functions operate when null values are present in your data and how
such operations can affect your results. Youve been introduced to the GROUP BY and HAVING
clauses and have been shown how these clauses can help you further filter and categorize your
summary data. Last, but not least, you know what pitfalls to look for when using an ORDER BY
clause with a GROUP BY clause and that column values listed in the SELECT list must also
appear in any GROUP BY clause. By no means does this article provide an exhaustive list of the
Oracle Database aggregate functions. Review the documentation for more details:
bit.ly/WxKZFu.The next installment of SQL 101 will discuss analytic functions.
Melanie Caffrey is a senior development manager at Oracle. She is a
coauthor of Expert PL/SQL Practices for Oracle Developers and DBAs
(Apress, 2011) and Expert Oracle Practices: Oracle Database
Administration from the Oak Table (Apress, 2010).
Send us your comments
SQL 101: A Window into the World of Analytic Functions
http://www.oracle.com/technetwork/issue-archive/2013/13-mar/o23sql-1906475.html[8/30/2014 12:44:12 PM]
As Published In
March/April 2013
TECHNOLOGY: SQL 101
A Window into the World of Analytic
Functions
By Melanie Caffrey
Part 10 in a series on the basics of the relational database and SQL
Part 9 in this series, Having Sums, Averages, and Other Grouped Data (Oracle Magazine,
January/February 2013), introduced common SQL aggregate functions and the GROUP BY and
HAVING clauses, showing how you can use them to manipulate single-row and grouped result
set data to convey more-meaningful results. The discussion of aggregate functions segues
logically into the subject of more-advanced SQL operations that use aggregations and other
specific views of your data. This article is the first in a three-article sequence that introduces you
to some commonly used analytic functions and their associated clauses. Analytic functions not
only operate on multiple rows but also can perform operations such as ranking data, calculating
running totals, and identifying changes between different time periods (to name a few)all of
which facilitate creation of queries that answer business questions for reporting purposes.
To try out the examples in this series, you need access to an Oracle Database instance. If
necessary, download and install an Oracle Database edition for your operating system. I
recommend installing Oracle Database, Express Edition 11g Release 2. If you install the Oracle
Database software, choose the installation option that enables you to create and configure a
database. A new database, including sample user accounts and their associated schemas, will be
created for you. (Note that SQL_101 is the user account to use for the examples in this series; its
also the schema in which youll create database tables and other objects.) When the installation
process prompts you to specify schema passwords, enter and confirm passwords for SYS and
SYSTEM and make a note of them.
Finallywhether you installed the database software from scratch or have access to an existing
Oracle Database instancedownload, unzip, and execute the SQL script to create the tables for
the SQL_101 schema that are required for this articles examples. (View the script in a text editor
for execution instructions.)
Increasing Your Bottom Line
You can use standard SQL to answer most data questions. However, pure SQL queries that
answer questions such as What is the running total of employee salary values as they are
summed row by row? arent easy to write and may not perform well over time. Analytic functions
add extensions to SQL that make such operations faster-running and easier to code.
The query in Listing 1 demonstrates use of the SUM analytic function. The query results list all
employees alongside their respective salary values and display a cumulative total of their salaries.
Code Listing 1: Obtain a cumulative salary total, row by row, for all employees
SQL> set feedback on
SQL> set lines 32000
SQL> select last_name, first_name, salary,
2 SUM (salary)
3 OVER (ORDER BY last_name, first_name) running_total
4 from employee
5 order by last_name, first_name;
LAST_NAME FIRST_NAME SALARY RUNNING_TOTAL
Dovichi Lori
Eckhardt Emily 100000 100000
Friedli Roger 60000 160000
James Betsy 60000 220000
Jeffrey Thomas 300000 520000
Michaels Matthew 70000 590000
Newton Donald 80000 670000
Newton Frances 75000 745000
Wong Theresa 70000 815000
leblanc mark 65000 880000
peterson michael 90000 970000
11 rows selected.
This result is accomplished with the query line that reads
SUM (salary)
OVER (ORDER BY last_name, first_name) running_total
Anatomy of an Analytic Function
Learning the syntax of an analytic function is half the battle in harnessing its power for efficient
query processing. The syntax for the analytic query line in Listing 1 is
FUNCTION_NAME( column | expression,column | expression,... )
OVER
( Order-by-Clause )
In Listing 1, the function name is SUM. The argument to the SUM function is the SALARY column
(although it could also be an expression). The OVER clause identifies this function call as an
analytic function (as opposed to an aggregate function). The ORDER BY clause identifies the
piece of data this analytic function will be performed over.
This series will discuss scalar subqueries in a later installment. Suffice it to say for this articles
purposes that using a scalar subquery is another method you could employ to achieve the result
obtained in Listing 1. However, it would perform significantly more slowly and its syntax would be
more difficult to write than the analytic query line in Listing 1.
SQL 101: A Window into the World of Analytic Functions
http://www.oracle.com/technetwork/issue-archive/2013/13-mar/o23sql-1906475.html[8/30/2014 12:44:12 PM]
Code Listing 2: Obtain a cumulative salary total, row by row, by department
SQL> select last_name, first_name, department_id, salary,
2 SUM (salary)
3 OVER (PARTITION BY department_id ORDER BY last_name, first_name)
department_total
4 from employee
5 order by department_id, last_name, first_name;
LAST_NAME FIRST_NAME DEPARTMENT_ID SALARY DEPARTMENT_TOTAL
Dovichi Lori 10
Eckhardt Emily 10 100000 100000
Friedli Roger 10 60000 160000
James Betsy 10 60000 220000
Michaels Matthew 10 70000 290000
Newton Donald 10 80000 370000
leblanc mark 20 65000 65000
peterson michael 20 90000 155000
Jeffrey Thomas 30 300000 300000
Wong Theresa 30 70000 370000
Newton Frances 75000 75000
11 rows selected.
The query in Listing 2 cumulatively sums the salary values of the employee rows within each
department. The PARTITION clause ensures that the analytic function is applied independently to
each department group (or partition). You can see that the cumulative total resets after the
department changes from 10 to 20, and again from 20 to 30, and finally from 30 to an employee
record that has no department ID. The analytic function syntax including a PARTITION clause
expands as follows on the syntax used in the Listing 1 example:
FUNCTION_NAME( argument,argument, )
OVER
( Partition-Clause Order-by-Clause )
A Separate Order
The queries in Listings 1 and 2 sort the rows returned by employee last name and first name. The
query in Listing 3 uses a slightly different ordering criterion for the analytic function computation.
Code Listing 3: Compute each row based on salary value
SQL> select last_name, first_name, department_id, salary,
2 SUM (salary)
3 OVER (PARTITION BY department_id ORDER BY salary)
department_total
4 from employee
5 order by department_id, salary, last_name, first_name;
LAST_NAME FIRST_NAME DEPARTMENT_ID SALARY DEPARTMENT_TOTAL
Friedli Roger 10 60000 120000
James Betsy 10 60000 120000
Michaels Matthew 10 70000 190000
Newton Donald 10 80000 270000
Eckhardt Emily 10 100000 370000
Dovichi Lori 10 370000
leblanc mark 20 65000 65000
peterson michael 20 90000 155000
Wong Theresa 30 70000 70000
Jeffrey Thomas 30 300000 370000
Newton Frances 75000 75000
11 rows selected.
The analytic function in Listing 3 computes the department total values based on salary, in
ascending order for each partition, with NULL salary values evaluated last. You can see that the
record for Lori Dovichithe only record with a NULL salary valueends up with the same
DEPARTMENT_TOTAL value as the record in the same department (Emily Eckhardt) that has the
highest salary value.
An analytic functions ORDER BY clause works independently from the ORDER BY clause of the
overall query that contains the analytic function. Little or no correlation exists between the two
unless they use the same column or expression listings in the same order. In Listing 4, for
example, note that even though the data returned is listed in department/last name/first name
order (like the result sets in Listings 1 and 2), the values returned for the DEPARTMENT_TOTAL
expression match the values for those returned in Listing 3. And even though Betsy James and
Lori Dovichi appear in a different order in the result sets of Listings 3 and 4, the values returned
for their respective department total computations are the same.
Code Listing 4: Sort the data returned from the query in Listing 3 differently
SQL> select last_name, first_name, department_id, salary,
2 SUM (salary)
3 OVER (PARTITION BY department_id ORDER BY salary)
department_total
4 from employee
5 order by department_id, last_name, first_name;
LAST_NAME FIRST_NAME DEPARTMENT_ID SALARY DEPARTMENT_TOTAL
Dovichi Lori 10 370000
Eckhardt Emily 10 100000 370000
Friedli Roger 10 60000 120000
James Betsy 10 60000 120000
Michaels Matthew 10 70000 190000
Newton Donald 10 80000 270000
leblanc mark 20 65000 65000
peterson michael 20 90000 155000
Jeffrey Thomas 30 300000 370000
Wong Theresa 30 70000 70000
Newton Frances 75000 75000
11 rows selected.
Your Choice of Window
An analytic function might or might not include a windowing clause. A windowing clause is a set of
parameters or keywords that defines the group (or window) of rows within a particular partition that
will be evaluated for analytic function computation. The query in Listing 1 uses a windowing clause
by default, because it uses an ORDER BY clause. An ORDER BY clause, in the absence of any
SQL 101: A Window into the World of Analytic Functions
http://www.oracle.com/technetwork/issue-archive/2013/13-mar/o23sql-1906475.html[8/30/2014 12:44:12 PM]
further windowing clause parameters, effectively adds a default windowing clause: RANGE
UNBOUNDED PRECEDING, which means, The current and previous rows in the current partition
are the rows that should be used in the computation. When an ORDER BY clause isnt
accompanied by a PARTITION clause, the entire set of rows used by the analytic function is the
default current partition.
The queries in Listings 3 and 4 include a PARTITION clause but use no windowing clause
parameters. In the calculated results, the DEPARTMENT_TOTAL values for Betsy James and
Roger Friedli are identical. In the absence of windowing clause parameters, when your querys
analytic function orders by a particular column or expression within its partition and two or more
rows have the same value, the analytic function is applied to each of them and returns the same
result, because the analytic function cannot ascertain the order in which they should be evaluated.
The query in Listing 5 uses the ROWS 2 PRECEDING windowing clause to sum the current rows
salary value with just the two preceding rows salary values. Even though the employee listed just
above Matthew Michaels, Betsy James, has a DEPARTMENT_TOTAL value of 220000, the
DEPARTMENT_TOTAL value listed for Matthew Michaels is 190000. This occurs because only
the SALARY value for Matthew Michaels, 70000, is summed with the SALARY values of the two
rows directly preceding histhose of Betsy James and Roger Friedli.
Code Listing 5: Add a ROWS windowing clause
SQL> select last_name, first_name, department_id, salary,
2 SUM (salary)
3 OVER (PARTITION BY department_id ORDER BY last_name, first_name
4 ROWS 2 PRECEDING) department_total
5 from employee
6 order by department_id, last_name, first_name;
LAST_NAME FIRST_NAME DEPARTMENT_ID SALARY DEPARTMENT_TOTAL
Dovichi Lori 10
Eckhardt Emily 10 100000 100000
Friedli Roger 10 60000 160000
James Betsy 10 60000 220000
Michaels Matthew 10 70000 190000
Newton Donald 10 80000 210000
leblanc mark 20 65000 65000
peterson michael 20 90000 155000
Jeffrey Thomas 30 300000 300000
Wong Theresa 30 70000 370000
Newton Frances 75000 75000
11 rows selected.
If a windowing clause that uses parameters is added to an analytic function, the resulting syntax
will look like this:
FUNCTION_NAME( argument,argument, )
OVER
( Partition-Clause Order-by-Clause Windowing-Clause)
Multiple Windows into Your Data
The windowing clause provides either a sliding or an anchored view of data, depending on which
parameters you pass to it. Queries with just an ORDER BY clause (such as those in Listings 1, 2,
3, and 4) provide an anchored view of the data: it begins with the first row (or top) of the partition
and ends with the current row being processed. The query in Listing 5 results in a sliding view of
the data, because the DEPARTMENT_TOTAL value for each row can change, depending on how
the data is sorted (ordered) within each partition.
Listing 5 demonstrates use of the ROWS clause as the parameter input to the windowing clause.
You can also create a sliding view of data by using the RANGE clause. Unlike the ROWS clause,
the RANGE windowing clause can be used only with ORDER BY clauses containing columns or
expressions of numeric or date datatypes. It has this datatype requirement because it operates on
all rows within a certain range of the current row. The value for the column or expression by which
your data is ordered within each partition falls within specified numeric or date units from the
current row.
Code Listing 6: Sort a partition by date of hire and use a RANGE windowing clause
SQL> select last_name, first_name, department_id, hire_date, salary,
2 SUM (salary)
3 OVER (PARTITION BY department_id ORDER BY hire_date
4 RANGE 90 PRECEDING) department_total
5 from employee
6 order by department_id, hire_date;
LAST_NAME FIRST_NAME DEPARTMENT_ID HIRE_DATE SALARY
DEPARTMENT_TOTAL
Eckhardt Emily 10 07-JUL-04 100000
100000
Newton Donald 10 24-SEP-06 80000
80000
James Betsy 10 16-MAY-07 60000
190000
Friedli Roger 10 16-MAY-07 60000
190000
Michaels Matthew 10 16-MAY-07 70000
190000
Dovichi Lori 10 07-JUL-11
peterson michael 20 03-NOV-08 90000
90000
leblanc mark 20 06-MAR-09 65000
65000
Jeffrey Thomas 30 27-FEB-10 300000
300000
Wong Theresa 30 27-FEB-10 70000
370000
Newton Frances 14-SEP-05 75000
75000
11 rows selected.
The query in Listing 6 illustrates how the RANGE clause works. The querys partition is sorted by
HIRE_DATE. The query then specifies the following windowing clause:
SQL 101: A Window into the World of Analytic Functions
http://www.oracle.com/technetwork/issue-archive/2013/13-mar/o23sql-1906475.html[8/30/2014 12:44:12 PM]
Next Steps
READ SQL 101, Parts 19
READ more about
relational database design and concepts
Oracle Database Concepts 11g Release 2
(11.2)
Oracle Database SQL Language Reference
11g Release 1 (11.1)
Oracle Database Data Warehousing Guide
11g Release 2 (11.2)
Oracle SQL Developer Users Guide Release
3.1
DOWNLOAD the sample script for this article
RANGE 90 PRECEDING
This line means, Provide a summary of
the current rows salary value together with
the salary values of all previous rows
whose HIRE_DATE value falls within 90
days preceding the HIRE_DATE value of
the current row. Note that within
Department 10, only three rows have a
DEPARTMENT_TOTAL value different
from their SALARY value. The employees
listed in these rows were all hired on the
same date and therefore fall within the
range of date values required for salary
summation.
Also note that within Department 30, two
employees were hired on the same date
but only one of the rows lists a
DEPARTMENT_TOTAL value different
from its SALARY value. This result is due
to the PRECEDING keyword in the
RANGE clause. Effectively, this means, Look at any rows that precede the current row before
determining whether the HIRE_DATE units being sorted fall within the range of the current rows
HIRE_DATE. No row precedes that of Thomas Jeffrey in Department 30, so his resultant
DEPARTMENT_TOTAL value remains unchanged and is no different from his listed SALARY
value.
The query in Listing 7 illustrates the importance of using only columns or expressions of date or
numeric datatypes. It tries to sort each partition by employee last name and first name. Because a
RANGE windowing clause can determine only an appropriate range of values dependent upon
numeric or date rangesnot textual or string rangesit cannot determine the appropriate range
and causes the query to fail.
Code Listing 7: RANGE windowing clause that uses an incorrect datatype
SQL> select last_name, first_name, department_id, hire_date, salary,
2 SUM (salary)
3 OVER (PARTITION BY department_id ORDER BY last_name, first_name
4 RANGE 90 PRECEDING) department_total
5 from employee
6 order by department_id, hire_date;
SUM (salary)
*
ERROR at line 2:
ORA-30486: invalid window aggregation group in the window specification
Also, if your querys analytic function uses a RANGE windowing clause, you will be able to use
only one column or expression in the ORDER BY clause; ranges are one-dimensional. These
restrictions do not apply to the ROWS windowing clause, which can be applied to any datatype
and is not limited to a single column or expression in the ORDER BY clause.
Narrowing Your Viewpoint
In its most basic form, a window can be specified in one of three mutually exclusive ways. Table 1
shows the types of parameters that can be passed to the ROWS or RANGE windowing clauses.
Windowing
Clause
Parameter
Description
current row The window begins and ends with the current row being processed.
UNBOUNDED
PRECEDING
The window begins with the first row of the current partition and ends with
the current row being processed.
numeric
expression
PRECEDING
ROWS clause The window begins with the row that is numeric
expression rows preceding the current row and ends with the current row
being processed.
RANGE clause The window begins with the row whose ORDER BY value
is numeric expression rows less than, or preceding, the current row and
ends with the current row being processed.
Table 1: Windowing clause parameters
So far, all the windows demonstrated in this article end at the current row and use preceding row
or range values in their computations. You can also use the BETWEEN operator to specify a
window in which the current row falls somewhere in the middle of the result set. The query in
Listing 8 demonstrates that in addition to a ROWS or RANGE clause that specifies that your
window starts with previous row values and ends with the current row being processed, you can
also use the FOLLOWING parameter to look at rows following the current row being processed
and make an evaluation based on those row values.
Code Listing 8: Query with a RANGE windowing clause that uses the BETWEEN and
FOLLOWING parameters
SQL> select last_name, first_name, department_id, hire_date, salary,
2 SUM (salary)
SQL 101: A Window into the World of Analytic Functions
http://www.oracle.com/technetwork/issue-archive/2013/13-mar/o23sql-1906475.html[8/30/2014 12:44:12 PM]
3 OVER (PARTITION BY department_id ORDER BY hire_date
4 RANGE BETWEEN 365 PRECEDING AND 365 FOLLOWING)
department_total
5 from employee
6 order by department_id, hire_date;
LAST_NAME FIRST_NAME DEPARTMENT_ID HIRE_DATE SALARY
DEPARTMENT_TOTAL
Eckhardt Emily 10 07-JUL-04 100000
100000
Newton Donald 10 24-SEP-06 80000
270000
James Betsy 10 16-MAY-07 60000
270000
Friedli Roger 10 16-MAY-07 60000
270000
Michaels Matthew 10 16-MAY-07 70000
270000
Dovichi Lori 10 07-JUL-11
peterson michael 20 03-NOV-08 90000
155000
leblanc mark 20 06-MAR-09 65000
155000
Jeffrey Thomas 30 27-FEB-10 300000
370000
Wong Theresa 30 27-FEB-10 70000
370000
Newton Frances 14-SEP-05 75000
75000
11 rows selected.
Conclusion
Using analytic functions is a powerful way to get answers about your data that would otherwise
require convoluted, possibly poorly performing SQL. Your reporting needs will dictate not only
which analytic functions you use but also which windowing clauses (if any) will provide the
reporting view into your data that best conveys meaningful results to your users. This article has
demonstrated use of a common analytic function (SUM); the PARTITION and OVER clauses; the
ROWS and RANGE windowing clauses; and several basic, common windowing clause parameter
specifications. The next installment of SQL 101 will continue the discussion of analytic functions.
To learn more details about what you can glean from using the Oracle analytic functions, review
the documentation at bit.ly/yWtbz1 and bit.ly/R4cZyq.
Melanie Caffrey is a senior development manager at Oracle. She is a
coauthor of Expert PL/SQL Practices for Oracle Developers and DBAs
(Apress, 2011) and Expert Oracle Practices: Oracle Database
Administration from the Oak Table (Apress, 2010).
Send us your comments
SQL article May-June 2013
http://www.oracle.com/technetwork/issue-archive/2013/13-may/o33sql-1917326.html[8/30/2014 12:49:06 PM]
As Published In
May/June 2013
Next Steps
READ SQL 101, Parts 110
READ more about
relational database design and
concepts
Oracle Database Concepts 11g
Release 2 (11.2)
Oracle Database SQL Language
Reference 11g Release 1 (11.1)
Oracle Database Data Warehousing
Guide 11g Release 2 (11.2)
Oracle Database SQL Language
Reference 11g Release 2 (11.2)
DOWNLOAD the sample script for
this article
TECHNOLOGY: SQL 101
Leading Ranks and Lagging
Percentages: Analytic Functions,
Continued
By Melanie Caffrey
Part 11 in a series on the basics of the relational database and SQL
This article is the second in a three-article sequence that introduces you to some commonly used
SQL analytic functions and their associated clauses. Analytic functions add extensions to SQL
that make complex queries easier to code and faster-running. In Part 10 of this series, A Window
into the World of Analytic Functions (Oracle Magazine, March/April 2013), you learned how the
SUM analytic function, the PARTITION and OVER clauses, the ROWS and RANGE windowing
clauses, and several windowing clause parameter specifications help you manipulate result set
data for business reporting purposes. This article introduces you to analytic functions that enable
your queries to
Rank datafor example, to display the three employees with the highest salaries by
department
Return the first or last value from a groupfor example, to compare the salary of every
employee in a department with that of the last employee hired in that department
Provide your report with the rows that either precede (lead) or follow (lag) the current row
being processedfor example, to discover how many days before an employees hire date
the penultimately hired employee was hired
Obtain percentages within a groupfor example, to find out what percentage a particular
employee received of the total amount a department pays its employees annually
To try out the examples in this series, you need
access to an Oracle Database instance. If
necessary, download and install an Oracle Database
edition for your operating system. I recommend
installing Oracle Database, Express Edition 11g
Release 2. If you install the Oracle Database
software, choose the installation option that enables
you to create and configure a database. A new
database, including sample user accounts and their
associated schemas, will be created for you. (Note
that SQL_101 is the user account for the examples
in this series; its also the schema in which youll
create database tables and other objects.) When the
installation process prompts you to specify schema
passwords, enter and confirm passwords for SYS
and SYSTEM and make a note of them.
Finallywhether you installed the database software
from scratch or have access to an existing Oracle
Database instancedownload, unzip, and execute
the SQL script to create the tables for the SQL_101
schema required for this articles examples. (View
the script in a text editor for execution instructions.)
Being Outranked
A query that retrieves the top or bottom N row(s) from a database table that satisfy certain criteria
is sometimes referred to as a top-N query. For example, you might want to ask who the most
highly paid employees are or which department has the lowest sales figures. An easy way to
answer such a question is to use either the RANK or the DENSE_RANK analytic function, both of
which calculate and display the numerical rank of a value within a group of values. The example in
Listing 1 lists all employees alongside their respective salary values, partitioned and sorted by
department and further sorted, in descending order, by salary. It uses the DENSE_RANK analytic
function to assign a numerical rank to the salaries within each department.
Code Listing 1: Code Listing 1: List employees, ranked by department, by salary
SQL> set feedback on
SQL> set lines 32000
SQL> select department_id, last_name, first_name, salary,
2 DENSE_RANK() over (partition by department_id
3 order by salary desc) dense_ranking
4 from employee
5 order by department_id, salary desc, last_name, first_name;
DEPARTMENT_ID LAST_NAME FIRST_NAME SALARY
DENSE_RANKING
10 Dovichi Lori
1
10 Eckhardt Emily 100000
2
10 Newton Donald 80000
3
10 Michaels Matthew 70000
4
10 Friedli Roger 60000
SQL article May-June 2013
http://www.oracle.com/technetwork/issue-archive/2013/13-may/o33sql-1917326.html[8/30/2014 12:49:06 PM]
5
10 James Betsy 60000
5
20 peterson michael 90000
1
20 leblanc mark 65000
2
30 Jeffrey Thomas 300000
1
30 Wong Theresa 70000
2
Newton Frances 75000
1
11 rows selected.
The results in Listing 1 reveal an interesting analytic function phenomenon. When a query uses a
descending sort order, a NULL value can affect the outcome of the analytic function being used.
By default, with a descending sort, SQL views NULLs as being higher than any other value. In
Listing 1, the record for employee Lori Dovichi has no salary value, yet the DENSE_RANK
analytic function gives her salary a rank value of 1 the highest rankin Department 10.
You can eliminate NULLs from consideration by adding a WHERE clause such as
WHERE SALARY IS NOT NULL
Alternatively, you can use the NULLS LAST extension to the ORDER BY clause in your
windowing clause, as demonstrated in Listing 2. The record for Lori Dovichi still appears first for
Department 10, because the querys overall ORDER BY clause still orders by salary in descending
order. But the rank value attributed to that record is now 5the lowest rank value for Department
10. Note also that the DENSE_RANK function assigns the same rank value, 4, to two records
(Roger Friedlis and Betsy James) in the results for Department 10, because both employees
have the same salary value.
Code Listing 2: List employees, ranked by department, by salary, with NULLS LAST
SQL> select department_id, last_name, first_name, salary,
2 DENSE_RANK() over (partition by department_id
3 order by salary desc NULLS LAST)
dense_ranking
4 from employee
5 order by department_id, salary desc, last_name, first_name;
DEPARTMENT_ID LAST_NAME FIRST_NAME SALARY
DENSE_RANKING
10 Dovichi Lori
5
10 Eckhardt Emily 100000
1
10 Newton Donald 80000
2
10 Michaels Matthew 70000
3
10 Friedli Roger 60000
4
10 James Betsy 60000
4
20 peterson michael 90000
1
20 leblanc mark 65000
2
30 Jeffrey Thomas 300000
1
30 Wong Theresa 70000
2
Newton Frances 75000
1
11 rows selected.
Listing 3 performs a similar query to that of Listing 2, with the RANK analytic function instead of
DENSE_RANK. Note that the results include no rank value of 5 for Department 10. The reason is
that DENSE_RANK and RANK attribute rank values to records differently. DENSE_RANK returns
ranking numbers without any gaps, regardless of any records that have the same value for the
expression in the ORDER BY windowing clause. In contrast, when the RANK analytic function
finds multiple rows with the same value and assigns them the same rank, the subsequent rank
numbers take account of this by skipping ahead. As you see in the results for Listing 3, RANK
assigns a rank value of 4 to two records and skips to a rank value of 6 for the final record in the
department, which has the lowest rank value.
Code Listing 3: Use the RANK analytic function instead of the DENSE_RANK analytic function
SQL> select department_id, last_name, first_name, salary,
2 RANK() over (partition by department_id
3 order by salary desc NULLS LAST)
regular_ranking
4 from employee
5 order by department_id, salary desc, last_name, first_name;
DEPARTMENT_ID LAST_NAME FIRST_NAME SALARY
REGULAR_RANKING
10 Dovichi Lori
6
10 Eckhardt Emily 100000
1
10 Newton Donald 80000
2
10 Michaels Matthew 70000
3
10 Friedli Roger 60000
4
10 James Betsy 60000
4
20 peterson michael 90000
1
20 leblanc mark 65000
2
SQL article May-June 2013
http://www.oracle.com/technetwork/issue-archive/2013/13-may/o33sql-1917326.html[8/30/2014 12:49:06 PM]
30 Jeffrey Thomas 300000
1
30 Wong Theresa 70000
2
Newton Frances 75000
1
11 rows selected.
Finishing First or Last
For reporting purposes, it might occasionally be useful to include the first value obtained for a
particular group or window when displaying your query results. You can use the FIRST_VALUE
analytic function for this purpose, as shown in Listing 4. The query in Listing 4 returns windows
that are partitioned by department and ordered by date of hire within each partition. Alongside
each returned salary value, the first salary value obtained per window is also displayed. This
information could be useful for comparing the salary value of every employee in a department with
that of the first employee hired in that department.
Code Listing 4: Display the first value returned per window, using FIRST_VALUE
SQL> select last_name, first_name, department_id, hire_date, salary,
2 FIRST_VALUE(salary)
3 over (partition by department_id order by hire_date)
first_sal_by_dept
4 from employee
5 order by department_id, hire_date;
LAST_NAME FIRST_NAME DEPARTMENT_ID HIRE_DATE SALARY
FIRST_SAL_BY_DEPT