You are on page 1of 281

SQL by Example

Learn how to create and query


databases in eight easy lessons!

Charlotte McGary
Copyright © 2014 Charlotte McGary. All Rights Reserved.

No portion of this book may be reproduced by any means


whatsoever without the expressed written permission of the
author.

Companion files for this book are available at:


https://www.charlotteswebworld.com/downloads.html
Table of Contents
Table of Contents
INTRODUCTION
Prerequisites
LESSON 1
An overview of databases
How data is stored
Tables
Columns
Rows
Data types
NULL Values
Schemas and Owners
Comments
Asking simple questions with SQL
Selecting data
USE keyword syntax
SELECT keyword syntax
FROM keyword syntax
Running your first query
Sorting data
The WHERE clause
Operators
Examples
Wildcards
The LIKE operator
The IN operator
Review 1
LESSON 2
Using Aliases
Calculating data
Data Type Precedence
The COUNT function
The DISTINCT Keyword
The AVG function
The SUM function
The MIN and MAX functions
Converting Data Types
CAST( ) and CONVERT( )
Converting to the FLOAT Data Type
Converting to the DECIMAL Data Type
Converting to the INT Data Type
The ROUND( ) Function
Conversion Tips
The GROUP BY clause
The HAVING clause
The OVER (PARTITION BY) clause
Review 2
LESSON 3
Retrieving data from multiple tables
Inner Joins
Outer Joins
LEFT and RIGHT keywords
Self Joins
Concatenating Strings
Review 3
LESSON 4
Combining query results
The UNION operator
Using Static Strings for Column Values
Date and Time functions and conversions
Date and time functions
The GETDATE() function
The DATEPART() function
Converting dates to strings
More Date Conversion Examples
Review 4
LESSON 5
Databases
Creating Databases
Deleting Databases
Tables
Primary Keys
Creating Tables
Inserting Rows
Adding Columns to a Table
Dropping a Column
Renaming a Column
Updating Rows
Deleting Rows
Dropping A Table
DELETE vs. DROP
Saving a Query
Executing a Saved Query
Creating and Populating a Table from a Saved Query
Views
System Views
Creating a View
Querying a View
Review 5
LESSON 6
Strings
Collation
Case-Sensitivity
Converting Case
UPPER() and LOWER()
Substrings
LEFT( ) and RIGHT( ) Functions
CHARINDEX( )
Using a Static String as an Output Value
More on Calculating Data
Converting Numerical Data to VARCHAR
Calculating Values from Column Data
Review 6
LESSON 7
Subqueries
Returning a Single Value with a Subquery
Using a Subquery in a WHERE Clause
Using a Subquery in a SELECT Statement
Returning Multiple Values with a Subquery
The CASE Statement
Simple CASE Statement
Searched CASE Statement
Stored Procedures
Using Stored Procedures and System Views
DBCC CHECKTABLE
sp_MSforeachtable
Creating a Stored Procedure
Using Variables in a Query
Generating an HTML report
Deleting a Stored Procedure
Review 7
LESSON 8
Constraints
Setting Constraints
Primary Key Constraints
Foreign Key Constraints
UNIQUE Constraints
NOT NULL Constraints
CHECK Constraints
Setting a Constraint During Table Creation
Testing Constraints
Dropping a Constraint
Adding or Changing a Constraint
Checking for Substrings
Importing Records from Excel
Using the Import Data Feature
Creating a New Table from an Excel Spreadsheet
Appending Data to an Existing Table
Working with Indexes
Creating an Index
Single-Column Index
Unique Index
Composite Index
Deleting an Index
Transactions
Review 8
APPENDICES
Appendix A: Installing SQL Server 2008 R2 SP2
Express Edition
Downloading the installation package
Installing the software
Downloading the pubs database
Connecting to your database server
Installing the pubs database
Appendix B: Tables and Columns in the Pubs
Database
Appendix C: Solutions to Review Exercises
Review 1 Solutions
Review 2 Solutions
Review 3 Solutions
Review 4 Solutions
Review 5 Solutions
Review 6 Solutions
Review 7 Solutions
Review 8 Solutions
Appendix D: Lesson Files
CreateAndPopulatePlanetsTable.sql
UpdatePlanetsTable.sql
UpdatePlanetsTable-2.sql
CreateAndPopulateMoonsTable.sql
UpdateMoonsTable.sql
CreateAndPopulatePlanetMassesTable.sql
UpdatePlanetMassesTable.sql
CreateProcedure_sp_find_closest.sql
CreateProcedure_sp_planet_categories.sql
Appendix E: Additional Resources
Free DBMS Downloads
INTRODUCTION
The more information a company has to keep track of, the more
likely it is that it uses some type Database Management System
(DBMS) for storing and retrieving critical information, such as
customer and employee identification and product inventory.

SQL is an acronym for Structured Query Language, and it is used to


give instructions to a database. Those instructions are used to create
a database, populate tables with data, and retrieve information from
databases. DBMSs that use SQL come in several forms from a
variety of vendors. For more information on vendors and free
versions of DBMS products, see Appendix E: Additional Resources.

Companion files for this book are available at:


https://www.charlotteswebworld.com/downloads.html.

Appendix D: Lesson Files lists the contents of each file.

Prerequisites
The examples in this book access Microsoft SQL Server 2008 R2
SP2 Express Edition and the pubs database using Microsoft SQL
Server Management Studio. If you do not have access to a SQL
Server, you will need to download and install both the free Express
Edition and the pubs database on your computer. For download and
installation instructions, see Appendix A: Installing SQL Server.
LESSON 1
In this lesson you will learn about:

Storing data in a database


Retrieving information from a database with simple SQL
queries
An overview of databases
Simply put, a database is an organized collection of related data. For
example, a library might store data related to book titles, authors,
and who currently has a book that is checked out. An astronomer
would likely store data related to stars, such as name, type, location,
and so on. The data could go into files in a file cabinet, but isolation
the information you need from a large amount of data would be slow
and tedious. You can think of a database stored electronically as a
virtual file cabinet, and SQL as a way of finding needed information
quickly and efficiently. We’ll discuss what SQL is shortly.

Don’t confuse the term database with Database Management


System (DBMS). The database contains all the data, whereas the
DBMS is the software used to create, change, and query the
database. The examples in this book use Microsoft SQL Server
Management Studio to access the sample databases.

How data is stored


There are methods for storing data in a database, just like there are
methods for filing folders in a filing cabinet. This section discusses
the basic structure of a database and the rules it must follow.

Tables
If you’ve ever seen an Excel spreadsheet, then a typical database
structure will look familiar. A database is usually divided into
separate tables with a grid structure, each table containing related
data (like a file in a file cabinet). For example, a car dealership would
have one table for customers and a separate one for inventory. The
customer table can contain only customer information, and the
inventory table can contain only information about cars in the
dealer’s inventory.

Most of the information you need from a database will be stored in


tables. You can retrieve information from a single table (the simplest
query) or multiple tables. This lesson will cover querying single
tables.

Columns
A column is one field in a table. Many examples in this book use a
publisher’s database named pub that contains an authors table with
the following columns (fields):

au_id au_lname au_fname address city state zip contract

NOTE:
Column names cannot have any embedded spaces. Putting spaces
in names can cause all sorts of difficulties. Therefore, to separate
words in table and field names, use an underscore ( _ ) instead of a
space.

Rows
A row is one record of data, similar to a row of data in Microsoft
Excel. Using our authors table from the previous example, the
following two records show five of the eight columns of data related
to authors Abraham Bennet and Marjorie Green.

au_id au_lname au_fname city state


409-56-7008 Bennet Abraham Berkeley CA
213-46-
Green Marjorie Oakland CA
89152

Data types
Each field has a specific data type associated with it. The data type
determines what type of data is allowed in that column. In the above
example, state column allows only character values. A list of
commonly used data types follows:

Data Type Description Example


Data Type Description Example
CHAR(n) Fixed-length string of n A field containing a state
characters. abbreviation could have a
data type of CHAR(2) with
an example value of “WA”.
VARCHAR(n) Variable-length string with To allow a string of up to 20
maximum of n characters. characters for a person’s last
name, you would create that
field with the data type
VARCHAR(20).
TINYINT Integer value ranging from If you have fields storing red,
0 through 255. green, and blue HTML color
values, which can only range
from 0 to 255, you could
create each of those three
fields with the data type
TINYINT.
SMALLINT Integer value ranging from If you have fields for X and Y
-32,768 through 32,767. coordinates that are limited
to this number range, you
could create these fields with
the SMALLINT data type.
INT Integer value ranging from If you have a field storing the
-2,147,483,648 through population count for a single
2,147,483,647. country as a whole number,
you could use the INT data
type.
FLOAT(p) Floating point value with If your field stores the
precision p, where p ranges population of the whole
from1-24 (uses up 4 planet (currently
bytes/7 digits) or 25-53 6,973,738,433), you would
(uses 8 bytes/15 digits). need a data type that can
Therefore the values can hold a number larger than
range from -1.79E + 308 the maximum INT value. In
through -2.23E - 308, 0 and this case, you could use the
2.23E + 308 through 1.79E FLOAT data type.
+ 308.
Data Type Description Example
DECIMAL(p,s) Fixed precision decimal If want to store or display a
value with a maximum of s real number value with a
digits to the right of the fixed number of digits after
decimal point a maximum the decimal point, you would
of p digits total (p = number need to use the DECIMAL
of digits left of decimal point data type. A value such as Pi
+ s). Values can range from displayed to decimal places
-10^38 + 1 through 10^38 – (3.141592) would need a
1. data type of at least
DECIMAL(7,6).
DATETIME Date and time with date For a field which contains the
ranging from January 1, date and time a student
1753, through December registered for a class, use
31, 9999 and time ranging the DATETIME data type.
from 00:00:00 through
23:59:59.997
DATE Date only, ranging fromFor a field containing a
0001-01-01 through 9999-
person’s date of birth, you
12-31. can use the DATE data type.
TIME Time only, ranging fromFor a field containing the
00:00:00.0000000 through
time in 24-hour format, use
23:59:59.9999999. the TIME data type.
MONEY Monetary value (8 bytes)
For a field containing values
ranging from related to the US
-922,337,203,685,477.5808
government’s deficit, we can
through only hope we never need a
922,337,203,685,477.5807.
data type larger than
MONEY.
SMALLMONEY Monetary value (4 bytes) For a field containing entries
ranging from -214,748.3648 in a personal checking
through 214,748.3647 account, such as six dollars
and fifty cents (6.50) the
SMALLMONEY data type
will be sufficient for 99% of
us.

NOTE:
CHAR vs. VARCHAR
Unless you have a fixed-length data type such as a zip code, state
abbreviation, or a phone number, it is more efficient to use the
VARCHAR data type for strings. The CHAR data type will reserve
storage space for the maximum number of declared characters,
whether or not they are all used (fills unused area with trailing
spaces). The VARCHAR data type only store the characters actually
used, saving memory and disk storage.

You can see the data type of each column in our authors table by
clicking on the plus (+) symbol to the left of each of the following in
the SQL Server Management Studio Object Explorer pane:

+Databases
+pubs
+Tables
+dbo.authors
+Columns

NULL Values
The term NULL value means no data has been entered. This is not
the same as a space character or a zero, as both “ ” and “0”
represent actual values. If you create a local database, it is tied to you
(the owner) and will not be accessible to others at a global level.
These tables are usually prefaced with “dbo.” (data base owner), the
default database user.

Schemas and Owners


Tables must follow certain rules that are instituted at the time the
table is created. For example, a schema will define how tables can
be related, what type of data can be stored, whether NULL values
are allowed, and so on. If you create a local database, it is tied to
you (the owner) and will not be accessible to others at a global level.
These tables are usually prefaced with “dbo.” (data base owner), the
default database user.

Comments
It is often helpful to add comments to your saved queries, especially
when you have a long series of SQL statements. Comments are
used for various purposes: identifying the author of the query,
explaining the purpose of different sequences of code, and so on.
Comments do not appear in query results. The syntax for comments
is as follows:
-- Single line comment or /* Single line comment */
/* Multiple line
comment */
Asking simple questions with
SQL
Short for Structured Query Language, SQL (pronounced sequel) is a
language used for communicating with databases. It’s a powerful yet
straight-forward and easy-to-learn language that can be used with
almost any DBMS. All the example queries used in this document
were executed using SQL Server Management Studio. See
Appendix A: Installing SQL Server for instructions on using this
DBMS.

Just as any language must follow a set of rules called grammar, SQL
also has a set of rules it must follow. The English language has
verbs, nouns, adjectives, and adverbs. SQL has some similar
constructs. For example, suppose you’re an assistant to a publisher,
and he keeps all his records in a filing cabinet. He has a folder in that
cabinet labeled “Authors”, with the information related to each author
printed on a separate piece of paper within that folder. Now if the
publisher wants you to retrieve that information, he might say “Get
me a list of all the authors we’ve published.” This lesson will show
you how to translate that request into SQL.
In the examples in this book, our digital filing cabinet is a database
named pubs, which contains our digital folders (tables named
authors, publishers, titles, and so on).

You will be accessing the pubs database (see Appendix B: Tables


and Columns in the Pubs Database) using SQL Server Management
Studio.

After starting SQL Server Management Studio, select the pubs


database, click the plus “+” sign to the left of the database name,
then click the plus sign to the left of “Tables.” Keep expanding plus
signs until you see the names of all the columns in the dbo.authors
table. (The prefix “dbo” stands for “database owner”, the default
owner in SQL Server.)

Selecting data
In order to retrieve one or more records, you must tell SQL what
information you want and where that information resides. For the
most simple queries, this can be accomplished using the keywords
USE, SELECT, and FROM.

USE keyword syntax


The USE keyword indicates which database contains the table(s)
you’re querying.

USE database_name

SELECT keyword syntax


The SELECT keyword indicates which columns are to be included in
the query.

To retrieve data from all columns in a record, simply type the word
SELECT followed by an asterisk (*). An asterisk is a wildcard, which
represents all data. For more information, see Wildcards.

SELECT *

To include only specific columns in your query type the word


SELECT following by a list of column names, separated by commas.

SELECT 1st_selected_col, 2nd_selected_col, … Nth_selected_col

FROM keyword syntax


The FROM keyword indicates from which table to retrieve records.
The syntax is simply the word FROM following by the table name.

FROM table_name
Running your first query
The first query you’re going to run retrieves data from the table
dbo.authors. Click New Query on the Standard Toolbar.

In the query pane to the right of the Object Explorer, enter the following
text (as you type, keywords automatically appear as blue text):

USE pubs
SELECT * FROM dbo.authors;

In the above example, the USE keyword indicates which database


you’re querying (pubs).

The SELECT keyword followed by an asterisk (*) says to retrieve all


columns from all records in the table you’re querying.

The FROM keyword indicates the location of the data to be selected


(the table dbo.authors).

The semicolon (;) at the end indicates the query is complete (similar
to a period at the end of a sentence).

To execute the query, click !Execute on the standard toolbar or


press the F5 key. (The first five results are displayed below.)
Sorting data
In the above example, the results of the query are displayed in the
order the records were entered into the table. As we currently have
only two records, that’s not a problem. But suppose we have the ten
records in the following example and want to sort them alphabetically
by “Name”?

To sort alphabetically by a column name, use the ORDER BY


clause. So, in our example, to sort in ascending order (the default)
by author’s last name, execute the following query:

SELECT *
FROM dbo.authors
ORDER BY au_lname;
The first five results are displayed below:

You could have specified ascending order by adding ASC after


au_lname in the ORDER BY clause, however as ascending order is
the default, you don’t need to include the ASC.

If you want to sort in descending order by author’s last name,


execute the following query:
SELECT *
FROM dbo.authors
ORDER BY au_lname DESC;
The first five results are displayed below:
You can also sort by multiple columns, To first sort by state, then by
city name within each state, execute the following query:
SELECT *
FROM dbo.authors
ORDER BY state, city;
The first five and the last 10 results are displayed below:

You can also select specific columns. For example, to list only the
author’s last names, you can select just the au_lname column. This
is a matter of simply replacing the asterisk “*” in the previous
example with the column name as follows:

SELECT au_lname
FROM dbo.authors;

The first five results are displayed below:


You can select records for more than one column by separating the
column names with commas. To also select records containing the
author’s first name, city, and state, modify the above query as
follows:

SELECT au_lname, au_fname, city, state


FROM dbo.authors
ORDER BY au_lname;
The first five results are displayed below:

NOTE:
The column named “state” is rendered in blue text because it is also
a reserved word. In this case, SQL is smart enough to know you’re
referring to your column name, but still lets you know it’s a reserved
word. If you don’t want it to appear in blue text, just enclose the
column name in square brackets like this: [state].

For a list of SQL Server reserved keywords, see


http://technet.microsoft.com/en-us/library/ms189822.aspx.

The WHERE clause


You’ve seen how to retrieve all records for a table and sort them by
specific columns. But what if you want to retrieve only those records
which meet certain criteria? That’s where the “WHERE” clause
comes in.
For example, to filter records so that just information for authors who
live in California is displayed, execute the following query:
SELECT *
FROM dbo.authors
WHERE state = 'CA';
Single quotes are used to delineate a string. You do not need to use
quotes with a numeric value.
Operators
There are several operators you can use to modify the search
criteria (that is, the filter conditions) specified in a WHERE clause.
The operators you’ll probably use the most are described in the
following table:
Operator Description
= Equal to
<> or != Not equal to
< Less than
<= Less than or equal to
> Greater than
>= Greater than or equal to
BETWEEN Between two values
(inclusive)
IS NULL Is a null value
AND Appends condition where
both conditions must be
met
OR Appends condition where
either condition must be
met
NOT Negates the following
condition
LIKE Creates a search pattern
with the (%) or ( _ )
wildcard
IN Determines whether the
specified value matches
any value in a list

Examples
Find all titles with a price less than $19.99 and sort in ascending
order by price:

SELECT title, price


FROM dbo.titles
WHERE price < 19.99
ORDER BY price;

List all titles except those with a price of 10.95 or 19.99:

SELECT title, price


FROM dbo.titles
WHERE price != 10.95
AND price != 19.99;

Find all titles whose price is between $10 and $25 and sort in
ascending order by price:

SELECT title, price


FROM dbo.titles
WHERE price BETWEEN 10 AND 25
ORDER BY price;

Find all titles whose price is NOT between $10 and $25 and sort in
ascending order by price:

SELECT title, price


FROM dbo.titles
WHERE NOT price BETWEEN 10 AND 25
ORDER BY price;

or

SELECT title, price


FROM dbo.titles
WHERE price < 10 OR price > 25
ORDER BY price;

Find all publishers having a NULL value in the state column.

SELECT pub_name, [state]


FROM dbo.publishers
WHERE [state] IS NULL;

Find all publishers where the value in the state column is not NULL.

SELECT pub_name, [state]


FROM dbo.publishers
WHERE NOT [state] IS NULL;

Wildcards
Wildcards allow you more flexibility in your queries, as they don’t
require an exact match.

The asterisk (*) is a special wildcard used to represent everything all


column names. It is not use to filter data with the LIKE operator.
Wildcards can only be used with text (string) data types. When
filtering data, the percent character (%) serves a purpose similar to
the asterisk. It matches any number of characters occurring any
number of times. The underscore character ( _ ) represents a single
character.

The LIKE operator


LIKE is actually a predicate—an expression used with a clause to
return a value of true or false. But, as with the predicates AND, OR,
NOT, and so on, it is more often referred to as an operator. It’s not
that important a distinction, but because some reference materials
refer them as such, you should be aware of the term. In keeping with
the more popular usage and to avoid confusion, this document will
refer to such expressions as operators.

The LIKE operator is used with wildcards to create a search pattern


instead of searching for a specific match.

In a previous example, you used the WHERE clause to retrieve


records for all authors who live in CA. But suppose you want to
retrieve only those records for authors whose last name begins with
the letter “M”. The LIKE operator, used in conjunction with wildcards
(%) and ( _ ), allows you to do that.

SELECT * FROM dbo.authors


WHERE au_lname LIKE 'G%';

To further filter the results so that records are displayed only authors
with a last name beginning with “G” who also live in CA, modify your
query as follows:

SELECT * FROM dbo.authors


WHERE au_lname LIKE 'G%' AND state = 'CA';

You will find the operators AND, OR, and NOT quite useful for this
type of situation.
List all cities whose name is two or more words and sort in
ascending order:

SELECT city
FROM dbo.authors
WHERE city LIKE '% %'
ORDER BY city;

The above query looks for any number of characters at the


beginning of the name, and any number of characters at the end of
the name, as long as there is at least one space somewhere
between them.

To only list cities with three or more words in the name, modify the
string associated with the LIKE operator by adding a second space
and a third % as follows:

SELECT city
FROM dbo.authors
WHERE city LIKE '% % %'
ORDER BY city;

List all authors who have a two-word last name that includes a
space:

SELECT *
FROM dbo.authors
WHERE au_lname LIKE '_ _ _ %';
The above query uses 3 successive ( _ ) wildcard characters (no
spaces between the underscores) followed by a space character and
any number of characters. Each underscore represents a single
character, while the % symbol represents any number of characters.

The IN operator
In the first section of this document, you learned how to use the
WHERE clause with the AND and OR operators to specify more than
one condition. If you have more than two conditions, however, the IN
operator makes your query less cumbersome to write.
For example, suppose you want to display the author’s last name,
first name, city and state if the author lives in CA, OR, or UT. You
could write the following query using the OR operator:
SELECT au_lname, au_fname, city, [state]
FROM dbo.authors
WHERE [state] = 'MD' OR [state] = 'KS' OR [state] = 'OR';
Or, you could get the same results with less typing with the IN
operator as follows:
SELECT au_lname, au_fname, city, state
FROM dbo.authors
WHERE state IN ('MD', 'KS', 'OR');

As stated previously, the result is the same from both queries:


NOTE:
The column named “state” is rendered in blue text by SQL Server
Management Studio because it is also a reserved word. In this case,
SQL is smart enough to know you’re referring to your column name,
but still lets you know it’s a reserved word. If you don’t want it to
appear in blue text, just enclose the column name in square brackets
like this: [state].
Review 1
Now that you’re familiar with some of the basic terminology and
concepts of communicating with a database, let’s review what you’ve
learned by creating some additional queries. Answers to all review
exercises can be found in Appendix C: Solutions to Review
Exercises. (No peeking at the answers before you try the
exercises. )

1. Using the pubs database, query the dbo.authors table to


display all authors’ first and last names in ascending order by
last name, then by first name.

2. Using the pubs database, query the dbo.authors table to


display the first and last names of all authors whose last
name begins with the letter “G”.
3. Using the pubs database, query the dbo.authors table to
display last names, city, and state for all authors living in CA
but do not live in Oakland. Sort in descending order by city.

4. Using the pubs database, query the dbo.titles table to display


the title id, title, and price for all records with a NULL value in
their price field. (HINT: See the Operators section of this
document.)

5. Using the pubs database, query the dbo.titles table to display


all titles and prices, where the price is between $2.00 and
$10.00 (inclusive).

6. Using the pubs database, query the dbo.titles table to display


all titles that do not start with the letter “T” in ascending order
by title.
7. Using the pubs database, query the dbo.titles table to display
the title and price for all books whose price is 2.99, 14.99, or
19.99.

8. Using the pubs database, display a list of all publisher IDs


and names, in ascending order by publisher name. (This is a
simple query. The challenge is in determining which table you
need to query, and the column names you should select.)

9. Using the pubs database, query the dbo.employee table to


display records only for those employees not having a middle
initial. Sort in descending order first by the employee’s last
name.
LESSON 2
In this lesson you will learn about:

Aliases
Calculated columns
Aggregate functions
Converting data types
Grouping data
Using Aliases
An alias is just what the name implies: an alternate name for either a
column or a table name. They can significantly reduce the amount
text you have to type and make your queries easier for you to read.
They are especially useful when creating a column from a calculation
and when querying multiple tables. An alias does not change the
name of the column; it’s just a temporary name used for display
output.

To create an alias, simply type the column name followed by the text
“AS” and the name you want to use as an alias for your column or
table. (Table aliases will be discussed in Lesson 3.)

The following query will replace the column name “pub_name” with
the text “Publisher’s Name” in the resulting display.

USE pubs
SELECT pub_name AS "Publisher's Name"
FROM dbo.publishers;

Quotes are required around any alias containing spaces.

When querying multiple tables, you have to specify which column


you’re selecting from which table when the same column name
exists in more than one of the tables. In this situation, table aliases
become quite useful. This will be discussed further in the section
Retrieving data from multiple tables.

In the next section we’ll be using aliases for columns displayed as a


result of calculations.
Calculating data
There are several functions used to retrieve data from a table then
perform a calculation on that data. In this section we’ll discuss the
calculated column values and the aggregate functions COUNT,
SUM, MIN, and MAX. With the exception of the COUNT(*) function,
aggregate functions do not include NULL values in their calculations.

Data Type Precedence


When combining data types in calculations, the order in which the
operations are performed and the resulting value’s data type are
determined by the data type with the highest precedence. For a list
of SQL Server data types and their precedence, see
http://msdn.microsoft.com/en-us/library/ms190309(SQL.100).aspx.

To illustrate the importance of data type precedence, consider the


following two queries:

Query #1:
SELECT 2 + 4 * 3; Result is 14.

Query #2:
SELECT (2 + 4) * 3; Result is 18.

In Query #1, because multiplication has a higher precedence than


addition, SQL multiplies 4 time 3 first, then adds 2.

In Query #2, the parentheses surrounding 2 + 4 indicates that these


two numbers should be added together first, and that result will then
be multiplied by 3.

In the following query, “price” is the data type MONEY and 0.095 is
FLOAT. The resulting data type of both calculated columns is FLOAT
because that data type has a higher precedence than MONEY.

USE pubs
SELECT title, price,
price * 0.095 AS "9.5% Tax",
price + price * 0.095 AS "Total"
FROM dbo.titles;
The first 5 results are displayed below:

The COUNT function


If you want to count the total number of records in a table, execute
the following query:

SELECT COUNT(*)
FROM dbo.titles;

Unfortunately, because the result of the calculation is not an existing


column in the table, it will have no column name when displayed.
That’s where our friend alias comes in.
SELECT COUNT(*) AS "Number of Titles"
FROM dbo.titles;

Now we have a meaningful name for our results column.

The above query counts the total number of records whether or not a
record has a column containing a NULL value. To find, for example,
the number of records with a non-null price, execute the following
query:
SELECT COUNT(price)
AS "Records with non-null prices"
FROM dbo.titles;

Since there are two records containing a NULL value in the price
column, the COUNT function displays 16 instead of 18 as the
number of records found.

The DISTINCT Keyword


This argument returns only unique values. For example, in the
dbo.titles table, if you were to count the number of records having a
non-null value in the pub_id column, the query would return 18
values, one for each record. To find the number of unique pub_id
values, preface pub_id with DISTINCT.
SELECT COUNT(pub_id), COUNT(DISTINCT pub_id)
AS "Number of Publishers"
FROM dbo.titles;

The following query lists all records containing an author ID. If you
execute the query, you will get 25 records. Some of the IDs are
repeated because those authors have written more than one book.

SELECT au_id
FROM dbo.titleauthor
ORDER BY au_id;

To find out exactly how many unique authors there are in the table,
execute the following query:

SELECT DISTINCT au_id


FROM dbo.titleauthor
ORDER BY au_id;

The results will correctly show that 19 authors wrote the 25 books in
the database.

The AVG function


This function calculates the average value of a column of data. For
example, to return the average price of all the titles, execute the
following query:

SELECT AVG(price) AS “Avg price”


FROM dbo.titles;

You can also use the results of aggregate functions in calculations.


For example, to display the amount of money made per book,
multiple the year-to-date sales by the cost of the book. The query is
as follows:

SELECT *, price * ytd_sales AS “Gross Income”


FROM dbo.titles;
Notice something different about the above query? The asterisk (*) is
used twice in the SELECT statement. In the first position it
represents all columns. In the second position, it represents
multiplication.

The SELECT * selects all columns in the table.

When you append , price * ytd_sales AS “Gross Income” , you are


adding a column to the results that does not exist in the table.

If you just want to list the title_id, price, ytd_sales, and your
calculated column, you could alter the query as follows:

SELECT title_id, price, ytd_sales,


price * ytd_sales AS “Gross Income”
FROM dbo.titles;

The SUM function


This function does exactly what you suspect: adds all the values in a
column to get a total. To calculate the total number of books sold so
far, you would sum the “ytd_sales” column as follows:
SELECT SUM(ytd_sales)
AS Number_of_books_sold
FROM dbo.titles;
The MIN and MAX functions
MIN and MAX also do exactly what you’d expect from their names:
find the minimum and maximum values in a column. For example, to
find the lowest and highest price of all books in the current inventory,
execute the following query:
SELECT MIN(price) AS Min_Price,
MAX(price) AS Max_Price
FROM dbo.titles;

You could have run this as two separate queries, one for MIN and
one for MAX, but it makes more sense to see both results side-by-
side.

To display the minimum price, the maximum price, and the difference
between them, execute the following query:
SELECT MIN(price) AS "Min Price",
MAX(price) AS "Max Price",
MAX(price) - MIN(price) AS "Difference"
FROM dbo.titles;
Converting Data Types
CAST( ) and CONVERT( )
There may be times when values you retrieve are not the correct
data type you need for calculations. There are two functions which
can convert data from one data type to another: CAST() and
CONVERT(). CAST is the ANSI standard. CONVERT is specific to
Microsoft SQL Server. CONVERT allows more flexibility than CAST
through the use of a third argument (style).

The data type you’re converting from must be compatible with the
data type you’re converting to. For example, you can convert a
numeric value into a character value, but attempting to convert a
character value to a numeric value, you will get a conversion error
(unless you’re converting a character value like a zip code which has
only the characters 0-9).

The syntax for CAST() is:

CAST(value to be converted AS resulting data type)

The syntax for CONVERT() is:

CONVERT (resulting data type, value to be converted, style)

Style is an optional integer value that tells the CONVERT function


how to interpret the value to be converted. (For information on what
the various values of style can be, see http://msdn.microsoft.com/en-
us/library/ms187928.aspx.)

We'll look at sample conversions to data types FLOAT, DECIMAL,


and INT in the next section.

Why convert, you ask? Good question. See if you can deduce the
answer from the following example, where x = 2 and y = 3 and both x
and y are the integer data type.

Equation: x/y

If x and y are data type FLOAT, where x = 2 and y = 3, the result will
be 0.666666666666667.

But if x and y are data type INT, the result will be 0.

The reason is that when one integer value is divided by another


integer value, if the result is not a whole number, anything after the
decimal point is truncated, not rounded to the nearest whole number.
Therefore, to get the correct result when dividing values with INT
data type, you will need to convert the values in your calculation.

Converting to the FLOAT Data Type


The syntax for converting to the FLOAT data type is as follows:

CONVERT (FLOAT, value to be converted)


or
CAST (value to be converted AS FLOAT)

If you divide one INT data type by another INT data type, the result
may surprise you, as in the following example:

SELECT 3/2 AS "Int/Int";

The result is 1 because dividing integers truncates the result. To


make sure you don’t get this kind of error, convert either the
numerator or denominator value (or both) to a floating point number
(FLOAT data type).
SELECT CONVERT(FLOAT,3)/2 AS "Float/Int";

or

SELECT 3/CONVERT(FLOAT,2) AS "Int/Float";

Converting to the DECIMAL Data Type


The syntax to convert to the DECIMAL data type is as follows:

CONVERT (DECIMAL(total number of digits, number of digits after


decimal point), value to be converted)
or
CAST (value to be converted AS DECIMAL(total number of digits,
number of digits after decimal point)

Let’s revisit the query in Data Type Precedence:

USE pubs
SELECT title, price,
price * 0.095 AS "9.5% Tax",
price + (price * 0.095) AS "Total"
FROM dbo.titles;

The first 5 of 18 results are displayed below:


The calculated results from the above query are of the FLOAT data
type because FLOAT (the value 0.095) has a higher data type
precedence than MONEY (the price column). To display the results
with just two decimal places, convert the results to the DECIMAL
data type as follows:
USE pubs
SELECT title, price,
CONVERT(DECIMAL(10,2), price * 0.095) AS "9.5% Tax",
CONVERT(DECIMAL(10,2), price + (price * 0.095)) AS "Total"
FROM dbo.titles;

The first 5 of 18 results are displayed below:

Converting to the INT Data Type


The syntax for converting to the INT data type is as follows:

CONVERT (INT, value to be converted)


or
CAST (value to be converted AS INT)

To convert the average advance to an integer value (INT), execute


the following query:
USE pubs
SELECT CONVERT(INT, AVG(advance))
AS "Avg Advance"
FROM dbo.titles;
Notice that the CONVERT function automatically rounds the number
during the conversion.

The ROUND( ) Function


The ROUND function allows you to round a value to the nearest
whole number.

The syntax of the ROUND function is as follows:

ROUND(value to be rounded, precision used when rounding)

The precision parameter is illustrated further in the following


example:

SELECT 9.4/5.3 AS "No Rounding",


ROUND(9.4/5.3, 0) AS "Round Precision 0",
ROUND(9.4/5.3, 1) AS "Round Precision 1",
ROUND(9.4/5.3, 2) AS "Round Precision 2",
ROUND(9.4/5.3, 3) AS "Round Precision 3",
ROUND(9.4/5.3, 4) AS "Round Precision 4",
ROUND(9.4/5.3, 5) AS "Round Precision 5",
ROUND(9.4/5.3, 6) AS "Round Precision 6";

As you can see, setting a precision of 6 decimal places in this


example is the same as not rounding at all because you’re using
every decimal place from the result.

Conversion Tips
Always remember to convert integers to decimal or floating point
values when the result of an integer division is used as the value in a
denominator, (e.g. 10 / (2/3)). For example, you know that when you
divide 2 by 3, you get 0.67. But that is not how SQL sees the result.
Integer results are truncated, not rounded up or down, so anything
after the decimal point is simply thrown away. Therefore, according
to SQL, the result of the integer division 2/3 is 0, which results in a
“divide by zero error” (see the example below).

SELECT 2 / (1/3 )

When and where you do your data type conversions within


calculations will also affect the results, as shown in the following
examples:

SELECT 2/3 AS "Result 1";

SELECT CONVERT(DECIMAL(6,2),2/3) AS "Result 2";

SELECT CONVERT(DECIMAL(6,2),2) / CONVERT(DECIMAL(6,2),3) AS "Result


3";

SELECT CONVERT(DECIMAL(6,2),
CONVERT(DECIMAL(6,2),2) / CONVERT(DECIMAL(6,2),3))
AS "Result 4";
The GROUP BY clause
As you learned in the previous lesson, ORDER BY is used to sort
data in ascending or descending order. GROUP BY, on the other
hand, is typically used with aggregate functions (such as COUNT,
AVG, MIN, MAX) to group identical data. This is best explained by
comparing the following two examples.

To find the number of titles for each publisher, you’d have to run the
following query once for each publisher:

SELECT COUNT(*) AS Title_count


FROM dbo.titles
WHERE pub_id = '1389';

The GROUP BY clause allows you to retrieve the number of titles for
each publisher in one query:

SELECT pub_id, COUNT(*) AS Title_count


FROM dbo.titles
GROUP BY pub_id;

NOTE:
Unlike the ORDER BY clause, a column name specified in the
SELECT statement that’s not part of an aggregate function must
appear in a GROUP BY clause.

To display the number of publishers in each country:

SELECT country, COUNT(country) AS "# Publishers"


FROM dbo.publishers
GROUP BY country;

To display the minimum and maximum prices by publisher:

SELECT pub_id AS "Publisher ID",


MIN(price) AS "Min. Price",
MAX(price) AS "Max. Price"
FROM dbo.titles
GROUP BY pub_id;

To display the number of books sold at each price:

SELECT price, COUNT(price) AS "Books listed at price"


FROM dbo.titles
GROUP BY price;
Publishers often pay authors a different royalty percentage based on
the number of books sold. Using the dbo.roysched table, the
average amount paid in royalties per book:

SELECT title_id,
CAST(
(AVG(hirange)-AVG(lorange)) * AVG(royalty*.01)
AS MONEY) AS "Avg. paid in royalties"
FROM dbo.roysched
GROUP BY title_id;

Calculations performed in the above query:

1. Subtracted the average lorange from the average hirange to get


the total average range (since we’re grouping by title_id, this
calculates all averages per book, not across all rows).
2. Found the average of all the royalties for that book, expressed
as a percentage.
3. Multiplied the results of #1 by #2.
4. Converted the result to data type MONEY.
The HAVING clause
As you saw in the previous lesson, the WHERE clause is used to
filter data. You can filter grouped data also, but because the WHERE
clause filters rows, it will not work on groups. The HAVING clause
used with GROUP BY functions as a filter.

To exclude books having a NULL value in the price column, modify


the last query in the previous section as follows:

SELECT price,
COUNT(price) AS "#@ at this price"
FROM dbo.titles
GROUP BY price
HAVING price IS NOT NULL;

The following query displays all prices that have more than one title
sold at that price:

SELECT price, COUNT(price) AS "# Books at this price"


FROM dbo.titles
GROUP BY price
HAVING COUNT(price) > 1;
To retrieve the number of titles for all publishers having more than 5
titles and sort in ascending order by the title count, execute the
following query:

SELECT pub_id,
COUNT(pub_id) AS Title_count
FROM dbo.titles
GROUP BY pub_id
HAVING COUNT(pub_id) > 5
ORDER BY COUNT(pub_id);

Notice that there are no quotes around the column alias Title_count.
That is because you only need to include the quotes when you
embed spaces in the name (i.e. “Title count”).

To find the number of authors living in zip codes beginning with “94”,
execute the following query:

SELECT zip,
COUNT(au_id) AS Au_count_by_zip
FROM dbo.authors
GROUP BY zip
HAVING zip LIKE '94%'

The GROUP BY statement groups rows, but it doesn’t sort the


grouped. To do that you need to include an ORDER BY clause. For
example, if you want to sort the title count in ascending order, add an
ORDER BY clause as follows:

SELECT pub_id, COUNT(*) AS Title_count


FROM dbo.titles
GROUP BY pub_id
HAVING COUNT(*) > 5
ORDER BY Title_count;

To count the number of titles at each of the prices listed, ignoring


those titles with prices between 10 and 15 dollars, execute the
following query:

SELECT price, COUNT(price) AS "Num. Books"


FROM dbo.titles
GROUP BY price
HAVING NOT price BETWEEN 10 AND 15;

To find the number of titles and the sum of the year-to-date sales for
each type of book, ignoring books where the type category hasn’t yet
been decided, execute the following query:

SELECT [type] AS "Category",


SUM(ytd_sales) AS "Sales to Date",
COUNT(ytd_sales) AS "Number of Titles"
FROM dbo.titles
GROUP BY [type]
HAVING [type] <> 'UNDECIDED';
Now consider the following example, which displays all titles having
more than one author:

SELECT title, au_ord


FROM dbo.titleview
WHERE au_ord > 1;

Notice that one title “Sushi, Anyone?” is listed twice. That is because
it has three authors: au_ord valuels of 1, 2, and 3.

To find the total number of authors for titles having more than one
author, change the previous query to find the highest au_ord value
and group by title:

SELECT title, MAX(au_ord) AS "# Authors"


FROM dbo.titleview
GROUP BY title
HAVING MAX(au_ord) > 1;

Because MAX(au_ord) is a calculated value, we had to give it a


column alias (“# Authors”) in order to display a column header with
the results.
The OVER (PARTITION BY)
clause
The OVER (PARTITION BY) clause is another way to calculate
aggregate data without using a GROUP BY clause. One advantage
it has over the GROUP BY clause is that it allows you to add
columns to your SELECT statement that would normally cause an
“invalid column” error when using a GROUP BY clause. (As shown
in the third example below.)

The syntax is as follows:

SELECT
a_column_name, aggregate_function_name (a_column_name)
OVER (PARTITION BY a_column_name) AS alias

It seems a little confusing at first, but consider the following query


that uses a GROUP BY clause:

USE pubs
SELECT title_id,
SUM(qty) AS "Qty/title all stores"
FROM dbo.sales
GROUP BY title_id;
Now look at the following query that produces the same results using
an OVER (PARTITION BY) clause (the text is case-insensitive):

SELECT DISTINCT title_id,


SUM(qty) OVER (Partition By title_id) AS "Qty/title all stores"
FROM dbo.sales;

However, if you were to try to add the title to the SELECT statement
in the GROUP BY query, you would receive an error:

SELECT s.title_id, title,


SUM(qty) AS "Qty/title all stores"
FROM dbo.sales AS s, dbo.titles AS t
GROUP BY s.title_id;

The OVER (PARTITION BY) clause allows you to add the title
without an error:

SELECT DISTINCT s.title_id, title, SUM(qty) OVER (Partition By


s.title_id) AS "Qty/title all stores"
FROM dbo.sales AS s, dbo.titles AS t
WHERE s.title_id = t.title_id;
Review 2
1. Using the pubs database, query the dbo.titles table to find
the total amount of advances paid for all books. Use the alias
Total_Advances for the results column header.

2. Using the pubs database, query the dbo.titles table to find


the total number of psychology books. Use
Psychology_Count as the column header for the results.

3. Using the pubs database, query the dbo.titles table to list the
book type and the number of publishers that carry each type
of book, ignoring any title with the type UNDECIDED. Use the
column alias “Num. pubs publishing this type” for your
calculated value.

4. Using the pubs database, query the dbo.authors table to list


the state and the number of authors living in each state. Use
the alias “Authors per state” for the results column. (HINT:
You are counting the number of times each state appears in
the table.)
5. Using the pubs database, query the dbo.titleauthor table to
list the author’s ID and the number of titles for each author
having more than one book title associated with his or her
name. Use the alias Title_count for the results column.
HINTS:
Use GROUP BY and the dbo.titleauthor table.
We know we need author info (au_id) and that we’re
counting titles (title_id). But we don’t just want a general
count of all titles. We want the number of titles grouped
by author. Additionally, we only want to display a record
if the number of titles for that author is greater than 1.

6. Using the pubs database, query the dbo.titles table to list


each publisher’s ID and the number of different types of
books it sells. (HINT: you want to count distinct values.) Use
the alias “Types of book sold” for the results column. Save
this query. You will modify it in Exercise #7.

7. Modify the query you created in Exercise 6 to display only


publishers who publish more than 2 types of books.
8. Query the dbo.sales table in the pubs database using an
OVER (PARTITION BY) clause. You want to display each
order number, and sum of all books ordered for that order
number. Do not repeat order numbers in the results.
LESSON 3
In this lesson you will learn about:

Querying multiple tables using joins


implicit inner joins
inner joins
outer joins
Concatenating values from two or more columns
Retrieving data from multiple
tables
In the previous sections, each of your queries involved a single table.
But sometimes you need information from more than one table.

Suppose you want to display every title and its publisher. The
dbo.publishers table contains both the publisher’s ID and name, but
no title information. The dbo.titles table contains the book titles and
the publisher’s ID, but not the name of the publisher. Therefore,
you’ll need to query both tables to get all the information you need.

We could query the two tables as follows:

SELECT title, pub_name AS Publisher


FROM dbo.publishers, dbo.titles
WHERE dbo.publishers.pub_id = dbo.titles.pub_id
ORDER BY pub_name, title;

However, it would require less typing to assign an alias to each of


the table names as follows:

SELECT title, pub_name AS Publisher


FROM dbo.publishers AS p, dbo.titles AS t
WHERE p.pub_id = t.pub_id
ORDER BY pub_name, title;
The text “dbo.publishers AS p” tells SQL to use the character “p”
instead of the full text “dbo.publishers” every time you need to
identify the table a column belongs to. Without table aliases, you’d
have to write out the full table name each time.

Let’s analyze the above query line-by-line. First we SELECT the


columns we want to display (pub_name and title). Because the
column name title exists only in the dbo.titles table, and the column
name pub_name exists only in the dbo.publishers table, you do not
have to tell SQL in which table to search for those columns.

Next we specify which tables we’re querying FROM (dbo.publishers


and dbo.titles).

The WHERE clause specifies which column we’re using to join the
tables (see also Inner Joins). In this example we’re matching the
pub_id in both tables, so only titles in the dbo.titles table which have
publisher IDs matching the publisher IDs in the dbo.publishers table
will be displayed.
Only three publisher IDs in the dbo.titles table match publisher IDs in
the dbo.publishers table, so results for only those three publishers
will be displayed.

But what if you also wanted to list the publisher’s ID along with the
publisher’s name? You might think the following query would work:
SELECT title, pub_name, pub_id
FROM dbo.publishers AS p, dbo.titles AS t
WHERE p.pub_id = t.pub_id
ORDER BY title, pub_name;
Unfortunately, executing the above query results in the following
error:
Msg 209, Level 16, State 1, Line 1
Ambiguous column name 'pub_id'.
Do you see why the above query generates that error? Because
there is a pub_id in both tables, the DBMS doesn’t know which
pub_id you’re referring to. We didn’t have that problem with the first
query, because the columns “pub_name” and “title” were unique to
each table. If you select identical column names that exist in more
than one of the tables you’re querying, then you must indicate which
table you’re pulling that column from. Modify the above query as
follows to get the query to execute correctly.

SELECT title, pub_name, t.pub_id


FROM dbo.publishers AS p, dbo.titles AS t
WHERE p.pub_id = t.pub_id
ORDER BY pub_name, title;

Now let’s look at a query that involves retrieving information from


three tables. It’s a little more complex, but it follows the same format
as the example using two tables.

Suppose you want a listing of all authors and the titles of their books.
The dbo.authors table contains author IDs and author names. The
dbo.titles table contains title IDs and titles. But neither table tells you
which author wrote which book. The table that links the two is the
dbo.titleauthor table, which contains both the author ID and the title
ID, but no author names or title names. To list all titles written by a
particular author, you will need to query all three tables.

First let’s take a look at the dbo.titleauthor table (the first 5 records
are shown to the right). This table contains an author ID and a title
ID, but no names or titles.
SELECT * FROM dbo.titleauthor;

Now look at the following query:

SELECT au_lname, au_fname, title


FROM dbo.authors AS a,
dbo.titles AS t,
dbo.titleauthor AS ta
WHERE ta.title_id = t.title_id
AND ta.au_id = a.au_id
ORDER BY au_lname, au_fname, title;
This query is an implicit Inner Join. The query checks each record of
the dbo.authors table and looks for a matching au_id in the
dbo.titleauthor table. If it finds a match, it will look at the
corresponding title_id in that record of the dbo.titleauthor table, then
search the dbo.titles table for a matching title_id. Even if it finds a
matching au_id in the dbo.authors and dbo.titleauthor tables, it will
not return a result unless the title_id in the dbo.titleauthor table also
exists in the dbo.titles table.

Inner Joins
In the first example in this lesson, you were actually joining two
tables by testing whether they had matching pub_id values. This
type of query is also known as an implicit Inner Join. It returns only
rows that both tables have in common.

Original query (implicit Inner Join):

SELECT title, pub_name


FROM dbo.publishers AS p, dbo.titles AS t
WHERE p.pub_id = t.pub_id
ORDER BY pub_name, title;
Modified query (explicit Inner Join or simply Inner Join):

SELECT title, pub_name


FROM dbo.publishers AS p INNER JOIN dbo.titles AS t
ON p.pub_id = t.pub_id
ORDER BY pub_name, title;

In the above case, instead of a WHERE clause, the ON clause


specifies the relationship between the two tables. Both produce the
same 18 records as a result:

The following example displays the publisher's name, title, type of


book, and the advance paid for each book of type popular_comp
where the advance is not null:

USE pubs
SELECT pub_name, title, [type], advance
FROM dbo.titles AS t INNER JOIN dbo.publishers AS p
ON t.pub_id = p.pub_id
AND [type] = 'popular_comp'
AND advance IS NOT NULL;

The following example displays the title and quantity of that title sold
at each store:

USE pubs
SELECT stor_id, title, qty
FROM dbo.sales AS s INNER JOIN dbo.titles AS t
ON s.title_id = t.title_id
ORDER BY title;

The first 10 of 21 resulting records:

In a previous example, we queried three tables using an implicit


Inner Join:

SELECT au_lname, au_fname, title


FROM dbo.authors AS a,
dbo.titles AS t,
dbo.titleauthor AS ta
WHERE ta.title_id = t.title_id
AND ta.au_id = a.au_id
ORDER BY au_lname, au_fname, title;

The following query produces the same results with an explicit Inner
Join:
SELECT au_lname, au_fname, title
FROM dbo.authors AS a
INNER JOIN dbo.titleauthor AS ta ON ta.au_id = a.au_id
INNER JOIN dbo.titles AS t ON ta.title_id = t.title_id
ORDER BY au_lname, au_fname, title;

Notice that, with the explicit Inner Joins of more than two tables, the
order of the joins is important. The table linking the other tables must
either come first or between the other tables. In this example, the
dbo.titleauthor table must be joined to the either the dbo.authors or
dbo.titles table before you can join the remaining table. For example,
the following are acceptable sequences for the joins:

FROM dbo.titleauthor AS ta
INNER JOIN dbo.authors AS a ON ta.au_id = a.au_id
INNER JOIN dbo.titles AS t ON ta.title_id = t.title_id

FROM dbo.titles AS t
INNER JOIN dbo.titleauthor AS ta ON ta.title_id = t.title_id
INNER JOIN dbo.authors AS a ON ta.au_id = a.au_id

But the following query produces an error because you haven’t yet
indicated you are querying the dbo.titleauthor table:

USE pubs
SELECT au_lname, au_fname, title
FROM dbo.authors AS a
INNER JOIN dbo.titles AS t ON ta.title_id = t.title_id
INNER JOIN dbo.titleauthor AS ta ON ta.au_id = a.au_id

The following example uses an Inner Join on more than two tables:

SELECT ta.title_id AS "Title ID",


COUNT(a.au_id) AS "Number Coauthors"
FROM dbo.titleauthor as ta
INNER JOIN dbo.authors AS a
ON a.au_id = ta.au_id
INNER JOIN dbo.titles AS t
ON t.title_id = ta.title_id
GROUP BY ta.title_id
HAVING COUNT(a.au_id) > 1;

The above query lists the IDs of all titles having more than one
author.

You can also combine joins with the GROUP BY clause. In the
following example, displays the total number of each book sold,
summed across all stores:

USE pubs
SELECT title, SUM(qty) AS "Total Sold"
FROM dbo.sales AS s INNER JOIN dbo.titles AS t
ON s.title_id = t.title_id
GROUP BY title
ORDER BY title;
Outer Joins
The difference between an Inner Join and an Outer Join basically
boils down to the fact that an Inner Join returns only rows common to
both tables, whereas an Outer Join can return rows unique to either
the first table listed (LEFT OUTER JOIN) or the second table listed
(RIGHT OUTER JOIN).

For example, the previous Inner Join on the dbo.publishers and


dbo.titles tables displayed 18 rows. In each row a publisher’s name
and a corresponding title were listed. But there are actually 5 rows
that weren’t displayed because there are 5 publishers with no title
information associated with them. (Remember, a column with no
data entered contains a NULL value, and NULL values cannot be
matched with each other.) There were publisher names for each of
those 5 rows, but no matching title, so an Inner Join cannot display
those rows. An Outer Join can, however, as in the following example.

SELECT pub_name, title


FROM dbo.publishers LEFT OUTER JOIN dbo.titles
ON dbo.publishers.pub_id = dbo.titles.pub_id
ORDER BY pub_name, title;
As you can see, the outer join displayed the additional 5 rows
containing NULL values.

LEFT and RIGHT keywords


In the above example, we used LEFT OUTER JOIN to relate the two
tables. The LEFT keyword will display all rows for the table to the left
(dbo.publishers) whether or not there are matching rows in the table
to the right of the Outer Join (dbo.titles). The RIGHT keyword does
the opposite: it says to include all rows from the table on the right,
whether or not there are matching rows in the table on the left.

The RIGHT OUTER JOIN below displays the same 18 rows as the
INNER JOIN because there are no titles in the title table that
correspond to those 5 publishers without titles.

SELECT pub_name, title


FROM dbo.publishers RIGHT OUTER JOIN dbo.titles
ON dbo.publishers.pub_id = dbo.titles.pub_id
ORDER BY pub_name, title;

In the following queries, the INNER JOIN, LEFT OUTER JOIN, and
RIGHT OUTER JOIN all produce different results:

SELECT au_id, stor_id


FROM dbo.authors AS a INNER JOIN dbo.stores AS s
ON a.[state] = s.[state]
ORDER BY title;
SELECT au_id, stor_id
FROM dbo.authors AS a LEFT OUTER JOIN dbo.stores AS s
ON a.[state] = s.[state]
ORDER BY title;
SELECT au_id, stor_id
FROM dbo.authors AS a RIGHT OUTER JOIN dbo.stores AS s
ON a.[state] = s.[state]
ORDER BY title;

The INNER JOIN produces 46 records: only those records where


there is a matching state in both the authors and stores table
(includes authors and stores only if the store is in one of the states in
which an author lives or an author lives in one of the states
containing a store).

The LEFT OUTER JOIN produces 53 records: all records in the


authors table, even if there is no matching state in the stores table
(includes authors in states other than where a store is).

The RIGHT OUTER JOIN produces 48 records: all records in the


stores table, even if there is no matching state in the authors table
(includes stores in states other than where an author lives).

Self Joins
A self-join joins a table to itself and compares values in one or more
columns in the table. For example, to display all authors who live in
the same city and state as the author with the author_id 213-46-8915
(last name “Green”), execute the following query. (Use the author’s
unique ID instead of his last name, just in case there is more than
one author with the same last name.)

SELECT a1.au_lname, a1.au_fname, a1.city, a1.[state]


FROM dbo.authors AS a1
INNER JOIN dbo.authors AS a2
ON a1.city = a2.city
WHERE a2.au_id = '213-46-8915'
ORDER BY a1.city, a1.au_lname, a1.au_fname;

You can also combine a self-join with either an inner or outer join.
The following example finds all titles having the same type as “The
Busy Executive’s Database Guide”, where the title has more than
one author:
USE pubs
SELECT t1.title, t1.[type]
FROM dbo.titles AS t1 INNER JOIN dbo.titles AS t2
ON t1.[type] = t2.[type]
INNER JOIN dbo.titleview AS tv
ON t1.title = tv.title
WHERE t2.title = 'The Busy Executive''s Database Guide'
AND au_ord > 1
ORDER BY title;

Notice the two successive single quotes ( '' ) in the line


WHERE t2.title = 'The Busy Executive''s Database Guide'

Using two single quotes in a row tells SQL to treat these two quotes
as one. This is called “escaping” a character. Otherwise, because
we’re using a single quote to contain the title string, if we used one
single quote for the apostrophe, then SQL would interpret the line as
follows, producing an error.
Concatenating Strings
Strings are simply several individual characters “strung” together,
hence the word “string”. While we may think of strings and
characters as the same thing, to a computer they are represented
quite differently. That is, the letter a defined as the data type CHAR
is not the same to a computer as the letter a defined as a VARCHAR
data type.

To concatenate strings simply means to connect two or more strings


together as one string. For example, in the author table there are
separate columns for an author’s first and last names. If you want to
display both names in one column, youcan connect the two columns
in a SELECT statement. (The ' ' in the SELECT statement below
inserts a space between the names.)

SELECT au_fname + ' ' + au_lname AS "Author", title


FROM dbo.authors AS a,
dbo.titles AS t,
dbo.titleauthor AS ta
WHERE ta.title_id = t.title_id
AND ta.au_id = a.au_id
ORDER BY title;
Review 3
1. Use an explicit Inner Join to query the dbo.titles and
dbo.publishers tables from the pubs database in order to
display the publisher’s name, the title, and the publication
date for each title. Use table aliases and sort the results first
by publisher name then by title.
HINT: To figure out what column to use when comparing tables,
search for what is probably the one column name they have in
common.

2. Modify the following query to change the display format of


“Firstname Lastname” to “Lastname, Firstname” and give the
result column the alias “Author”.
SELECT au_fname + ' ' + au_lname AS "Author", title
FROM dbo.authors AS a,
dbo.titles AS t,
dbo.titleauthor AS ta
WHERE ta.title_id = t.title_id
AND ta.au_id = a.au_id
ORDER BY title;
The modified query will display the following 25 records:

3. Using an implicit Inner Join, query the dbo.publishers and


dbo.titles tables in the pubs database to list all publishers
who pay their authors a royalty higher than 10 percent (the
percentages are listed as whole numbers in the table).

4. Using either an implicit Inner Join or an explicit Inner Join,


query the dbo.publishers and dbo.titles tables in the pubs
database to list all publishers who pay their authors a royalty
higher than 10 percent, but list each publisher’s name only
once. Use the column alias “Pubs with high royalty” for the
publisher name column.
HINT: Use DISTINCT.

5. Using an explicit Inner Join to query the dbo.titles and


dbo.roysched tables in the pubs database, display all titles
and the average “lorange” and “hirange” associated with each.
Group by title and use the following aliases:
Column Alias
title Book Title
average lorangeAverage Low
average hirangeAverage High

6. You will query the dbo.titles and dbo.roysched tables in for


this exercise. Use an Outer Join to display titles and
corresponding royalties from the dbo.titles table even when
there is no matching title_id in the dbo.roysched table. Be
sure to list each title only once.
7. Using an implicit Inner Join, query the dbo.authors, dbo.titles,
and dbo.titleauthors tables to list all authors (last name only),
their book titles, and the royalty they were paid for each title,
only if the royalty is not NULL. Use aliases for the table
names, and sort the results in ascending order by author’s
last name, then by title.

8. Write two queries joining the dbo.titles with the dbo.sales


tables to display the title, qty and stor_id columns. The first
query will display the columns, even if there are no matching
title IDs in the dbo.sales table. The second query will display
the columns even if there are no matching values in the
dbo.titles table. Use aliases for the table names, and sort the
results in ascending order by qty.
LESSON 4
In this lesson you will learn about:

Combining query results with the UNION statement


Using a static string value as output in a column
Using date and time conversion functions
Combining query results
The UNION operator
One way to combine the results of two or more queries is to link
them with the UNION operator. For example, suppose you want to
list all authors who do not live in CA. But you also want to list all
authors whose last name begins with “G” regardless of where they
live. If execute the queries separately, you get the following results.

Authors who do not live in CA:

SELECT au_lname, au_fname, [state]


FROM dbo.authors
WHERE state <> 'CA';

Authors whose last name begins with “G”:

SELECT au_lname, au_fname, [state]


FROM dbo.authors
WHERE au_lname LIKE 'G%';

Now combine the two queries with the UNION operator:


SELECT au_lname, au_fname, [state]
FROM dbo.authors
WHERE state <> 'CA'

UNION

SELECT au_lname, au_fname, [state]


FROM dbo.authors
WHERE au_lname LIKE 'G%';

Notice that only the final query has the ending semi-colon “;”.

Unions must have at least two SELECT statements, with the UNION
operator placed between each query, and each query must specify
the same number of column names (or functions) and data type. You
can use ORDER BY after the last query if you want to sort the
results.

Using Static Strings for Column


Values
The following example finds the names of all authors with a CA
address and all publishers with a CA address:
SELECT au_fname + ' ' + au_lname
FROM dbo.authors
WHERE [state] = 'CA'
UNION
SELECT pub_name
FROM dbo.publishers
WHERE [state] = 'CA'

Unfortunately, because we are creating a union using two different


column names, there will be no column name in the results unless
we create one using an alias:
SELECT au_fname + ' ' + au_lname
AS "Based in CA"
FROM dbo.authors
WHERE [state] = 'CA'
UNION
SELECT pub_name
FROM dbo.publishers
WHERE [state] = 'CA'
You can also add a static string as a column value. For example, to
insert a column at the beginning of each record that indicates if the
result is an author’s name or a publisher’s name, modify the above
query as follows:
SELECT 'Author' AS "Author/Publisher",
au_fname + ' ' + au_lname
AS "Based in CA"
FROM dbo.authors
WHERE [state] = 'CA'
UNION
SELECT 'Publisher', pub_name
FROM dbo.publishers
WHERE [state] = 'CA'
You only add a column alias to the first SELECT statement.
Subsequent queries added with the UNION statement will use that
alias in the column position it was assigned to.

The following example adds another UNION. This query finds the
names of all authors living in CA, all publishers with a CA address,
and all titles published in CA. A static string is used to create the first
results column, which will display the text “Author”, “Publisher”, or
“Title” depending on whether the result is from the dbo.authors,
dbo.publishers, or dbo.titles table.

SELECT 'Author' AS "Author/Publisher/Title",


au_fname + ' ' + au_lname AS "Based or Published in CA"
FROM dbo.authors
WHERE [state] = 'CA'
UNION
SELECT 'Publisher', pub_name
FROM dbo.publishers
WHERE [state] = 'CA'
UNION
SELECT 'Title', t.title
FROM dbo.titles AS t, dbo.publishers AS p
WHERE t.pub_id = p.pub_id
AND p.[state] = 'CA';
Date and Time functions and
conversions
Although every DBMS has data types for date and time, they don’t
all store those values in the same format. The examples in this
document refer to the format used by SQL Server. For a complete
list of SQL Server date and time conversion functions, see
http://technet.microsoft.com/en-us/library/ms186724.aspx.

The data types we’ll be discussing in this section are as follows:

Data Type Description


DATE Stores date
TIME Stores time
DATETIME Stores date and time to the millisecond
SMALLDATETIME Stores date and time to the minute

Date and time functions


There are several functions you can use in SQL server to retrieve
and manipulate the date and time. The functions we’ll be covering in
this section are as follows:

Function Description
GETDATE() Retrieves the current system date and time
DATEPART() Returns specified part of date

The GETDATE() function


To retrieve the current date and time (SQL Server), execute the
following query:

SELECT GETDATE();

This shows the year-month-day followed by the


hours:minutes:seconds, which translates to June 2, 2012 at
approximately 3:26 pm and 33 seconds.

The DATEPART() function


This function returns the part of the date you specify in the following
format:

DATEPART(part, date)

The “part” portion of the DATEPART function can retrieve numerous


parts of the date, but the ones we’ll be discussing are shown in the
following table:

part value
year yy
month mm
day dd
hour hh
minute mi
second ss
dayofyear dy

You can display the individual parts of the current date as follows:

SELECT GETDATE() AS "Today's Date",


DATEPART(yy, GETDATE()) AS "Year",
DATEPART(mm, GETDATE()) AS "Month",
DATEPART(dd, GETDATE()) AS "Day",
DATEPART(hh, GETDATE()) AS "Hour",
DATEPART(mi, GETDATE()) AS "Minute",
DATEPART(ss, GETDATE()) AS "Second",
DATEPART(dy, GETDATE()) AS "Days into Year";
To display specific parts of a column having the DATETIME data
type, replace the GETDATE() function in the DATEPART conversion
with the column name. For example, to retrieve all titles published in
the year 2004 from the dbo.titles table in the pubs database, execute
the following query:

SELECT title, pubdate FROM dbo.titles


WHERE DATEPART(yy, pubdate) = 1994;

To find all titles published in June, regardless of the year:

SELECT title, pubdate FROM dbo.titles


WHERE DATEPART(mm, pubdate) = 6;

To list the year, month, and day of publication in separate columns,


followed by the title and sorted in ascending order by year, month,
and title. You can accomplish this with the following query:

SELECT DATEPART(yy, pubdate) AS "Year Published",


DATEPART(mm, pubdate) AS "Month Published",
DATEPART(dd, pubdate) AS "Day Published",
title
FROM dbo.titles
ORDER BY "Year Published", "Month Published",
"Day Published",title;

Converting dates to strings


In our titles table, pubdate is the DATETIME data type. If we want to
check for specific dates, we need to convert pubdate to a string in
order to do the comparison.

Although it wasn’t mentioned in the previous section Converting Data


Types, there is an additional, optional value you can use with the
CONVERT function. The syntax is as follows:
CONVERT (resulting data type, value to be converted, style)
where style is an integer value that specifies how the expression is
to be converted. We will most often be using the datetime style 120,
which tells the CONVERT function to convert to yyyy-mm-dd
hh:mi:ss format. For a complete listing of possible style values, see
http://msdn.microsoft.com/en-us/library/ms187928.aspx.

For example, to retrieve a list of all books published between


January 1, 1994 and December 31, 2013, execute the following
query:

SELECT title AS Title,


CONVERT(CHAR(10), pubdate, 120) AS "Date Published"
FROM dbo.titles
WHERE CONVERT(CHAR(10), pubdate, 120)
BETWEEN '1994-01-01' AND '2013-12-31'
ORDER BY "Date Published";

Another way to do retrieve the same information produced by the


previous query:

SELECT title AS Title,


CONVERT(CHAR(10), pubdate, 101) AS "Date Published"
FROM dbo.titles
WHERE DATEPART(yy, pubdate) >= '1994' AND DATEPART(yy,
pubdate) <= '2013'
AND DATEPART(mm, pubdate) >= '01' AND DATEPART(mm,
pubdate) <= '12'
ORDER BY "Date Published";

If you just want to compare years, we could have selected the year
with DATEPART instead of using CONVERT.

SELECT title AS Title,


DATEPART(yy,pubdate) AS "Year Published"
FROM dbo.titles
WHERE DATEPART(yy,pubdate)
BETWEEN 1994 AND 2013
ORDER BY "Year Published";

If you just want to display the date, time of publication, along with the
time in hh:mm:ss format, execute the following query:

SELECT title AS Title,


CONVERT(DATE, pubdate) AS "Date Published",
CONVERT(TIME, pubdate) AS "Time Published",
CONVERT(VARCHAR, pubdate, 108) AS "Time as hh:mm:ss"
FROM dbo.titles;

The above query just takes the year, month, and day portions of the
DATETIME field. It does not rearrange the order in which the year,
month, and day appear. To do that we have to nest our conversions
and add the style parameter.

To convert the date and time to just the date in the formats mm-dd-yy
and mm-dd-yyyy execute the following query:

SELECT title AS Title,


CONVERT(VARCHAR(8),pubdate,10) AS "Date Published (10)",
CONVERT(VARCHAR(10),pubdate,110) AS "Date Published
(110)"
FROM dbo.titles;

To convert to mm/dd/yy format, change the 10 to 1:

SELECT title AS Title,


CONVERT(VARCHAR(8), pubdate, 1) AS "Date Published"
FROM dbo.titles;

To convert to mm/dd/yyyy format, change the 10 to 101:

SELECT title AS Title,


CONVERT(VARCHAR(10), pubdate, 101) AS "Date Published"
FROM dbo.titles;

Always, remember to make the VARCHAR length large enough to


accommodate the numbers and the separators. Using VARCHAR()
without a number uses the maximum allowed size of 255 characters.

More Date Conversion Examples


SELECT GETDATE() AS "Full Date",
CONVERT(CHAR(10),GETDATE(),120) AS "yyyy-mm-dd",
CONVERT(CHAR(12),GETDATE(),107) AS "Mon dd, yyyy";

SELECT CONVERT(VARCHAR,GETDATE(),100)
AS "dd Mon yyyy (AM/PM)",
CONVERT(VARCHAR,GETDATE(),113)
AS "dd Mon yyyy (24-hr+ms)";

SELECT title, pubdate,


DATEPART(yy,pubdate) AS "Year Only",
DATEPART(mm,pubdate) AS "Month #",
CONVERT(CHAR(3),pubdate,100) AS "Month Name"
FROM dbo.titles;
Review 4
1. Use a UNION statement to unite two queries on the dbo.titles
tables in the pubs database. The first query will display the
title, price, and advance for all titles with a price of 2.99. The
second query will display the same information for all titles
with an advance less than or equal to 5000.

2. Using the pubs database, query the dbo.employee table to


display the last name, first name, and hire date for every
employee who was hired in the year 1991, in descending
order by hire date.

3. Using the pubs database, query the dbo.employee table to


display the last name and hire date of every employee who
was hired after 1992. Display in descending order by hire
date.
4. Modify the query you created in Exercise 3 to display the hire
date column name as “Hire Date” and the hire date values in
the format mm-dd-yy.

5. Using the pubs database, query the dbo.employee table to


display the last name of each employee whose last name
begins with the letter “L” and the year that person was hired.
Use “Year Hired” as the column name for the year. Sort in
ascending order by employee’s last name.
6. Using the pubs database, query the dbo.employee table to
display the last name of every employee whose last name
begins with the letter “L”, and the hire date in three separate
columns—one for year, one for month, and one for day of
year. Use “Year Hired”, “Month Hired”, and “Days into Year
Hired”, respectively, as the column names for the date parts.
Sort in ascending order by employee’s last name.
LESSON 5
In this lesson you will learn about:

Creating a database
Creating, modifying, and deleting tables and views
Adding and updating records
Databases
When designing a database, you want to give careful consideration
to several factors, including the number of tables you will be adding,
the structure and data type of each column, and how much data will
be replicated from table to table. While repeating the same fields
across numerous tables may simplify some of your queries, too
much redundancy of data unnecessarily increases the size of the
database and makes adding and updating data more time
consuming, as identical columns in multiple tables must all be
updated.

For example, in the pubs database, there is an authors table


containing, au_fname, and au_lname, and au_id fields. The titles
table contains the title and pub_id fields.

If wanted to be able to display the author’s name and title of her


book by querying only one of the two tables, you would have to add
two more columns to the authors table: title and pubname and three
more columns to the titles table: au_fname, au_lname, pubname.
Every time you added or changed that information, you would then
have to update au_fname and au_lname in two tables instead of
one, and the same for the title field.

Because all that redundancy makes updating records far more time
consuming than it needs to be, the pubs database has a more
efficient way to handle it. There is a table named dbo.titleauthor that
contains the au_id and the title_id. By setting foreign keys in the
authors and titles tables linking to the au_id and title_id, respectively,
in the titleauthor table, you do end up having to query three tables to
get the results, but the size of each table is substantially reduced.

Creating Databases
SQL Server Management Studio makes it easy to create a database
with the default settings. In this lesson you are going to create the
Solar_System database that will be used throughout the remainder
of the book.

In the Object Explorer pane, right click on Databases, then select


“New Database...”.

When the New Database dialog opens, type Solar_System into the
Database name box, then click OK.

You will now see Solar_System listed under Databases in the Object
Explorer.

Alternately, you can use a SQL command to create the database. In


a new query window, enter the following command:

CREATE DATABASE Solar_System;

Deleting Databases
To delete a database using SQL Server Management Studio, right-
click on the name of the database, select Delete from the pop-up
menu, then click OK.

To use a SQL command instead, in a new query window enter the


following command:

DROP DATABASE <name of database>;

To confirm that the database has been removed, right-click


Databases in the Object Explorer, then select Refresh. The list is
updated to display the names of the current databases.
Tables
The information in a database is divided into separate tables with a
grid structure, each table containing related data (like a file in a file
cabinet).Querying these tables is the primary way in which we
retrieve meaningful data from a database. This section will describe
how to create, delete, and remove (drop) tables. You’ll also learn
how to insert the initial data into the table after creation and how to
add and remove records from an existing table.

Primary Keys
In order to get accurate information from your database, there has to
be some way to uniquely identify each row in a table. This is
accomplished using a “primary key.” A primary key is also a
constraint. For more information on keys and constraints, see
Constraints.

NOTE:
If you delete a record, you cannot reuse that primary key for
any other record in the table.
The value in each primary field must be unique in the table.
You cannot change the value of a primary key once it’s been
entered.
A primary key cannot be a NULL value.

Creating Tables
To create a new table, at the minimum you will need to provide the
following information:

Name of database to contain the table (USE


database_name)
A name for the table (name cannot contain spaces)
Names for each column/field in the table (name cannot
contain spaces)
The data type for each field (see Data types)
Whether or not the field can contain NULL values

Optionally, you may set the primary key at the same time you create
the table (see Primary Keys).

You use the CREATE TABLE statement with the following syntax:

CREATE TABLE table_name


(
Col. 1 name Col. 1 data type Whether or not can have null
values,
Col. 2 name Col. 2 data type Whether or not can have null
values,
Col. 3 name Col. 3 data type Whether or not can have null
values,
.
.
.
Col. N name Col. N data type Whether or not can have
null values
);

You will now use SQL Server Management Studio to create a table
called Planets with seven columns.

Once you’ve connected to the DBMS, identify the database in which


you’re going to create the table. In this case, you will be using the
Solar_System database. If the database does not already exist,
create it using the instructions in Creating Databases.

Click New Query in the toolbar, and type the following line to tell
Management Studio what database to use:

USE Solar_System

Next, enter the commands and parameters needed for creating the
table. Each of the seven fields you’ll create is assigned a data type
and either allowed or not allowed NULL values. The information for
each field is grouped together and separated by commas. (Do not
add a comma after the final column entry.)

USE Solar_System
CREATE TABLE Planets
(
PID VARCHAR(2) NOT NULL PRIMARY KEY,
Planet VARCHAR(20) NOT NULL,
Diameter_km INT NULL,
Distance_AU DECIMAL(10,2) NULL,
Distance_Mkm INT NULL,
Rotation_days DECIMAL(10,2) NULL,
Orbit_years DECIMAL(10,2) NULL,
Num_Moons INT NULL
);

After executing, right-click on Tables under the Solar_System


database in the Object Explorer window, then click Refresh to see
the new table name appear.

Expand the (+) symbol to the left of the dbo.Planets table until you
can see all the column names.
You’ll notice the name of the table is “dbo.Planets” not “Planets”.
Since a specific user was not specified as the owner of the database
at the time it was created, the owner defaulted to SQL Server’s built-
in user account “dbo” (database owner).

Inserting Rows
At this point, we’ve created the table’s structure, but the table itself is
empty of data. To add data to the table we’ll use the INSERT INTO
command.

The syntax for INSERT INTO is as follows:

INSERT INTO table_name


VALUES (values for all fields in record 1, separated by commas),
(values for all fields in record 2, separated by commas),
.
.
.
(values for all fields in record N, separated by commas),

To insert the data into our Planets table, type the following
commands into a new query window:

NOTE:
You can copy and paste the query text from this document into your
query window. Be careful when copying text across pages, however,
as you do not want to include the page footer.
INSERT INTO dbo.Planets
VALUES ('1', 'Mercury', 4878, 0.38, 58, 58.6, 0.24, 0),
('2', 'Venus', 12100, 0.72, 108, 243.0, 0.62, 0),
('3', 'Earth', 12742, 1, 150, 1, 1, 1),
('4', 'Mars', 6792, 1.52, 228, 1.03, 1.89, 2),
('5', 'Jupiter', 139822, 5.20, 778, 0.41, 11.86, 50),
('6', 'Saturn', 116464, 9.53, 1429, 0.43, 29.46, 53),
('7', 'Uranus', 50724, 19.19, 2871, 0.75, 84.01, 27),
('8', 'Neptune', 49248, 30.06, 4504, 0.67, 164.79, 13),
('9', 'Pluto', 2324, 39.5, 5906, 6.39, 248, 5);

After executing, open a new query window and execute the following
query to view the data you just inserted into the table:

SELECT * FROM dbo.Planets;

Adding Columns to a Table


Suppose when you created your table that you forgot to include a
column. You could delete the table and create it again, but SQL
gives you a way to add columns after the initial creation. For that we
use the ALTER TABLE command.

The syntax of the ALTER TABLE command is as follows:


ALTER TABLE table_name
ADD column_name data_type null_or_not_null;

Let’s add fields named Position and Density to our Planets table:

ALTER TABLE dbo.Planets


ADD Position INT NULL;
ALTER TABLE dbo.Planets
ADD Density FLOAT NULL;

By running the previous SELECT query on the Planets table, you will
see that the column has been added to the table. The values are all
NULL since we haven’t inserted the data for that column yet. For that
we’ll use the UPDATE command.

But now, after creating the column, you realize that you Position is
redundant, as the planet ID (PID) indicates the planet’s position from
the sun. Therefore, you want to remove (drop) the Position column.

Dropping a Column
To completely remove a column from a table, use the DROP
command with the ALTER TABLE command as follows:

ALTER TABLE dbo.Planets


DROP COLUMN Position;

Renaming a Column
If you want to rename a column you want to keep, use the system
stored procedure SP_RENAME. Be careful when renaming columns,
however. If other scripts or stored procedures reference the original
column name, these scripts and procedures will no longer work.

Rename the Density column you just created from Density to


Density_gpcm3 (grams per cubic centimeter, a measure of mass
per unit of volume) as follows:

USE Solar_System
GO
SP_RENAME 'dbo.Planets.Density', 'Density_gpcm3', 'COLUMN'
GO

The above procedure indicates that “Density” is the current name of


a column in the table called dbo.Planets, and that you want to
rename it to “Density_gpcm3”.

For more on stored procedures, see the section Stored Procedures.

Updating Rows
The UPDATE command is used to insert or change data in a
column. You can update all rows or specific rows. The syntax is as
follows:

UPDATE table_name
SET column_name = value WHERE condition that identifies record

To add data to the Density_gpcm3 column for every record in our


table, execute the following query:

UPDATE dbo.Planets
SET Density_gpcm3 = 5.427 WHERE Planet ='Mercury'

UPDATE dbo.Planets
SET Density_gpcm3 = 5.243 WHERE Planet ='Venus'
UPDATE dbo.Planets
SET Density_gpcm3 = 5.513 WHERE Planet ='Earth'

UPDATE dbo.Planets
SET Density_gpcm3 = 3.934 WHERE Planet ='Mars'

UPDATE dbo.Planets
SET Density_gpcm3 = 1.326 WHERE Planet ='Jupiter'

UPDATE dbo.Planets
SET Density_gpcm3 = 0.687 WHERE Planet ='Saturn'

UPDATE dbo.Planets
SET Density_gpcm3 = 1.270 WHERE Planet ='Uranus'

UPDATE dbo.Planets
SET Density_gpcm3 = 1.638 WHERE Planet ='Neptune'

UPDATE dbo.Planets
SET Density_gpcm3 = 2.050 WHERE Planet ='Pluto';

Display the data in the table and you will see the values in the
Density_gpcm3 column have been added.

Deleting Rows
You can delete the data from all rows in a table or just specific rows
with the DELETE statement. The syntax is as follows:

DELETE FROM table_name WHERE condition that identifies


record(s) to be deleted

For example, as Pluto has been demoted from a planet to a dwarf


planet, you could delete all records pertaining to Pluto from the
dbo.Planets table as shown below. But don’t delete poor Pluto yet;
we still need it for the remainder of the lessons in this book!

DELETE FROM dbo.Planets


WHERE Planet ='Pluto';

If you're deleting all rows from a table, the WHERE clause can be
omitted.

Dropping A Table
To remove a table from a database, you use the DROP statement.
This destroys the table completely. The syntax is as follows:

DROP TABLE table_name

NOTE:
Be careful about dropping tables that have dependencies (e.g.,
foreign keys, views), as this will affect those dependencies. The
easiest way to determine a table’s dependencies is to right-click on
the name of the table you wish to drop, and select View
Dependencies. You can then view a list of all objects that depend
on the table you want to drop as well as all objects on which the
table depends.
DELETE vs. DROP
Using the DELETE statement to delete all rows from a table only
removes the data contained in those rows. It empties the table but
retains the table's structure. To remove the table itself from the
database, you must use the DROP statement.

Saving a Query
For queries you will be running frequently, it's more efficient to save the
query in a file, then load that file each time you want to run the query.
Not only does this save time by not having to retype the query, but it
eliminates potential typing mistakes that could result in errors.

To save a query, first you will need to create it. You can do this using
a text-editing program like Notepad on your local computer, or you
can create the query in SQL Server Management Studio and then
save it to the location of your choice. In the following example, you'll
create and save the query from SQL Server Management Studio.

Open a new query window and type in the following query:

USE Solar_System
SELECT *
FROM dbo.Planets
ORDER BY Planet;

An asterisk (*) next to the name on the query tab indicates the query
has not yet been saved or changes have been made to it since it
was last saved.

Right-click on the tab displaying the name default query name


SQLQuery1.sql (yours may display a different name if you have
executed more than one query this session). Select "Save
SQLQuery1.sql".

When the Save File As dialog box appears, enter name and path for
the file. In this case, call it "SampleQuery.sql" and save it in the
default Projects folder. Once you've saved the file the asterisk will
disappear until you make a change.

NOTE:
All saved queries must have the extension ".sql". If your file
extensions are not displayed, you can change your current setup to
display them.
Windows XP:
Open Windows Explorer, then click Tools > Folder Options > View
Tab. In the Advanced Settings box, uncheck "Hide extensions for
known file types." (If you wish to apply this change to views of other
folders, click "Apply to All Folders" in the Folder views box.)
Windows 7:
Open Windows Explorer, then click Organize > Folder and Search
Options > View Tab. In the Advanced Settings box, uncheck "Hide
extensions for known file types." (If you wish to apply this change to
views of other folders, click "Apply to Folders" in the Folder views
box.)

Executing a Saved Query


To execute a saved query in SQL Server Management Studio, click
File > Open > File.

Select the name of the file you want to open, then click "Open."

The contents of the file will appear in the query window. Press the F5
key or click the !Execute button to run the query.

Creating and Populating a Table from


a Saved Query
The statements for creating a table and inserting records can consist
of just a few lines to hundreds or thousands of lines. For the
lengthier examples in these lessons, files are provided that contain
the code for inserting records. See Appendix D: Lesson Files.

Create and Populate Moons Table


In this example, you will use a saved query file to create and
populate the Moons table.

In SQL Server Management Studio, click File > Open > File, select
the file "CreateAndPopulateMoonsTable.sql", then click "Open."
Once the contents of the file appear in the query window, execute
the query.

You should receive the message: (151 row(s) affected)


If you run the query SELECT * FROM dbo.Moons, you will notice
that Jupiter's moon Cyllene has two NULL values. Using what you
learned about updating rows, you'll now enter the missing data for
Diameter_km and Distance_km.
UPDATE dbo.Moons
SET Diameter_km = 2,
Distance_km = 23800000
WHERE Moon ='Cyllene';

Since the fourth and fifth moons of Pluto that were discovered have
now been named, you’ll want to update that information in the
dbo.Moons table also:

UPDATE dbo.Moons
SET Moon = 'Kerberos'
WHERE Moon ='P4';

UPDATE dbo.Moons
SET Moon = 'Styx'
WHERE Moon ='P5';

Create and Populate Planet_Masses Table


To create and insert values into the Planet_Masses table, you can
either type the information into the query window directly, or load
then execute the file CreateAndPopulatePlanetMassesTable.sql.

USE Solar_System
CREATE TABLE Planet_Masses
(
PID VARCHAR(2)NOT NULL PRIMARY KEY,
Base_kg FLOAT NOT NULL,
Exponent FLOAT NOT NULL,
Gravity_mps2 FLOAT NULL
);
INSERT INTO dbo.Planet_Masses
VALUES ('1', 3.3010, 23, 3.7),
('2', 4.8673, 24, 8.87),
('3', 5.9722, 24, 9.80665),
('4', 6.4169, 23, 3.71),
('5', 1.8981, 27, 24.79),
('6', 5.6832, 26, 10.4),
('7', 8.6810, 25, 8.87),
('8', 1.0241, 26, 11.15),
('9', 1.3090, 22, 0.66);

Alter and Update Planet_Masses Table


Now that you've created the table, add a column named Weight_lb
by executing the following query:

ALTER TABLE dbo.Planet_Masses


ADD Weight_lb INT NULL;

To enter values for Weight_lb, execute the following query or load


and execute the file UpdateDataWeightCol.sql.
UPDATE dbo.Planet_Masses
SET Weight_lb = 38 WHERE PID ='1'
UPDATE dbo.Planet_Masses
SET Weight_lb = 91 WHERE PID ='2'
UPDATE dbo.Planet_Masses
SET Weight_lb = 100 WHERE PID ='3'
UPDATE dbo.Planet_Masses
SET Weight_lb = 38 WHERE PID ='4'
UPDATE dbo.Planet_Masses
SET Weight_lb = 253 WHERE PID ='5'
UPDATE dbo.Planet_Masses
SET Weight_lb = 107 WHERE PID ='6'
UPDATE dbo.Planet_Masses
SET Weight_lb = 91 WHERE PID ='7'
UPDATE dbo.Planet_Masses
SET Weight_lb = 114 WHERE PID ='8'
UPDATE dbo.Planet_Masses
SET Weight_lb = 7 WHERE PID ='9';

Now that you’ve created a few tables, let's review some basic query
structures.

To display all columns in a table:

SELECT * FROM table_name;

The following example displays all columns in the Planets table,


sorted in ascending order by Position:
SELECT * FROM dbo.Planets
ORDER BY Density_gpcm3;
To query multiple tables:

SELECT t1.columnX, t2.columnY


FROM table1 AS t1, table2 AS t2
WHERE t1.column_name = t2.column_name;

The following example displays the planet name and moon name for
each planet having a 5th moon:
SELECT p.Planet, m.Moon AS "5th Moon"
FROM dbo.Planets AS p, dbo.Moons AS m
WHERE p.PID = m.PID AND m.Discovery_Id = 5
;

Using the UNION statement:

Query # 1 UNION Query #2

The following example displays any planet or moon having a


diameter between 4000 and 6000 km, inclusive:

SELECT Moon AS "Has 4000 - 6000 km diameter"


FROM dbo.Moons
WHERE Diameter_km BETWEEN 4000 AND 6000

UNION

SELECT Planet
FROM dbo.Planets
WHERE Diameter_km BETWEEN 4000 AND 6000;
Views
A view can be thought of as a virtual table. It looks like a table when
referenced, but rather than stored as a set of values like a table, it's
stored as a SELECT statement which obtains values only when
referenced by a SQL statement. At this point, it manifests itself as a
physical table, which only exists until the referencing statement has
completed execution. A view uses column names just like a table,
and can be created from one or more tables.

Views can reduce the amount of storage space that would otherwise
be allocated to redundant data. They can also increase security by
filtering out data from the underlying tables. That is, you can create a
view to contain only those columns you want visible to users (for
example, displaying an employee's name but not her social security
number).

System Views
Each database has a set of Views that are automatically created with
the database and updated as content is changed. A partial listing of
the System Views associated with the Solar_System database is
shown below:

To see a list of all column names in the Solar_System database


along with the name of the corresponding table, data type, and
whether the field can contain a null value, execute the following
query:

USE Solar_System
SELECT TABLE_NAME, COLUMN_NAME, DATA_TYPE,
IS_NULLABLE, COLLATION_NAME
FROM INFORMATION_SCHEMA.COLUMNS
ORDER BY TABLE_NAME;

A partial list of the results is displayed below:

Creating a View
To create a View, you use the CREATE VIEW statement as follows:

CREATE VIEW database_name


AS
SELECT column names separated by commas
FROM table_name
GO

Create a view called Planet_Radii:

CREATE VIEW dbo.Planet_Radii


AS
SELECT PID, Diameter_km / 2 AS Radius
FROM dbo.Planets
GO

Querying a View
You query a view in the same way that you query a table. For
example, to list each planet and its radius when the radius is greater
than 5000, execute the following query:
SELECT * FROM dbo.Planet_Radii
WHERE Radius > 5000;

Unfortunately, the above query only gives us the planet’s ID. To get
the planet’s name, you’ll also need to query the dbo.Planets table.

SELECT Planet, Radius


FROM dbo.Planets AS p, dbo.Planet_Radii AS pr
WHERE p.PID = pr.PID AND Radius > 5000;

We'll come back to views later when we create columns based on


calculations.
Review 5
Let’s review what you’ve learned so far by creating some additional
queries. Answers to all review exercises can be found in Appendix
C: Solutions to Review Exercises. (No peeking at the answers before
you try the exercises. )

1. Create a table named Astronomers with the following column


names and data types. In the same query insert the values
into the table. (Do not delete this table, you will be using to it
throughout this review.) Save this query as
Review5Exercise1.sql; you will need to execute it again
in Exercise 7 of this review and in Exercise 1 of Review 6.
Fields
AsId—This is the Astronomer ID. Data type: CHAR(2). Use as
the primary key.
Fname—Astronomer’s first name. Data type: VARCHAR(20). This
field may contain a NULL value.
Lname—Astronomer’s last name. Same data type as Fname. This
field cannot contain a NULL value.
Born—Date the astronomer was born. Data type: DATE. This field
may contain a NULL value.
Died—Date the astronomer died. Data type: DATE. This field may
contain a NULL value.

Values (Enter dates as a string in the format YYYY-MM-DD.)


William Herschel, born November 15, 1738, died August 25,
1822.
Clyde Tombaugh, born February 4, 1906, died January 17, 1997.
2. Add a column named Object_Discovered to the Astronomer’s
table. Data type: VARCHAR(20). This field may contain a
NULL value.

3. Update the values in the new Object_Discovered column.


Value for William Herschel: Uranus
Value for Clyde Tombaugh: Pluto

4. Add a record to Astronomer’s table. (Remember to enter


dates as a string in the format YYYY-MM-DD.)
Values
PID—3
Fname—Galileo
Lname—Galilei
Born—February 15, 1564
Died—January 8, 1642
Object_Discovered—Io

5. Create a View of the Astronomer’s table you created in


Exercise 1. The view should contain the AsId, Fname, and
Lname columns from the table.

6. Delete from the Astronomers table the record you just added.
7. Drop the Astronomers table from the database.
8. Re-create the Astronomers table from the query you saved in
Exercise 1.
9. Create a View named dbo.Review5_Exercise9 that contains
the name of each planet from dbo.Planets that matches a
planet name in dbo.Moons and the name of each moon that
has a diameter greater than 500.
The resulting View will contain the 16 records shown below:
LESSON 6
In this lesson you will learn about:

Strings
Concatenation
Case-sensitivity
Substrings
Converting from one data type to another
Using data from more than one column in calculations
Strings
Just as a word in alphabet-based languages is made up of one or
more individual letters, a string (also referred to as a "literal") is made
up of a series of one or more characters. For example, the English
word "West" consists of the letters "W", "e", "s", and "t." In SQL
terminology, "West" is a string containing four characters. That is,
"West" is a string whose length is four characters. A string may
contain any character that is part of the character set used by SQL
(more about character sets in the next section).

NOTE:
Keep in mind that strings and integers are stored differently. The
string "42" is different than the number 42. For example, if you have a
column called Number with the data type VARCHAR(2), then Number
is a string. But if Number is defined with the data type INT, then
Number is an integer value. This is an important distinction, because
you can only perform arithmetic operations on a numerical data type,
not on a string. You should also be aware that the "+" operator
concatenates strings (see the following section), but adds numbers.

Collation
Collation defines the character set to use along with the sort order for
searches. You can set it at the server, database, table, and column
levels, depending on your access permissions. The COLLATE clause
is used to set the, often in conjunction with a WHERE clause to
specify whether to use case-sensitivity in a search.

The syntax for the COLLATE clause is:

COLLATE collation_name

For a list of valid collation names, execute the following query using
the system function fn_helpcollations():

SELECT name,description
FROM fn_helpcollations()
WHERE name LIKE 'SQL_Latin%';

A partial list follows:

In the example from the previous section, we used the following


COLLATE clause when searching for the value ' Earth ' :

COLLATE SQL_Latin1_General_CP1_CS_AS

Let’s break this down.


SQL_Latin1_General_CP1—Default English character set for United
States English.
CS—Case-sensitive.
AS—Accent sensitive.

To change to a case-insensitive search, simple change the CS to CI


in the collation name.

Case-Sensitivity
Uppercase and lowercase characters are stored with different values
in a computer, so the character "A", for example, is not the same as
the character "a". When searching for strings, this is an important
distinction to keep in mind. If your search term is "A", a case-sensitive
search will return all "A" characters but will not return any "a"
characters. A case-insensitive search will return both "A" and "a"
characters.

Microsoft SQL Server is usually set up to use case-insensitive


searches, but you can check that specifically by running the system
procedure sp_helpsort (EXEC sp_helpsort). We will discuss system
procedures in more detail in the section Stored Procedures.
If Microsoft SQL Server's default search is case-insensitive, as
indicated above, but you want to change it to case-sensitive for a
particular query, you can add a COLLATE clause. (The COLLATE
clause is discussed in the following section.)

Case-insensitive query:

SELECT Planet FROM dbo.Planets


WHERE Planet ='EARTH'
OR Planet ='Mars'
OR Planet ='JUPITER';

Case-sensitive query applied to search for "EARTH" only:

SELECT Planet FROM dbo.Planets


WHERE Planet ='EARTH' COLLATE
SQL_Latin1_General_CP1_CS_AS
OR Planet ='Mars'
OR Planet ='JUPITER';

Converting Case
There may be times, either when conducting a case-sensitive search
or setting a constraint, that you may want to change the case of a
string to all Upper or all Lower characters. To do that, you use the
UPPER() and LOWER() functions. Be aware that the conversion is
only for comparison purposes. The data itself is not changed.

UPPER() and LOWER()


The following examples use UPPER() and LOWER() in queries. The
first example selects and converts planet names to uppercase for
display. The second example displays all planet names that, when
converted to lowercase, equal "earth". This type of query is useful if
you aren't sure that data in a column was entered in a consistent
format.

Example #1: This example selects and converts planet names to


uppercase for display.
Example #2: This example displays all planet names that, when
converted to lowercase, equal "earth". This type of query is useful if
you aren't sure that data in a column was entered in a consistent
format.
Substrings
A substring is a subset of characters within a string. Substrings can
be used for extracting and sorting data. The syntax for the
SUBSTRING function is slightly different depending on which system
you're using. Microsoft SQL Server uses the following:

SUBSTRING(string, start, length)

where string is value in column, start is the starting position within the
string, and length is the number of characters to extract from the
starting position.

Substrings will be covered in more detail in the section Checking for


Substrings.

You can find the length of any string using the LEN() function, whose
syntax is as follows:

SELECT Planet,LEN(Planet) AS "Num_Chars"


FROM dbo.Planets;

NOTE:
LEN( ) vs. LENGTH( )
The LEN( ) function is specific to Microsoft SQL Server. Oracle and
MySQL use the LENGTH( ) function.

To display every planet in the Planets table whose name begins with
the letter "M", execute the following query you can use LIKE:
USE Solar_System
SELECT Planet, PID AS "Position"
FROM dbo.Planets
WHERE Planet LIKE 'M%';

Alternately, you can use the SUBSTRING( ) function:

USE Solar_System
SELECT Planet, PID AS "Position"
FROM dbo.Planets
WHERE SUBSTRING(Planet, 1, 1) = 'M';

If, however, you wanted to display every planet whose name ends in
"us", you can also use LIKE or SUBSTRING( ).

SELECT Planet, PID AS "Position"


FROM dbo.Planets
WHERE Planet LIKE '%us';

USE Solar_System
SELECT Planet, PID AS "Position"
FROM dbo.Planets
WHERE SUBSTRING(Planet, LEN(Planet)-1, 2) = 'us';

An alternative for specifying characters at the beginning or end of a


string is to use SQL Server's LEFT and RIGHT functions as
described in the next section.

LEFT( ) and RIGHT( ) Functions


The LEFT( ) and RIGHT( ) functions used with SQL Server enable
you to retrieve the first X characters to the left of a string and the last
X characters to the right, respectively.
The syntax for the LEFT function is as follows:
LEFT(column_name, num_chars)
Example:
SELECT Planet FROM dbo.Planets
WHERE LEFT(Planet, 1) = 'M';

The syntax for the RIGHT function is a follows:


RIGHT(column_name, num_chars)
Example:
SELECT Planet FROM dbo.Planets
WHERE RIGHT(Planet, 2) = 'us';

CHARINDEX( )
There may be times you want to select or search for data based on
where a character is within a string. The Microsoft SQL Server
CHARINDEX( ) function, used in SELECT and WHERE statements,
allows you to do just that. The syntax for the function is as follows:

CHARINDEX(character_to_locate, string_to_search)

A query using either the CHARINDEX( )function or the LIKE operator


will find the same records, but the LIKE operator cannot list the
location of the character within the string. If displaying the character
position is not an issue, then you can use whichever query method
you prefer. Be aware that, although the fields we'll be searching in our
examples are small enough to use either method, the use of the LIKE
operator can become complicated when searching longer strings.

For example, to find all planets that have an “r” as the third character
in their name:
SELECT Planet
FROM dbo.Planets
WHERE CHARINDEX('r',Planet) = 3;
To use LIKE, you would rewrite the query as follows: (IMPORTANT:
the __ in the WHERE statement below is two successive underscore
characters.):
SELECT Planet
FROM dbo.Planets
WHERE Planet LIKE '__r%';
For the following example, you'll need to add a column called "Class"
to the Planets table. The Class field will be a two character
designation. The first character will be either a "G" or an "S",
representing Gas and Solid, respectively. The second character will
be either an "L" or an "M", representing an axial tilt of less than 90
degrees or more than 90 degrees, respectively.

USE Solar_System
ALTER TABLE dbo.Planets
ADD Class CHAR(2) NULL;

Now update the values in that column:

USE Solar_System
UPDATE dbo.Planets
SET Class = 'SL' WHERE Planet = 'Mercury'

UPDATE dbo.Planets
SET Class = 'SM' WHERE Planet = 'Venus'

UPDATE dbo.Planets
SET Class = 'SL' WHERE Planet = 'Earth'

UPDATE dbo.Planets
SET Class = 'SL' WHERE Planet = 'Mars'

UPDATE dbo.Planets
SET Class = 'GL' WHERE Planet ='Jupiter'

UPDATE dbo.Planets
SET Class = 'GL' WHERE Planet ='Saturn'

UPDATE dbo.Planets
SET Class = 'GM' WHERE Planet ='Uranus'

UPDATE dbo.Planets
SET Class = 'GL' WHERE Planet ='Neptune'

UPDATE dbo.Planets
SET Class = 'SM' WHERE Planet ='Pluto'
;

To display all planets in the Planets table having a solid composition


(an “S” in the first position):

USE Solar_System
SELECT Planet, Class, CHARINDEX('S', Class) AS "Index of 'S'"
FROM dbo.Planets
WHERE CHARINDEX('S', Class) = 1
ORDER BY PID;
USE Solar_System
SELECT Planet, Class
FROM dbo.Planets
WHERE CLASS LIKE 'S_'
ORDER BY PID;

To check to see if a character exists anywhere in a string use “> 0” as


your test within your filter operation (in your WHERE clause). All
planets that don’t have the specified character in the name will return
a CHARINDEX of 0.For example, to display planet names that
contain a “p”, execute the following query:

USE Solar_System
SELECT Planet, CHARINDEX('p', Planet) AS "Index of 'p'"
FROM dbo.Planets
WHERE CHARINDEX('p', Planet) > 0
ORDER BY Planet;

The same results can be produced using LIKE operator as follows:

USE Solar_System
SELECT Planet, CHARINDEX('p', Planet) AS "Index of 'p'"
FROM dbo.Planets
WHERE Planet LIKE '%p%'
ORDER BY Planet;

Using a Static String as an Output


Value
Sometimes you may want to display a column with a set value
depending on the data being selected. For example, suppose you
want to display the names of all planets and moons in the Planets
and Moons tables, but you want to indicate next to each name
whether it is a planet or a moon. To do that you would use a UNION
statement to link the two tables, then create a column that contains
either the string "Planet" or the string "Moon" based on the above
criteria.

USE Solar_System
SELECT 'Planet' AS "Planet/Moon",
Planet AS "Name"
FROM dbo.Planets

UNION

SELECT 'Moon', Moon


FROM dbo.Moons;

A partial list of the output is shown below:


More on Calculating Data
Tables are often designed to save storage space by keeping
duplicated data to a minimum. One of the ways of doing this is to
calculate values in queries rather than saving the results of such
calculations in the database itself. This section will describe various
ways calculations can be performed and how to convert data types
to be used in calculations, when necessary.

Converting Numerical Data to


VARCHAR
In addition to the data type conversions discussed in the lesson,
Converting Data Types, you can also convert numerical data to a
VARCHAR data type. For example, suppose you wanted to display
in a single column the following information about each moon in the
dbo.Moons table:

name of moon is distance km from planet

where name of moon is the column Moon and distance is the column
Distance_km.

If you tried the following query, you would receive an error, because
Moon and Distance_km are two different data types:

SELECT Moon + ' is ' + Distance_km + ' km from planet'


FROM dbo.Moons;

To produce the desired results, you must convert the distance to the
VARCHAR data type. Modify the preceding query as follows:

SELECT Moon + ' is ' + CONVERT(VARCHAR,Distance_km)


+ ' km from planet'
FROM dbo.Moons;

The first seven results are displayed below:

If you want to include the planet’s actual name, you will need to
query both the dbo.Moons and dbo.Planets tables as follows:

SELECT Moon + ' is ' + CONVERT(VARCHAR,Distance_km)


+ ' km from ' + Planet
FROM dbo.Moons AS m, dbo.Planets AS p
WHERE m.PID = p.PID;

The first seven results are displayed to the right:

Calculating Values from Column Data


Calculated values can be used as output or to insert data. It is
important to know the data types required for your calculations and
convert where necessary.

If we want to query the Planet_Masses table to retrieve the actual


mass of each planet, we’d have to multiply the value in the Mass_kg
column by 10 to the power of the value in the Exponent column. The
equation we’ll be using is:

Mass = Base_kg * 10Exponent

USE Solar_System
SELECT Planet, Base_kg * POWER(10.0,Exponent) AS "Mass_kg"
FROM dbo.Planets AS p, dbo.Planet_Masses AS pm
WHERE p.PID = pm.PID;

Let’s dissect the SELECT statement from our query, starting with this
term:
POWER(10.0, Exponent)
The Power function has the following syntax:

Power(expression, exponent)

where expression is any numeric expression, and exponent is the


power you’re raising that expression to. For example, Power(3,2) =
32 = 9.

In our query, Power(10.0,Exponent) raises the number 10 to the


power of whatever value the column named Exponent contains. So,
if Exponent contains the number 24 for a particular record,
Power(10.0, Exponent) = Power(10.0, 24) = 1024.
Next, we’ll multiply the output from the POWER function by
Mass_kg:

Base_kg * POWER(10.0, Exponent)

For the next example, you’ll create a view called View_Masses in


the Solar_System database using the Planet column from the
Planet_Masses table and creating a Mass column by combining the
Mass_kg and Exponent columns in the above calculation. We’ll
assign the Mass column a FLOAT data type by converting the results
of the calculation to a floating point number. The data in the column
will then be displayed in its correct scientific notation.

CREATE VIEW dbo.View_Masses


AS

SELECT Planet,
CONVERT(FLOAT,(Base_kg * Power(10.0, Exponent)),0) AS
Mass_kg
FROM dbo.Planets AS p, dbo.Planet_Masses AS pm
WHERE p.PID = pm.PID;

GO

Here we used 0 for the style argument of the CONVERT function,


which, for FLOAT data types, means use a maximum of six digits
and scientific notation when appropriate (default). A value of 1 would
use 8-digits, 2 would use 16.
You can also perform calculations with functions like COUNT( ), MIN(
), MAX( ), AVG( ), SUM( ), etc. with the GROUP BY clause. The
following example determines the distance from each planet of its
closest moon using the MIN( ) function and the GROUP BY clause:

USE Solar_System
SELECT PID, MIN(Distance_km)
AS Distance_from_Closest_Moon
FROM dbo.Moons
GROUP BY PID;

To display the planet name associated with each PID, you need
query both the dbo.Planets and dbo.Moons tables. The example
below revises the above query, using an INNER JOIN so that the
Planet name is retrieved:

USE Solar_System
SELECT p.PID, Planet, MIN(Distance_km)
AS Distance_from_Closest_Moon
FROM dbo.Planets AS p INNER JOIN dbo.Moons AS m
ON p.PID = m.PID
GROUP BY p.PID, Planet;

You can also use the above query to create an additional view as
follows:
CREATE VIEW dbo.View_Closest_Moons
AS
SELECT p.PID, Planet, MIN(Distance_km)
AS Distance_from_Closest_Moon
FROM dbo.Planets AS p INNER JOIN dbo.Moons AS m
ON p.PID = m.PID
GROUP BY p.PID, Planet;
GO
Review 6
1. Display the first name, the length of the first name, the last
name, and the length of the last name for each astronomer in
the dbo.Astronomers View you created in Review 5. Be sure
to use an alias for the calculated column names.

2. Using the CHARINDEX( ) function, display all moons having


"i" as the second character in the name. (Case-sensitivity is
not required.) Instead of the column name Moon, use the
alias "Moon with 'i' as 2nd char". (Save this query, you'll
need it for Exercise 3.)

3. Modify the query in Exercise 2 above to display one column


with the name of the moon concatenated with the name of its
Planet. Insert the string ' = Moon of ' between the two. Use
the column alias "Moons and their Planets" for the result.
(Save this query; you will need it for Exercise 4.)
4. Re-do the query from Exercise 3 using SUBSTRING instead
of CHARINDEX to achieve the same results displayed in
Exercise 3.
5. (OPTIONAL) The equation F=ma (Force = mass x
acceleration) is well known in physics. We have that
information in our Planet_Masses table, with Weight_lb being
equivalent to Force, Gravity_mps2 equivalent to the
acceleration, and the Mass of the Person as the calculated
value Weight_lb / Gravity_mps2.
You will display the following:
PID
Weight_lb multiplied by 2.2 (to get kilograms) using the
alias "Force (Newtons)"
Weight_lb multiplied by 2.2 divided by Gravity_mps2 using
the alias "Mass of Person (kg)"
Gravity_mps2 using the alias "Acceleration (m/sec**2)"
LESSON 7
In this lesson you will learn about:

Querying databases using simple and correlated


subqueries
Stored procedures
Subqueries
A subquery is also known as a nested query. Basically, it's a
SELECT statement within another SQL statement or subquery. In
this section we'll discuss using subqueries embedded in SELECT
statements and WHERE clauses.

The same rules apply to subqueries as to regular queries, with a few


additions:
A subquery must be enclosed within parentheses.
A subquery cannot contain an ORDER BY clause.
Don't use a semicolon at the end of a subquery.
Only columns from tables specified in the outer query can
be displayed in the query results.
The results of a subquery must return only one row (except
when containing an operator such as IN).
When using subqueries, the subquery (inner query), is
executed first and the results passed to the outer query.
There are two types of subqueries: Simple and Correlated.

Simple Subquery—Can run independently of the outer query.


Correlated Subquery—Dependent upon the outer query to run.

Examples in this lesson which contain correlated subqueries will be


indicated as such.

Returning a Single Value with a


Subquery
Except under the condition discussed in the section Returning
Multiple Values with a Subquery, all subqueries must return only a
single row of data.

Using a Subquery in a WHERE Clause


The following query implicitly joins the authors table (pubs database)
with itself to find all authors living in the same city as the author with
the last name “Green”.

USE pubs
SELECT a1.au_lname, a1.au_fname, a1.city, a1.[state]
FROM dbo.authors AS a1, dbo.authors AS a2
WHERE a1.city = a2.city
AND a2.au_lname = 'Green';

The same results can be achieved by using a subquery in the


WHERE clause as follows:
USE pubs
SELECT au_lname, au_fname, city, [state]
FROM dbo.authors
WHERE city =
(SELECT city
FROM dbo.authors
WHERE au_lname = 'Green');

The subquery is evaluated first returning the result “Oakland”.


Therefore, the WHERE clause is evaluated as “WHERE city =
‘Oakland’”.

Switching back to the Solar_System database, suppose you want to


display the names of all moons with a diameter greater than the
planet Mercury. You can easily accomplish this with a subquery.

USE Solar_System
SELECT Moon, Diameter_km
FROM dbo.Moons
WHERE Diameter_km >
(SELECT Diameter_km
FROM dbo.Planets
WHERE Planet = 'Mercury');

The subquery is evaluated first, returning the value 4878 to the outer
query. The outer query then compares the diameters of all moons to
that value, then displays the names and diameters of the three
moons having diameters greater than 4878.

To display planet names that do not exist in the Moons table:

SELECT Planet
FROM dbo.Planets
WHERE NOT PID IN
(SELECT PID FROM dbo.Moons);

Subqueries on a Single Table


You can also use a subquery on the same table as the outer query.
For example, if you want to list all planets whose gravity is greater
than Earth's (PID = 3) execute the following query:
SELECT PID
FROM dbo.Planet_Masses
WHERE Gravity_mps2 >
(SELECT Gravity_mps2
FROM dbo.Planet_Masses
WHERE PID = '3');

To get the actual names of the planets, you need to query the
dbo.Planets table also:

SELECT Planet
FROM dbo.Planet_Masses AS pm,
dbo.Planets AS p
WHERE Gravity_mps2 >
(SELECT Gravity_mps2
FROM dbo.Planet_Masses
WHERE PID = '3')
AND pm.PID = p.PID ;

Using a Subquery in a SELECT Statement


Using the pubs database again, suppose you want to find the book
title, price, and its price as a percentage of the highest priced book. If
you tried the following query, it would fail because the title and price
columns not part of the MAX function have to be in a GROUP BY
clause to be used.
USE pubs
SELECT title, price,
price/MAX(price) * 100 AS "% of Max Price"
FROM dbo.titles
WHERE price IS NOT NULL;
To get around this problem, use a subquery to retrieve the MAX price
as follows:

USE pubs
SELECT title, price,
price/(SELECT MAX(price) FROM dbo.titles) * 100
AS "% of Max Price"
FROM dbo.titles
WHERE price IS NOT NULL;

Returning to the Solar_System database, suppose you want to list


each planet, its gravity, and comparisons of its gravity to the lightest
and heaviest planets. As with the previous attempt to query the
dbo.titles table in the pubs database, you might think the following
query would work:
SELECT PID,
Gravity_mps2,
Gravity_mps2/MIN(Gravity_mps2)
AS "Ratio to Lightest",
Gravity_mps2/MAX(Gravity_mps2)
AS "Ratio to Heaviest"
FROM dbo.Planet_Masses;
However this query produces the same type of error:
Msg 8120, Level 16, State 1, Line 1
Column 'dbo.Planet_Masses.Planet' is invalid in the select list because
it is not contained in either an aggregate function or the GROUP BY clause.

Because we're using aggregate functions (MIN, MAX) in our


SELECT statement, then every column in a SELECT statement must
also appear in an aggregate function (or GROUP BY clause).
Therefore PID and Gravity_mps2 cannot be used alone in the select
statement. To resolve this problem, we use a subquery for each
calculation involving an aggregate function. The subqueries are
evaluated separately from the outer query, avoiding the previous
problem of mixing aggregates with non-aggregates without a
GROUP BY clause.

The table listed in the FROM statement in the subquery is treated as


a separate table. The Gravity_mps2 value used as the numerator is
pulled from the table used by the outer query, while the
Gravity_mps2 used in the aggregate functions is pulled from the
table used by the subquery.
The above query is a correlated subquery because it depends on a
column value in a table from the outer query (pm.Gravity_mps2) in
order to run correctly.

Using a JOIN will not work because a join is run as a single query,
with all column values, functions, and filtering criteria evaluated at
the same time. Subqueries are evaluated in succession (innermost
to outermost), with the results of each subquery passed as input to
the next higher query.

To display the planet name instead of the PID, modify the previous
query as follows:

SELECT Planet,
Gravity_mps2,
(SELECT pm.Gravity_mps2/MIN(Gravity_mps2)
FROM dbo.Planet_Masses) AS "Ratio to Lightest",
(SELECT pm.Gravity_mps2/MAX(Gravity_mps2)
FROM dbo.Planet_Masses) AS "Ratio to Heaviest"
FROM dbo.Planet_Masses AS pm, dbo.Planets AS p
WHERE pm.PID = p.PID ;

Returning Multiple Values with a


Subquery
As mentioned previously, a subquery must return only one row of
data unless a multiple-value operator such as IN is used. For
example, suppose you want to display all moons that start with the
same letter as a planet name:

USE Solar_System
SELECT Moon
FROM dbo.Moons
WHERE SUBSTRING(Moon,1,1)
IN (SELECT SUBSTRING(Planet,1,1) FROM dbo.Planets);
First the SELECT statement in the subquery was evaluated,
returning the first character in each planet in the Planet column of
the Planets table. Then the first character in each moon name in the
Moons column of the Moons table was compared to the set of
characters returned by the subquery. The above query then returns
59 rows displaying all moons that begin with the letters "E", "J", "M",
"N", "P", "S", and "U".

You could have written the query without the subquery, but that
would have required you to know the contents of Planets table. In
this case, it would have been easy, but usually you don't know what
all the values are in a column in advance. This is another reason
subqueries are so useful.
The CASE Statement
A CASE statement is an efficient way to select, filter, or sort
columns. There are two types of CASE statements: Simple and
Searched. Both can be used with the various SQL statements and
clause, including:

SELECT
WHERE
HAVING
ORDER BY
INSERT
UPDATE
SET
DELETE

This lesson will focus on using the CASE statement with the
SELECT and ORDER BY statements.

Simple CASE Statement


The simple CASE statement compares an expression to a simple
expressions in one or more WHEN clauses. Unlike the searched
CASE statement, the simple CASE statement does not allow
comparison or logical operators. The syntax is as follows (the ELSE
argument is optional):

CASE expression_to_compare_with_when_expressions
WHEN expression1 THEN Result1
WHEN expression2 THEN Result2
.
.
.
WHEN expressionN THEN ResultN
ELSE Result_for_all_other_conditions
END
For example, to display a different value for each record of the Class
column based on the original column value, execute the following
query:

USE Solar_System
SELECT Planet, CASE Class
WHEN 'SL' THEN 'Solid, tilt<90'
WHEN 'SM' THEN 'Solid, tilt>90'
WHEN 'GL' THEN 'Gas, tilt<90'
WHEN 'GM' THEN 'Gas, tilt>90'
END
FROM dbo.Planets;

To give the alias “Designation” to the column name, change the


query slightly:

SELECT Planet, Designation=CASE Class


WHEN 'SL' THEN 'Solid, tilt<90'
WHEN 'SM' THEN 'Solid, tilt>90'
WHEN 'GL' THEN 'Gas, tilt<90'
WHEN 'GM' THEN 'Gas, tilt>90'
END
FROM dbo.Planets;
Searched CASE Statement
The searched CASE statement is a little more practical. It evaluates
a set of Boolean expressions to determine the results(for
example, WHEN state='CA') . The searched CASE statement allows
comparison and logical operators. The syntax is as follows (the
ELSE argument is optional):

CASE
WHEN boolean_expression1 THEN Result1
WHEN boolean_expression2 THEN Result2
.
.
.
WHEN boolean_expressionN THEN ResultN
ELSE Result_for_all_other_conditions
END

For example, to change the sort order of planets either by density or


diameter based on the value in the Class column, execute the
following query:

USE Solar_System
SELECT * FROM dbo.Planets
ORDER BY CASE
WHEN CLASS LIKE 'S%' THEN Density_gpcm3
WHEN CLASS LIKE 'G%' THEN Diameter_km
END;
When displaying the dbo.Planets table with just the statement
“SELECT * FROM dbo.Planets”, you get the following results:

You can also combine subqueries with Searched Case statement.


The following example queries the dbo.Moons table to display the
name of each Moon and a new column that displays either “Larger
than Mercury” or “Smaller than Mercury” depending on whether the
Diameter_km column in the moons table is greater than the
Diameter_km value for the planet Mercury in the dbo.Planets table. It
sorts the results in descending order by Diameter_km.

USE Solar_System
SELECT Moon, Diameter=CASE
WHEN Diameter_km > (SELECT Diameter_km
FROM dbo.Planets
WHERE Planet = 'Mercury')
THEN Larger than Mercury'
ELSE 'Smaller than Mercury'
END
FROM dbo.Moons
ORDER BY Diameter_km DESC;

First 5 of 151 results:


Stored Procedures
Stored procedures are a series of SQL statements compiled, saved
in the database. Unlike a saved .sql file which must be loaded before
it can be executed, a stored procedure is ready for use instantly.
There are numerous system procedures available, or you can create
your own. This lesson will discuss both types.

Using Stored Procedures and System


Views
Following are examples of stored procedures and system functions
which you may find useful.

Rename a table or view


USE Solar_System
GO
SP_RENAME 'old_table_name', 'new_table_name'
GO

Rename a column
USE Solar_System
GO
SP_RENAME 'table_name.old_col_name', 'new_col_name', 'COLUMN'
GO

List all stored procedures associated with a


database
USE Pubs
EXEC sp_stored_procedures;

List all system procedures


SELECT NAME
FROM SYS.ALL_OBJECTS
WHERE type='P'
ORDER BY NAME;
List all user-created stored procedures
SELECT * or SELECT *
FROM sys.procedures; FROM
INFORMATION_SCHEMA.ROUTINES;

Determine the default collation:


SELECT SERVERPROPERTY('COLLATION') AS SQLCollation;

or for more details:


EXEC sp_helpsort;

Get help on a specific table


EXEC sp_help 'dbo.Moons';

DBCC CHECKTABLE
DataBase Consistency Check (DBCC) CHECKTABLE looks for
problems in a specific table and also returns the number of rows in
the table. For example, the following query checks the table
dbo.Moons for errors:
DBCC CHECKTABLE ("dbo.Moons");

The following result shows no errors:

sp_MSforeachtable
The sp_MSforeachtable procedure accepts parameters that allow
you to retrieve information such as the name of each table in a
database, the number of rows in specified tables, and so on. In lieu
of specifying a table name, you can use “?” as a type of wildcard to
mean all tables.

Find the names of every table in a database


EXEC sp_MSforeachtable 'SELECT "?" AS "Table Name"';

Find the number of rows each table in a database


EXEC sp_MSforeachtable 'DBCC CHECKTABLE ("?")';
or to display results in columns:

EXEC sp_MSforeachtable
'SELECT "?" AS TableName, COUNT(*) AS NumberOfRows FROM
?';

Creating a Stored Procedure


One of the most useful stored procedures to have is one which
generates an HTML report from a query and email it to yourself or an
associate. We'll create this procedure shortly.

The syntax for creating a stored procedure is as follows:

CREATE PROCEDURE procedure_name


AS
BEGIN
list of SQL statements to run when the stored procedure is executed

END
GO

The prefix "dbo." will automatically be added to your procedure name


at the time you create it.

The first procedure we'll create is a query that displays all planets
with moons, the name of their closest moon, and the distance from
that moon. Since it's a query that uses nested subqueries and
grouping, and you've been instructed that you'll run this query often,
creating it as a stored procedure will save you a lot of time and effort
in the long run.

Before creating the procedure, however, let’s look at the query itself
step-by-step.

The first part of the query, shown below, displays two columns: (1)
the name of each planet in dbo.Planets with matching PIDs in
dbo.Moons and (2) the minimum value of Distance_km for each PID
(because we’re grouping by PID).

USE Solar_System
SELECT
--Subquery #1
--First column Planet with alias "Name of Planet"
--Finds all PIDs in dbo.Planets with matching PIDs
--in dbo.Moons
(SELECT Planet
FROM dbo.Planets AS p1
WHERE m1.PID = p1.PID) AS "Name of Planet",

--Subquery #2
--Second column MIN(Distance_km) with alias
--"Distance to Moon(km)"
MIN(m1.Distance_km) AS "Distance to Closest Moon (km)"
FROM dbo.Moons AS m1
GROUP BY PID;

Next we need to find the name of the moon in each PID grouping
whose Distance_km equals the minimum distance we retrieved in
the first part of the query. To do that, you’ll add a third subquery just
before the line “FROM dbo.Moons AS m1”. (Don’t forget to add a
comma after the second subquery.)

(SELECT Moon FROM dbo.Moons


WHERE Distance_km IN (MIN(m1.Distance_km)))
AS "Name of Closest Moon"

The entire query is shown below:

USE Solar_System
SELECT
--Subquery #1
--First column Planet with alias "Name of Planet"
--Finds all PIDs in dbo.Planets with matching PIDs
--in dbo.Moons
(SELECT Planet
FROM dbo.Planets AS p1
WHERE m1.PID = p1.PID) AS "Name of Planet",

--Subquery #2
--Second column MIN(Distance_km) with alias
--"Distance to Moon(km)"
MIN(m1.Distance_km) AS "Distance to Closest Moon (km)",
--Subquery #3
--Third column Moon with alias "Name of Closest Moon"
--Retrieves name of moon in dbo.Moons whose distance
--(for each PID) equals the minimum distance for that
--PID (retrieved in subquery #2)
(SELECT Moon FROM dbo.Moons
WHERE Distance_km IN (MIN(m1.Distance_km)))
AS "Name of Closest Moon"

FROM dbo.Moons AS m1
GROUP BY PID;

To turn this query into a stored procedure, insert the following text at
the beginning of the query:

CREATE PROCEDURE sp_find_closest


AS
BEGIN

Then insert the following text at the end of the query:

END
GO

The entire code to create procedure (with comments removed)


follows:

CREATE PROCEDURE sp_find_closest


AS
BEGIN
SELECT

(SELECT Planet
FROM dbo.Planets AS p1
WHERE m1.PID = p1.PID) AS "Name of Planet",

MIN(m1.Distance_km) AS "Distance to Closest Moon (km)",

(SELECT Moon FROM dbo.Moons


WHERE Distance_km IN (MIN(m1.Distance_km)))
AS "Name of Closest Moon"

FROM dbo.Moons AS m1
GROUP BY PID;

END
GO

To save the procedure, click !Execute or press the F5 key.

To run the stored procedure, open a new query window and execute
the following statement:

EXEC dbo.sp_find_closest;

You can query the system view


INFORMATION_SCHEMA.ROUTINES to find all stored procedures
that begin with the characters sp_f.

List all user-created stored procedures which


begin with the characters "sp_f"
SELECT *
FROM INFORMATION_SCHEMA.ROUTINES
WHERE ROUTINE_NAME LIKE 'sp_f%';

(First 5 columns are displayed below:)


Using Variables in a Query
To create and use a variable in a query, you first define it with the
DECLARE statement, giving it a name and a data type, then assign
a value to it with the SET statement. Variable names always begin
with the “@” symbol.

DECLARE @variable_name data_type


SET @variable_name = value

Consider the following example:


- or -
DECLARE @x FLOAT DECLARE @x FLOAT = 5.75
DECLARE @y FLOAT DECLARE @y FLOAT = 2.0
DECLARE @z FLOAT DECLARE @z FLOAT = @x * @y
SET @x = 5.75
SET @y = 2.0
SET @z = @x * @y

SELECT @z; SELECT @z;

When you run the above query, the “SELECT @z” statement
displays the contents of the variable @z in the results window.

One use of variables is to substitute them for subqueries. Consider


the following subquery that finds all planets with a diameter less than
or equal to Earth’s:

USE Solar_System
SELECT Planet, Diameter_km
FROM dbo.Planets
WHERE Diameter_km <= (SELECT Diameter_km FROM
dbo.Planets
WHERE Planet = 'Earth')
ORDER BY Diameter_km DESC;

You get the same results by storing the results of the select
statement from the subquery in a variable, then using the value of
that variable later in your query:

USE Solar_System
DECLARE @earth INT = (SELECT Diameter_km FROM
dbo.Planets
WHERE Planet = 'Earth')
SELECT Planet, Diameter_km
FROM dbo.Planets
WHERE Diameter_km <= @earth
ORDER BY Diameter_km DESC;

To use retrieve a list of all moons in the dbo.Moons table having a


diameter greater than or equal to Earth’s moon and where, on the
planet that moon is orbiting, you would weight more than or equal to
what you weigh on Earth, you could execute the following query:

SELECT m.PID, m.Moon, m.Diameter_km,


pm.Weight_lb AS "Weight on PID"
FROM dbo.Planet_Masses AS pm,
dbo.Moons AS m,
WHERE m.Diameter_km >= (SELECT Diameter_km
FROM dbo.Moons
WHERE PID = '3')
AND pm.Weight_lb >= (SELECT Weight_lb
FROM dbo.Planet_Masses
WHERE PID = '3')
AND pm.PID = m.PID
ORDER BY m.PID;

Or, you could store the results of the two subqueries in two variables,
then use those variables in the WHERE clause and get the same
results:

DECLARE @EarthMoon_dia DECIMAL(10,2) = (SELECT


Diameter_km
FROM dbo.Moons
WHERE PID = '3')
DECLARE @EarthWeight INT = (SELECT Weight_lb
FROM dbo.Planet_Masses
WHERE PID = '3')
SELECT m.PID, m.Moon, m.Diameter_km,
pm.Weight_lb AS "Weight on PID"
FROM dbo.Planet_Masses AS pm, dbo.Moons AS m
WHERE m.Diameter_km >= @EarthMoon_dia
AND pm.Weight_lb >= @EarthWeight
AND pm.PID = m.PID
ORDER BY PID;

One of the best uses of variables is to create HTML output. The


following example shows how to create and use a variable to create
HTML output:

DECLARE @var VARCHAR(MAX);


SET @var = '<html><body>Hello World!</body></html>';
SELECT @var;
Follow these steps to save your file then view in your browser:

Step 1. Right-click on the results line, then select Save Results


As....

Step 2. In the Save as type box, select All files (*.*), then name the
file test.htm. (You must use the .htm extension NOT .txt. Otherwise
the browser cannot display the HTML content.)

Step 3. Navigate to test.htm, then double-click on the file to open it


in your default browser. You should see the text in “Hello World!”
your browser window.
Generating an HTML report
Now you’re going to create an HTML report generator called
sp_planet_categories. Unless you want to do all the typing
yourself, load the accompanying file called
"Create_sp_planet_categories.sql" in a new query window, then
execute it. The complete query is shown below.
The "N'" prefacing each line of text containing HTML code below says
to treat the text following it as Unicode (NVARCHAR data type). The
‘+’ symbol concatenates each line to create a single string to assign to
the @tableHTML variable.

CREATE PROCEDURE sp_planet_categories


AS
BEGIN

DECLARE @tableHTML nVarchar(max)


DECLARE @subject nVarchar(max)
SET @subject = 'Planetary Report'
SET @tableHTML =
N'<html><head><title>Query Results</title></head><body>' +
N'<table width="75%" style="color: red; border: 1 solid black; padding: 1; text-
align: center">' +
N'<caption style="font-size: 24pt">Inner Planets</caption>' +
N'<tr><th> Planet </th><th> Diameter(Km) </th><th> Distance(AU) </th>' +
N'<th> Distance(MKm) </th><th> Rotation(days) </th><th> Orbit(years) </th>
<th> # Moons </th><th> Density </th>'+
N'</tr>' +
CAST ( ( SELECT td = Planet, '',
td = Diameter_km, '',
td = Distance_AU, '',
td = Distance_Mkm, '',
td = Rotation_days, '',
td = Orbit_years, '',
td = Num_Moons, '',
td = Density_gpcm3, ''
FROM dbo.Planets
WHERE PID <= 4
ORDER BY PID
FOR XML PATH('tr'), TYPE
) AS NVARCHAR(MAX) ) +
N'</table>' +

N'<table width="75%" style="color: blue; border: 1 solid black; padding: 1; text-


align: center">' +
N'<caption style="font-size: 24pt">Outer Planets</caption>' +
N'<tr><th> Planet </th><th> Diameter(Km) </th><th> Distance(AU) </th>' +
N'<th> Distance(MKm) </th><th> Rotation(days) </th><th> Orbit(years) </th>
<th> # Moons </th><th> Density </th>'+
N'</tr>' +
CAST ( ( SELECT td = Planet, '',
td = Diameter_km, '',
td = Distance_AU, '',
td = Distance_Mkm, '',
td = Rotation_days, '',
td = Orbit_years, '',
td = Num_Moons, '',
td = Density_gpcm3, ''
FROM dbo.Planets
WHERE PID > 4
ORDER BY PID
FOR XML PATH('tr'), TYPE
) AS NVARCHAR(MAX) ) +
N'</table>' +
N'</body></html>'

/* The following statement calls a system procedure from this stored


procedure. If your administrator has made the mail component
available on by your server, uncomment the following 3 lines. */
/* exec msdb.dbo.sp_send_dbmail
@recipients='myemail@mail.com',@subject=@subject, @body=@tableHTML,
@body_format ='HTML' */

SELECT @tableHTML
END
GO
Executing the above query creates and saves the stored procedure.
To run the procedure, open a new query window and execute the
following code:
EXEC dbo.sp_planet_categories;
You will see the following results:

To save the file (name it QueryResults.htm) then view it your


browser window, follow the steps you used to display the test.htm
file.

Deleting a Stored Procedure


To remove a stored procedure from a database, you can right-click
on the procedure name then select Delete, or use the DROP
PROCEDURE command.

DROP PROCEDURE procedure_name

If you don't know the exact name of the procedure, it can be found in
the Stored Procedures subfolder of the Programmability folder under
your database name in the Object Explorer.
Review 7
1. Using a subquery in a WHERE clause, display the name,
diameter, and moon ID of each moon having a diameter
greater than 50% of that of Mars.

2. Display the minimum and maximum planetary distances from


the sun along with the name of the planet at each of these
distances. (HINT: Use two INs linked by an OR and separate
subqueries in the WHERE clause for the MIN and MAX
functions.)

3. Using a Simple CASE statement, query the dbo.Planets table


to display the name of each Planet and a new column that
displays either YES or No depending on the value in the
Num_Moons column. If the number of moons is zero (0), then
display “No”. Otherwise, display “YES”.
4. (CHALLENGE QUESTION!) Using a Searched CASE
statement, query the dbo.Planets and dbo.Planet_Masses
table to display the name of each Planet and a new column
that displays “You weigh more”, “You weigh less” or “Weight
unchanged” depending on whether the Weight_lb column in
the planet_masses table is greater than the Weight_lb value
for the planet Earth in the planet_masses table. Sort in
ascending order by the PID value in the planets table. Give
the new column the alias “Weight”.
HINTS:
You are querying two tables, so use either an implicit or
explicit inner join (the solution shows an implicit inner
join).
Use a subquery as part of the WHEN clause, similar to
the example shown in the lesson.
5. In Lesson 1 you were introduced to system views that
allowed you to retrieve a copious amounts of data about
tables, columns, etc. using INFORMATION_SCHEMA
subsets. For this exercise, you will create a stored procedure
called sp_review_3_3 that retrieves the following information
from the Solar_System database in three successive queries:
Query #1: Display column constraints.
Query #2: Display table information.
Query #3: Execute the sp_tables procedure, retrieving only the
information related to
user-created tables.

6. Modify the sp_planet_categories report created earlier in the


lesson to create a report called sp_planet_sizes. Instead of
displaying distance categories, you will show two tables: one
displaying planets with a diameter greater than earth, the
other displaying planets with a diameter smaller than Earth.
Sort by diameter in ascending order.
HINT: You will need to use a subquery in the WHERE clause
similar to that in the example Subqueries on a Single Table.
LESSON 8
In this lesson you will learn about:

Creating, modifying, and removing column constraints


Importing table data from an Excel spreadsheet
Transactions
Constraints
Constraints (also known as Integrity Constraints) are rules that
define what values can be entered into a column.

Following are the types of constraints you can set.

Constraint Description
PRIMARY Used to uniquely identify a row. A table may
KEY have only one Primary Key.
FOREIGN A column in one table that references a
KEY Primary Key in another table. A table may
contain more than one Foreign Key.
UNIQUE Forces a value in a column to be unique
(Primary Keys automatically have this
constraint).
NOT NULL Ensures that a value must be entered in that
column for each record.
CHECK Checks the validity of values based on
specified criteria.

Setting Constraints
Constraints can be set at the time a table is created or added later.
This section provides examples of both methods.
The syntax of the CONSTRAINT statement is:
CONSTRAINT constraint_name constraint conditions

Primary Key Constraints


When we originally created our PID column in the Planets table, we
specified the data type, set that column as the Primary Key, and
constrained it to allow only non-null values as follows:
PID VARCHAR(2) NOT NULL PRIMARY KEY
We could have constrained it as the Primary Key by removing
PRIMARY KEY from the above statement, then adding either of the
two following statements just before the closing ")" of our CREATE
TABLE statement:

PRIMARY KEY (PID)


or
CONSTRAINT planets_pk PRIMARY KEY (PID)

Foreign Key Constraints


A Foreign Key references a Primary Key in another table. When you
set a Foreign Key constraint, you cannot drop the table it references,
or depends on, without either dropping the Foreign Key constraint or
the referencing table.
The syntax for creating a Foreign Key constraint is as follows:
CONSTRAINT constraint_name
FOREIGN KEY (referencing_table's_foreign_key_column)
REFERENCES
referenced_table_name(referenced_table's_primary_key_column)
Example:

UNIQUE Constraints
A Unique constraint declares that the data in a particular column
must be unique in that column. All Primary keys are Unique, but not
every column with the Unique constraint applied is necessarily a
Primary key.

The syntax for creating a Unique constraint is as follows:

CONSTRAINT table_name UNIQUE(column_name)


NOT NULL Constraints
As shown earlier, you create a NOT NULL constraint by placing the
text "NOT NULL" after the data type when creating table columns.

CHECK Constraints
A Check constraint validates data as it is entered. Examples of
CHECK constraints include ensuring a value must be greater than
zero, be within a certain range, or start with an uppercase letter.

The syntax for a CHECK constraint is as follows:

CONSTRAINT constraint_name CHECK(condition_to_evaluate)

The condition to be evaluated can include calculations, substrings,


data type conversions, LIKE and IN comparisons, etc.

Setting a Constraint During Table Creation


Let's create a new table with the following constraints.

Pname is created as the Primary Key


Position is a Foreign Key which references the PID in the
dbo.Planets table and must be greater than zero.
Year_length must be a value greater than zero and less than
or equal to 250.
Distance_AU must be unique and greater than zero.

The new table will be populated with the first three records from
dbo.Planets.

USE Solar_System
CREATE TABLE Sample_Constraints
(
Pname VARCHAR(20) NOT NULL,
Position VARCHAR(2) NOT NULL,
Year_length FLOAT NULL,
Distance_AU FLOAT NOT NULL,
CONSTRAINT sample_constraints_PK
PRIMARY KEY(Pname),

CONSTRAINT sample_constraints_FK
FOREIGN KEY(Position)
REFERENCES dbo.Planets(PID),

CONSTRAINT sample_constraints_UQ_position
UNIQUE(Position),

CONSTRAINT sample_constraints_UQ_distance
UNIQUE(Distance_AU),

CONSTRAINT sample_constraints_ck_position
CHECK(Position > 0),

CONSTRAINT sample_constraints_ck_year
CHECK(Year_length > 0 AND Year_length <= 250),

CONSTRAINT sample_constraints_ck_distance
CHECK(Distance_AU > 0)
);

Now execute the following query to view the contents of the new
Sample_Constraints table:

SELECT * FROM dbo.Sample_Constraints


ORDER BY PID;
You can view current keys and constraints by expanding the
Columns, Keys, and Constraints lists under the table name in the
SQL Server Management Studio's Object Explorer. Since columns
having the Unique constraint can be used as references for Foreign
Keys, these constraints will appear in the list with the other keys.

Testing Constraints
To test the validity of data entered into the table you just created,
we'll try to enter invalid data.

Test #1: Attempt to enter a NULL value for


Position:
USE Solar_System
INSERT INTO dbo.Sample_Constraints
VALUES('Planet X', '1', 250, 35);

Results in the following error:


Now enter a valid value for Position:

INSERT INTO dbo.Sample_Constraints


VALUES('Planet X', '4', 250, 35);

The resulting message indicates a successful entry


(1 row(s) affected)

A new query shows the new record added to the table:

Test #2: Attempt to enter a record without


specifying the distance:
INSERT INTO dbo.Sample_Constraints
VALUES('Planet Y', '5', 0.5, NULL);

Results in the following error:

Entering the following will add the record:

INSERT INTO dbo.Sample_Constraints


VALUES('Planet Y', '5', 0.5, 0.18);
Test #3: Attempt to enter a record with a negative
distance:
INSERT INTO dbo.Sample_Constraints
VALUES('Planet Z', '6', 25, -3);

Results in the following error:

Dropping a Constraint
Whenever you want to remove a constraint you must do so using the
DROP statement in conjunction with an ALTER TABLE statement.

The syntax is as follows:

ALTER TABLE table_name


DROP CONSTRAINT constraint_name

For example, to remove the Foreign Key constraint from our


Sample_Constraints table, execute the following query:

ALTER TABLE dbo.Sample_Constraints


DROP CONSTRAINT sample_constraints_FK;

Adding or Changing a Constraint


To add or change a constraint on an existing table column, you will
start with the ALTER TABLE statement, then change your constraint
with either the ALTER COLUMN or ADD CONSTRAINT statement,
depending on the type of constraint to be changed/added.

To change the Year_length column in the Sample_Constraints table


so that it does not allow NULL values, then add the constraint that
the column must contain only unique values, execute the following
queries:

ALTER TABLE dbo.Sample_Constraints


ALTER COLUMN Year_length FLOAT NOT NULL;

To change the sample_constraints_ck_distance constraint so that


the distance has to be less than 300 AU, first you have to DROP the
constraint, then add it back in:

ALTER TABLE dbo.Sample_Constraints


DROP CONSTRAINT sample_constraints_ck_distance;

ALTER TABLE dbo.Sample_Constraints


ADD CONSTRAINT sample_constraints_ck_distance
CHECK(Distance_AU > 0 AND Distance_AU < 300);

The NOCHECK Option


Sometimes problems occur when trying to add a new constraint to a
column which already contains data that conflicts with the new
constraint. For example, enter the following record into the
Sample_Constraints table:

INSERT INTO dbo.Sample_Constraints


VALUES('Ares', '6', 1.5, 2);
Now try to add the following constraint on the Pname column:

ALTER TABLE dbo.Sample_Constraints


ADD CONSTRAINT sample_constraints_ck_Pname
CHECK (NOT Pname LIKE 'A%');

You will receive the following error message because we are trying
to set a constraint that does not allow values in the Pname column to
begin with the character "A" but it already contains the value "Ares".
Msg 547, Level 16, State 0, Line 1
The ALTER TABLE statement conflicted with the CHECK constraint sample_constraints_ck_Pname". The
conflict occurred in database "Solar_System", table "dbo.Sample_Constraints", column 'Pname'.
To solve this problem, we can add the constraint for future entries,
add the WITH NOCHECK option. This will not check the current
entries before trying to add the constraint.

ALTER TABLE dbo.Sample_Constraints WITH NOCHECK


ADD CONSTRAINT sample_constraints_ck_Pname
CHECK (NOT Pname LIKE 'A%');

Checking for Substrings


Earlier we discussed CHECK constraints on numeric data types.
When you created the Sample_Constraints table, you added CHECK
constraint to check the values being entered into certain columns.
You can also check the character values at specific locations within a
string and create CHECK constraints associated with those values.

Create a new table called Sample_Constraints_2:

USE Solar_System
CREATE TABLE Sample_Constraints_2
(
PID VARCHAR(2) NOT NULL PRIMARY KEY,
Pname VARCHAR(20) NOT NULL,
Position FLOAT NOT NULL
);

INSERT INTO dbo.Sample_Constraints_2


VALUES('2m','Venus',2);

You will see that the characters "2m" are accepted as values for the
PID field, since we haven't told such combinations are not allowed.

What we want is to constrain the first character of PID to the


characters A-Z and the second character to the characters 0-9. To
do this, we would add a constraint that checks the first and second
positions of the PID value as follows (be sure to save this query as
we will need it again shortly):
USE Solar_System
ALTER TABLE Sample_Constraints_2 WITH NOCHECK
ADD CONSTRAINT sample_constraints_2_PID_CK
CHECK(
(SUBSTRING(PID, 1, 1) LIKE
'[ABCDEFGHIJKLMNOPQRSTUVWXYZ]')
AND
(SUBSTRING(PID, 2, 1) LIKE '[0123456789]')
)
;

To understand why this works, let's dissect the query that created the
constraint, starting with the ALTER TABLE statement. You'll notice
that we added the WITH NOCHECK option at the end. This is
because there are already values in our table which would violate
the constraints we're setting, and we want to ignore those for now.

The ADD CONSTRAINT command names the constraint we're


adding, then drops to the next line and indicates that the type of
constraint to be added is a CHECK constraint. The CHECK
constraint is checking the first and second positions of the PID value.
SUBSTRING(PID, 1, 1) checks only the first character in
the PID field.
LIKE '[ABCDEFGHIJKLMNOPQRSTUVWXYZ] ensures
the above substring contains only the characters A-Z. (At
this point, our check is not case-sensitive. We'll change
that shortly.)
SUBSTRING(PID, 2, 1) checks only the second character
in the PID field.
LIKE '[0123456789] ensures the above substring contains
only the characters 0-9.
Now if you try to insert an invalid PID like in the query below, you will
receive an error message:
INSERT INTO dbo.Sample_Constraints_2
VALUES('3m','Earth', 3);
Msg 547, Level 16, State 0, Line 1
The INSERT statement conflicted with the CHECK constraint "sample_constraints_2_PID_CK".
The conflict occurred in database "Solar_System", table "dbo.Sample_Constraints_2", column 'PID'.
The statement has been terminated.

But if you switch the positions of the "3" and the "m", the data is
accepted.
Now drop the CHECK constraint you created and add one that will
ensure the entry of only uppercase values in the first position of PID.
ALTER TABLE dbo.Sample_Constraints_2
DROP CONSTRAINT sample_constraints_2_PID_CK;
To re-create the constraint to include case-sensitivity, execute the
following query:

ALTER TABLE Sample_Constraints_2 WITH NOCHECK


ADD CONSTRAINT sample_constraints_2_PID_CK
CHECK(
(SUBSTRING(PID, 1, 1) COLLATE SQL_Latin1_General_CP1_CS_AS
LIKE '[ABCDEFGHIJKLMNOPQRSTUVWXYZ]')
AND
(SUBSTRING(PID, 2, 1) LIKE '[0123456789]')
)
;

Or you can use the UNICODE function with CHECK statement. For
example, you could replace the first SUBSTRING/COLLATE/LIKE
sequence
(SUBSTRING(PID, 1, 1) COLLATE SQL_Latin1_General_CP1_CS_AS
LIKE '[ABCDEFGHIJKLMNOPQRSTUVWXYZ]')

with the following:


UNICODE(SUBSTRING(PID,1,1) >= 65 AND SUBSTRING(PID1,1) <=90)

The Unicode value for uppercase A is 65, and the value for
uppercase Z is 90.
You can also shorten the above A through Z and 0 through 0 ranges
to [A-Z] and [0-9], respectively.

After adding the previous constraints, if you execute the following


query, you will receive an error message:

INSERT INTO dbo.Sample_Constraints_2


VALUES('m5','Jupiter', 5);
Msg 547, Level 16, State 0, Line 1
The INSERT statement conflicted with the CHECK constraint "sample_constraints_2_PID_CK".
The conflict occurred in database "Solar_System", table "dbo.Sample_Constraints_2", column 'PID'.
The statement has been terminated.

But if you use an uppercase character in the first position of PID, the
data is accepted.

INSERT INTO dbo.Sample_Constraints_2


VALUES('M5','Jupiter', 5);
Importing Records from Excel
There may be times when the data you want to insert into a table
already exists in an Excel spreadsheet. There are various methods
for importing records. You can use the built-in Import Data feature in
SQL Server Management Studio or you can use distributed queries
through OLE DB (Object Linking and Embedding, DataBase).

Using the Import Data Feature


Creating a New Table from an Excel Spreadsheet
To run the following example, you’ll need to do some prep work:

1. Create the folder C:\SQLon your computer.


2. Create an Excel file named TestImport.xlsx in Word 2007.
3. Enter the information shown in the image to the right.
4. Rename the worksheet from Sheet1 to TestImportData.

To import the data from the spreadsheet:

1. Right-click on the name of the database which will contain the


table, then select Tasks > Import Data.
2. Skip the Welcome page of the SQL Server Import and
Export Wizard and go directory to the Choose a Data
Source step.
3. On the Choose a Data Source page of the SQL Server
Import and Export Wizard, select the Data source (Microsoft
Excel).

4. In the Excel connection settings group box, enter or browse


for the Excel file path to the Excel file containing the
spreadsheet you're importing.
5. Select the Excel version of that file (Microsoft Excel 2007 will
be preselected if the file extension is .xlsx).
6. Check the First row has column names check box.
7. Click Next.
8. In the Choose a Destination dialog box, select a
Destination (SQL Server Native Client 10.0).

9. Select the Server Name (you will probably want to keep the
preselected name, which should be the server you're logged
into).
10. In the Authentication group box, select Use Windows
Authentication if it's not already selected.
11. The Database name you're working with should be
preselected, or you may choose a different database at this
time.
12. Click Next.
13. In the Specify Table Copy or Query dialog box, select Copy
data from one or more tables or views.
14. Click Next.
15. In the Select Source Tables and Views dialog box, select
TestImportData$ as the source, then click in the Destination
column and delete the $ at the end of the name as shown
below. (You are importing the sheet named TestImportData$
but the table name in SQL Server will now be
dbo.TestImportData.)

16. (Optional) Click the Preview button to see a preview of what


the table will look like.
17. Click Next.
18. In the Save and Run Package dialog box, select the Run
immediately check box.

19. Click Next to view a summary of your choices, or click Finish


to complete wizard.
After clicking Finish, you will see a running report of the import
progress. The image below indicates a successful import.

To see the imported table in the Object Explorer, right-click on the


name of the database where you inserted the table, and select
Refresh. then click on the plus sign (+) to the left of the new table
name:

To view the contents of the imported table, simply execute a normal


query on it:

SELECT * FROM dbo.TestImportData;


Janeway has a NULL value in the name column because that
column in that record of the spreadsheet was blank.

Appending Data to an Existing Table


The easiest way to append data, is to import a data from a file with a
sheet name that matches an existing table name. For example, in
the previous section you created a table named "Customers" using
the Import Data feature. If you want to add data from a different
Excel file, follow the same steps in the previous section for creating a
table with the following restrictions:

The sheet name must match the table name ("Customers"


in the previous example).
The columns must match.
All column headers must be removed.
The data you are appending cannot duplicate existing
primary key IDs.

For example, modify the existing TestImport.xlsx file by adding the


following data to Sheet2. (Do not include headers.)

Now follow the same procedure as earlier for importing the data with
the following changes:

MODIFIED STEP 6: In the Choose a Data Source dialog, deselect


First row has column names.
MODIFIED STEP 15: In the Select Source Tables and Views dialog,
select Sheet2$ as the source, then rename the destination to name
of the table you’re adding the records to (dbo.TestImportData).

The only progress line that’s should look different is the one that
shows the number of rows transferred (you transferred 3 rows the
first time).

Now a standard query on the table dbo.TestImportData displays the


following results:
Working with Indexes
Just like an index for a book, an index for a table is designed to save
you time when trying to look up information. For illustrative purposes
we'll be indexing columns in small tables, but when creating indexes
in the real world, you should only index in when the following
conditions exist. Otherwise, an index could end up adding
significantly more time to INSERT, UPDATE, and DELETE
statements. You should also keep in mind that indexes take up disk
space.

Indexes are most efficient when:


A table contains a large number of records.
Columns being indexed contain a large number of unique
values.
Columns being indexed return a small percentage of
records when filtered with a WHERE clause.

Creating an Index
There are four types of indexes you can create:
Single-column index
Unique index
Composite index
Implicit index (column index automatically created at the
time the table is created, such as for Primary keys and
Unique constraints)

An index can be either Clustered or Non-clustered (default).

Clustered indexes determine the physical order of the rows, i.e., the
table is resorted in the order of the indexing criteria. You can have
only one clustered index per table.

Non-clustered indexes, the table rows are not reordered. Instead, the
index is a pointer to the location of the data, which can be in any
order. All the indexes created in this lesson will be typical non-
clustered indexes.

Neither clustered nor non-clustered indexes can use columns of data


types TEXT, NTEXT, or IMAGE for indexing.

Single-Column Index
This type of index is used frequently in cases where a WHERE
clause is repeatedly used to retrieve an ID of some sort. Single-
column indexes are also frequently used on Foreign key columns.

The syntax for creating a single-column index is as follows:

CREATE INDEX index_name


ON table_name (column_name)

For example, to create an index on the Pname column in our


Sample_Constraints table, execute the following query:

USE Solar_System
CREATE INDEX Pname_IDX
ON Sample_Constraints (Pname);

Refresh your table in the Object Explorer, expand the Indexes folder
under the table name, and you'll see the name of the index you just
created. To see information about the index, right click on the index
name, then select Properties.

Unique Index
This type of index is the same as a single-column index, with the
added requirement that all values in that column must be unique
(e.g., a social security number, an employee ID, etc.).

The syntax for creating a unique index is as follows:

CREATE UNIQUE INDEX index_name


ON table_name (column_name)

Composite Index
This type of index is based on two or more columns that are typically
used together in WHERE and/or ORDER BY clauses. For example,
if, from the titles table in the pubs database, you often sorted by
title_id, pub_id, you could create a composite index on those
columns. If, in the authors table you often checked for specific values
of au_lname and au_fname, a composite index on those columns
might decrease your search time.

Be careful in creating composite indexes, however, as they can


negatively impact performance if querying tables with small amounts
of data. Composite indexes are best used with very large databases.

The syntax for creating a composite index is as follows:

CREATE INDEX index_name


ON table_name (column1_name, column2_name)

Deleting an Index
To delete an index, use the DROP INDEX command as follows:

DROP INDEX index_name ON table_name

or

DROP INDEX table_name.index_name


Transactions
A transaction is a series of SQL statements executed as a group. All
of the statements must complete execution or none of the
statements take effect. Therefore, if any of the statements in the
transaction cannot be executed, then the effects of all previous
statements are "rolled back" and the query ends. This is especially
useful if you want to delete all references to an ID across multiple
tables which contain that ID as both primary and foreign keys. For
example, if you have a book title you no longer sell, you would need
to delete it from all tables where the title ID appears. Because the
title ID exists as a foreign key in more than one table, you have to
make sure you delete all the references at the same time, otherwise
you'll get what's called a "referential integrity error." By putting these
statements in a transaction, you can ensure that none of the
statements get executed unless the removal is successful for every
statement.

In Microsoft SQL Server, all transactions start with the BEGIN


TRANSACTION statement and end with either a COMMIT or
ROLLBACK statement.

Ending with a COMMIT statement makes the changes made during


the transaction permanent. Ending with a ROLLBACK statement
leaves the data unchanged. You can only roll back changes made
since the last COMMIT or ROLLBACK command, however.

Before we get to the transaction example in this section, add the


following foreign key constraint to the Moons table in the
Solar_System database:

USE Solar_System
ALTER TABLE dbo.Moons
ADD CONSTRAINT fk_moons
FOREIGN KEY(PID) REFERENCES dbo.Planets(PID);

Now if you try to delete the planet Pluto from the dbo.Planets table:
DELETE FROM dbo.Planets
WHERE PID = '9';

You will receive the following error because the PID column in
dbo.Moons is “linked” to a column in the dbo.Planets table:

Now consider the following statements executed sequentially (but do


NOT execute them):
DELETE FROM dbo.Planets
WHERE PID = '9';
DELETE FROM dbo.Moons
WHERE PID = '9';
DELETE FROM dbo.Planet_Masses
WHERE PID = '9';
Executing the above statements would result in the following
messages:

Pluto would not be deleted from the dbo. Planets table due to an
error, yet it would be deleted from both the dbo.Moons and
dbo.Planet_Masses tables. This would destroy referential integrity
among the tables.
To maintain referential integrity, place your statements within a
Transaction, test for errors and, if found, ROLLBACK the Transaction
without committing it.

Now you can execute the following transaction:


USE Solar_System
BEGIN TRANSACTION

DELETE FROM dbo.Planets


WHERE PID = '9';
IF ( @@error <> 0) GOTO ERR_HANDLER

DELETE FROM dbo.Moons


WHERE PID = '9';
IF ( @@error <> 0) GOTO ERR_HANDLER

DELETE FROM dbo.Planet_Masses


WHERE PID = '9';
IF ( @@error <> 0) GOTO ERR_HANDLER

COMMIT
RETURN

ERR_HANDLER:
PRINT '*** Your transaction is being rolled back due to an error ***'
ROLLBACK TRANSACTION

The variable @@error is an INT system variable that is always


equal to zero except when an error has occurred.
The IF statement checks the @@error variable to see if an error
has occurred.
If no error occurs (the value is zero), then the transaction
executes the next statements in the series.
If an error does occur, then the query goes to the label
name specified after the GOTO keyword (in this case
ERR_HANDLER).
The COMMIT statement makes any changes to the database
permanent.
The RETURN statement ensures that the error handling section is
skipped unless the query is directed there by a GOTO statement.
The error handler section comes after the COMMIT statement so
that you can display a message and ROLLBACK the Transaction
when an error is encountered.
Because the first DELETE statement in the above query results in an
error, none of the remaining DELETE statements are executed.

To achieve the desired results, make sure you delete Pluto from the
dbo.Planets table after you’ve deleted it from the dbo.Moons table.
The revised Transaction follows:
USE Solar_System
BEGIN TRANSACTION

DELETE FROM dbo.Planet_Masses


WHERE PID = '9';
IF ( @@error <> 0) GOTO ERR_HANDLER

DELETE FROM dbo.Moons


WHERE PID = '9';
IF ( @@error <> 0) GOTO ERR_HANDLER

DELETE FROM dbo.Planets


WHERE PID = '9';
IF ( @@error <> 0) GOTO ERR_HANDLER

COMMIT
RETURN

ERR_HANDLER:
PRINT '*** Your transaction is being rolled back due to an error ***'
ROLLBACK TRANSACTION
Review 8
PREREQUISITES:
Before you begin this review, make sure you have created the
C:\SQL_ADV folder on your computer.
1. Add a CHECK constraint to the Sample_Constraints table
that does not allow the column Pname to contain any of the
characters 0 through 9 in any position.
2. Create a table in Excel with columns headers ID (this will be
your primary key), L_name, F_name, Gender, and
Profession. Insert at least 5 rows of data (you may leave any
column blank except ID and L_name). Save the Excel file in
your C:\SQL_ADV folder as Review8_Exercise1.xlsx.
SAMPLE SPREADSHEET:

3. Import the Excel data from Exercise 2 into SQL. Your table
name will be dbo.StarGate. You can leave the sheet name
as the default or name it something else. To import the data,
you may use either a distributed query or the Import Data
Feature in SQL Server Management Studio
4. Alter the ID column in the dbo.StarGate table so that the data
type is INT and NULL values are not allowed.
5. Execute the following as a single Transaction (include error
checking and an error handling section):
1. Alter the dbo.StarGate table to add a PRIMARY KEY
constraint on the ID column.
2. Try to insert the following record (HINT: you need an
error check after this statement):
INSERT INTO dbo.StarGate
VALUES (1,'Mitchell', 'Cameron','M','Farscape Refugee')
3. Create a Composite Index on the L_name and F_name
columns in the table you imported in Exercise #3.
4. Display the contents of the newly created table.

6. Fix the error in the Transaction from Exercise #5 and re-run


the query.

7. Remove the Index you created on the dbo.StarGate table.


APPENDICES
Appendix A: Installing SQL
Server 2008 R2 SP2 Express
Edition
Downloading the installation package
1. Go to Microsoft’s website at:
http://www.microsoft.com/en-us/download/details.aspx?id=30438
2. Click Download.
3. In the Choose the download you want window, scroll down
if necessary and select SQLEXPRWT_x64.exe if you have a
64-bit Windows operation system (SQLEXPRWT_x32.exe for
32-bit systems).
4. Click Next.
5. If your browser blocks the pop-up, you will need to unblock it
before you can continue. Once unblocked, you will see a
message asking if you want to run or save the file. Click Save
to save the file to your default download location. (To specify
a different download location, click the arrow to the right of
Save and select Save As.)

Installing the software


1. To begin the installation process, navigate to the folder
containing the downloaded file, then double-click on the file
name.
2. Follow the on-screen instructions.
3. In the SQL Server Installation Center dialog, click New
installation or add features to an existing installation.
4. In the SQL Server 2008 R2 Setup dialog License Terms
window, select I accept the license, then click Next.
5. In the Feature Selection window, select Select All, then click
Next.
6. In the Instance Configuration window, select Default
Instance, write down the displayed Instance ID
(MSSQLSERVER) and Instance Root Directory information
for later use, then click Next.
7. In the Server Configuration window, select Automatic for
both the SQL Server Database Engine and the SQL Server
Browser, then click Next.
8. In the Database Engine Configuration window, keep the
default settings, then click Next.
9. In the Error Reporting window, keep the default settings, then
click Next.
10. After the installation progress is complete, click Close.
11. Restart your computer.

Downloading the pubs database


The pubs database used in many of the examples in this book must
be installed separately. To download and install pubs:

1. Go to the Microsoft Download Center at:


http://www.microsoft.com/en-us/download/details.aspx?id=23654
(More information can be found at:
http://technet.microsoft.com/en-
us/library/ms143221(v=sql.105).aspx)
2. Click Download, to save the file SQL2000SampleDb.msi on
your computer.
3. Navigate to the folder where you saved
SQL2000SampleDb.msi, then double-click on the file name
to start the installation.
4. Follow the on-screen instructions, accepting all the defaults.
5. After the installation is complete, click Close.
6. After following the instructions in Connecting to your
database server, you can install the pubs database.
If you want to also install the AdventureWorks2008R2 Sample
Databases, go to
http://msdn.microsoft.com/en-us/library/aa992075.aspx.

Connecting to your database server


Click Start All Programs Microsoft SQL Server2008 R2 SQL
Server Management Studio

After you open SQL Server Management Studio, you will see a
“Connect to Server” dialog box similar to the following:

Click Connect and you will see a screen similar to the following:
Now you’re ready to install the pubs database.

Installing the pubs database


1. While in Microsoft SQL Server Management Studio, open a
new query window by clicking New Query in the toolbar.
2. In the menu bar, click File > Open > File and navigate to the
folder C:\SQL Server 2000 Sample Databases.
3. Select the saved query instpubs.sql, then click Open. The
contents of the file will appear in the query pane.
4. Execute your query by clicking the F5 key or the “! Execute”
button.
5. When you see the message “Query executed successfully”,
you will be able to see pubs in the list of databases.
To see a list of available databases on the server, click the plus (+)
symbol to the left of the Databases folder.

To see a list of tables in the pubs database, click the plus (+) symbol
to the left of pubs, then click the plus symbol to the left of Tables.
To see a list of column names and their assigned data types, click
the plus symbol to the left of any table name, then click the plus
symbol to the left of Columns.

To create a query, select a database in the Object Explorer pane


(“pubs” in this example), then click “New Query.” The query window
will open with the default query name:

To save a query, right click on the tab showing the current query
name, select “Save SQLQuery1.sql” then save it as the name you
choose in whatever location you’ve set up to hold your query files.
Appendix B: Tables and
Columns in the Pubs Database
Below is a screen shot from SQL Server Management Studio
showing a portion of the pubs database with the columns information
for the authors, titleauthor, and titles tables expanded, along with a
sample query:

To see a list of all table names and their fields, execute the following
query:

USE pubs
SELECT TABLE_NAME, COLUMN_NAME
FROM INFORMATION_SCHEMA.COLUMNS;
Appendix C: Solutions to
Review Exercises
Review 1 Solutions
1. Using the pubs database, query the dbo.authors table to
display all authors’ first and last names in ascending order by
last name, then by first name.
SOLUTION:
USE pubs
SELECT au_fname, au_lname
FROM dbo.authors
ORDER BY au_lname, au_fname;
2. Using the pubs database, query the dbo.authors table to
display the first and last names of all authors whose last
name begins with the letter “G”.
SOLUTION:
SELECT au_fname, au_lname
FROM dbo.authors
WHERE au_lname LIKE 'G%';

3. Using the pubs database, query the dbo.authors table to


display last names, city, and state for all authors living in CA
but do not live in Oakland. Sort in descending order by city.
SOLUTION:
SELECT au_lname, city, state
FROM dbo.authors
WHERE state = 'CA'
AND city <> 'Oakland'
ORDER BY city DESC;

4. Using the pubs database, query the dbo.titles table to display


the title id, title, and price for all records with a NULL value in
their price field. (HINT: See the operators section of this
document.)
SOLUTION:
SELECT title_id, title, price
FROM dbo.titles
WHERE price IS NULL;

5. Using the pubs database, query the dbo.titles table to display


all titles and prices, where the price is between $2.00 and
$10.00 (inclusive).
SOLUTION:
SELECT title, price
FROM dbo.titles
WHERE price BETWEEN 2 AND 10;

6. Using the pubs database, query the dbo.titles table to display


all titles that do not start with the letter “T” in ascending order
by title.
SOLUTION:

SELECT title
FROM dbo.titles
WHERE NOT title LIKE 'T%'
ORDER BY title;
7. Using the pubs database, query the dbo.titles table to display
the title and price for all books whose price is 2.99, 14.99, or
19.99.
SELECT title, price
FROM dbo.titles
WHERE price IN (2.99, 14.99, 19.99)
ORDER BY price;

8. Using the pubs database, display a list of all publisher IDs


and names, in ascending order by publisher name. (This is a
simple query. The challenge is in determining which table you
need to query, and the column names you should select.)
SOLUTION:
SELECT pub_id, pub_name
FROM dbo.publishers
ORDER BY pub_name;
9. Using the pubs database, query the dbo.employee table to
display records only for those employees not having a middle
initial. Sort in descending order first by the employee’s last
name.
SOLUTION:
SELECT *
FROM dbo.employee
WHERE minit = ' ';
ORDER BY lname DESC ;
Review 2 Solutions
1. Using the pubs database, query the dbo.titles table to find
the total amount of advances paid for all books. Use the alias
Total_Advances for the results column header.
SOLUTION:
SELECT SUM(advance) AS Total_Advances
FROM dbo.titles;

2. Using the pubs database, query the dbo.titles table to find


the total number of psychology books. Use
Psychology_Count as the column header for the results.
SOLUTION:
SELECT COUNT([type]) AS Psychology_Count
FROM dbo.titles
GROUP BY [type]
HAVING [type] = 'psychology';

3. Using the pubs database, query the dbo.titles table to list the
book type and the number of publishers that carry each type
of book, ignoring any title with the type UNDECIDED. Use the
column alias “Num. pubs publishing this type” for your
calculated value.
SOLUTION:
SELECT [type],
COUNT(pub_id) AS "Num. pubs publishing this type"
FROM dbo.titles
GROUP BY [type]
HAVING [type] <> 'UNDECIDED';

4. Using the pubs database, query the dbo.authors table to list


the state and the number of authors living in each state. Use
the alias “Authors per state” for the results column. (HINT:
You are counting the number of times each state appears in
the table.)
SOLUTION:
SELECT state, COUNT(state) AS "Authors per state"
FROM dbo.authors
GROUP BY state;
or

SELECT state, COUNT(au_id)


AS "Authors per state"
FROM dbo.authors
GROUP BY state;

5. Using the pubs database, query the dbo.titleauthor table to


list the author’s ID and the number of titles for each author
having more than one book title associated with his name.
Use the alias Num_titles for the results column.
HINTS:
Use GROUP BY and the dbo.titleauthor table.
We know we need author info (au_id) and that we’re
counting titles (title_id). But we don’t just want a general
count of all titles. We want the number of titles grouped
by author. Additionally, we only want to display a record
if the number of titles for that author is greater than 1.
SOLUTION:
SELECT au_id, COUNT(title_id) AS Num_titles
FROM dbo.titleauthor
GROUP BY au_id
HAVING COUNT(title_id) > 1;

6. Using the pubs database, query the dbo.titles table to list


each publisher’s ID and the number of different types of
books it sells. (HINT: you want to count distinct values.) Use
the alias “Types of book sold” for the results column. Save
this query. You will modify it in Exercise #7.
SOLUTION:
SELECT pub_id,
COUNT(DISTINCT[type]) AS "Types of Books Sold"
FROM dbo.titles
GROUP BY pub_id
7. Modify the query you created in Exercise 6 to display only
publishers who publish more than 2 types of books.
SOLUTION:
SELECT pub_id,
COUNT(DISTINCT[type]) AS "Types of Books Sold"
FROM dbo.titles
GROUP BY pub_id
HAVING COUNT(DISTINCT[type]) > 2;

8. Query the dbo.sales table in the pubs database using an


OVER (PARTITION BY) clause. You want to display each
order number, and sum of all books ordered for that order
number. Do not repeat order numbers in the results.
SOLUTION:
USE pubs
SELECT DISTINCT ord_num,
SUM(qty) OVER(PARTITION BY ord_num) AS "Total Qty
ordered"
FROM dbo.sales
ORDER BY ord_num;
Review 3 Solutions
1. Use an explicit Inner Join to query the dbo.titles and
dbo.publishers tables from the pubs database in order to
display the publisher’s name, the title, and the publication
date for each title. Use table aliases and sort the results first
by publisher name then by title.
HINT: To figure out what column to use when comparing tables,
search for what is probably the one column name they have in
common.
SOLUTION:
SELECT pub_name, title, pubdate
FROM dbo.titles AS t INNER JOIN dbo.publishers AS p
ON t.pub_id = p.pub_id
ORDER BY pub_name, title;

2. Modify the following query to change the display format of


“Firstname Lastname” to “Lastname, Firstname” and give the
column the alias “Author”.
SOLUTION:
SELECT au_lname + ', ' + au_fname AS "Author", title
FROM dbo.authors AS a,
dbo.titles AS t,
dbo.titleauthor AS ta
WHERE ta.title_id = t.title_id
AND ta.au_id = a.au_id
ORDER BY title;

3. Using an implicit Inner Join, query the dbo.publishers and


dbo.titles tables in the pubs database to list all publishers
who pay their authors a royalty higher than 10 percent (the
percentages are listed as whole numbers in the table)

SOLUTION:
SELECT pub_name, royalty
FROM dbo.publishers, dbo.titles
WHERE dbo.publishers.pub_id = dbo.titles.pub_id
AND royalty > 10;
4. Using either an implicit Inner Join or an explicit Inner Join,
query the dbo.publishers and dbo.titles tables in the pubs
database to list all publishers who pay their authors a royalty
higher than 10 percent, but list each publisher’s name only
once. Use the column alias “Pubs with high royalty” for the
publisher name column.

SOLUTION:
SELECT DISTINCT pub_name AS "Pubs with high royalty"
FROM dbo.publishers, dbo.titles
WHERE dbo.publishers.pub_id = dbo.titles.pub_id
AND royalty > 10;

or

SELECT DISTINCT pub_name AS "Pubs with high royalty"


FROM dbo.publishers INNER JOIN dbo.titles
ON dbo.publishers.pub_id = dbo.titles.pub_id
AND royalty > 10;

5. Using an explicit Inner Join to query the dbo.titles and


dbo.roysched tables in the pubs database, display all titles
and the average “lorange” and “hirange” associated with each.
Group by title and use the following aliases:

Column Alias
title Book Title
average lorangeAverage Low
average hirangeAverage High

SOLUTION:
SELECT title AS "Book Title",
AVG(lorange) AS "Average Low",
AVG(hirange) AS "Average High"
FROM dbo.titles AS t INNER JOIN dbo.roysched AS r
ON t.title_id = r.title_id
GROUP BY title;

6. You will query the dbo.titles and dbo.roysched tables in for


this exercise. Use an Outer Join to display titles and
corresponding royalties from the dbo.titles table even when
there is no matching title_id in the dbo.roysched table. Be
sure to list each title only once.

SOLUTION:
SELECT DISTINCT title, t.royalty
FROM dbo.titles AS t LEFT OUTER JOIN dbo.roysched AS r
ON t.title_id = r.title_id
ORDER BY title;

With the LEFT OUTER JOIN, the two titles from the titles table
that have no corresponding titlle_id in royalty schedule table are
also displayed.

7. Using an implicit Inner Join, query the dbo.authors, dbo.titles,


and dbo.titleauthors tables to list all authors (last name only),
their book titles, and the royalty they were paid for each title,
only if the royalty is not NULL. Use aliases for the table
names, and sort the results in ascending order by author’s
last name, then by title.

SOLUTION:
SELECT au_lname, title, royalty
FROM dbo.authors AS a,
dbo.titles AS t, dbo.titleauthor AS ta
WHERE a.au_id = ta.au_id AND t.title_id = ta.title_id
AND NOT royalty IS NULL
ORDER BY au_lname, title;

8. Write two queries joining the dbo.titles with the dbo.sales


tables to display the title, qty, and stor_id columns. The first
query will display the columns, even if there are no matching
title IDs in the dbo.sales table. The second query will display
the columns even if there are no matching values in the
dbo.titles table. Use aliases for the table names, and sort the
results in ascending order by qty.
Review 4 Solutions
1. Use a UNION statement to unite two queries on the dbo.titles
tables in the pubs database. The first query will display the
title, price, and advance for all titles with a price of 2.99. The
second query will display the same information for all titles
with an advance less than or equal to 5000.

SELECT title, price, advance


FROM dbo.titles
WHERE price = 2.99

UNION

SELECT title, price, advance


FROM dbo.titles AS T
WHERE advance <= 5000
ORDER BY title;

2. Using the pubs database, query the dbo.employee table to


display the last name, first name, and hire date for every
employee who was hired in the year 1991, in descending
order by hire date.
SOLUTION:

SELECT lname, fname, hire_date


FROM dbo.employee
WHERE DATEPART(yy, hire_date) = 1991
ORDER BY hire_date DESC;

3. Using the pubs database, query the dbo.employee table to


display the last name and hire date of every employee who
was hired after 1992. Display in descending order by hire
date. (You will be modifying this query in exercise #4 below.)

SOLUTION:

SELECT lname, hire_date


FROM dbo.employee
WHERE DATEPART(yy, hire_date) > 1992
ORDER BY hire_date DESC;
4. Modify the query you created in Exercise 3 to display the hire
date column name as “Hire Date” and the hire date values in
the format mm-dd-yy.

SOLUTION:

SELECT lname,
CONVERT(VARCHAR(8), hire_date, 10) AS "Hire Date"
FROM dbo.employee
WHERE DATEPART(yy, hire_date) > 1992
ORDER BY hire_date DESC;

5. Using the pubs database, query the dbo.employee table to


display the last name of each employee whose last name
begins with the letter “L” and the year that person was hired.
Use “Year Hired” as the column name for the year. Sort in
ascending order by employee’s last name.

SOLUTION:

SELECT lname,
DATEPART(yy, hire_date) AS "Year Hired"
FROM dbo.employee
WHERE lname LIKE 'L%'
ORDER BY lname;
6. Using the pubs database, query the dbo.employee table to
display the last name of every employee whose last name
begins with the letter “L”, and the hire date in three separate
columns—one for year, one for month, and one for day of
year. Use “Year Hired”, “Month Hired”, and “Days into Year
Hired”, respectively, as the column names for the date parts.
Sort in ascending order by employee’s last name.

SOLUTION:

SELECT lname,
DATEPART(yy, hire_date) AS "Year Hired",
DATEPART(mm, hire_date) AS "Month Hired",
DATEPART(dy, hire_date) AS "Days into Year Hired"
FROM dbo.employee
WHERE lname LIKE 'L%'
ORDER BY lname;
Review 5 Solutions
1. Create a table named Astronomers with the following column
names and data types. In the same query insert the values
into the table. (Don ' t delete this table, you will be using to it
throughout this review.) Save this query as
Review5Exercise1.sql in the default “Projects” folder;
you will need to execute it again in Exercise 7 of this
review and in Exercise 1 of Review 6.
Fields
AsId—This is the Astronomer ID. Data type: CHAR(2). Use as
the primary key.
Fname—Astronomer ' s first name. Data type: VARCHAR(20).
This field may contain a NULL value.
Lname—Astronomer ' s last name. Same data type as Fname.
This field cannot contain a NULL value.
Born—Date the astronomer was born. Data type: DATE. This field
may contain a NULL value.
Died—Date the astronomer died. Data type: DATE. This field may
contain a NULL value.

Values (Enter dates as a string in the format YYYY-MM-DD.)


William Herschel, born November 15, 1738, died August 25,
1822.
Clyde Tombaugh, born February 4, 1906, died January 17, 1997.

SOLUTION:
USE Solar_System

CREATE TABLE Astronomers


(
AsId CHAR(2) NOT NULL PRIMARY KEY,
Fname VARCHAR(20),
Lname VARCHAR(20),
Born DATE,
Died DATE
);

INSERT INTO Astronomers


VALUES ('01','William','Herschel','1738-11-15','1822-08-25'),
('02','Clyde','Tombaugh','1906-02-04','1997-01-17');

2. Add a column named Object_Discovered to the


Astronomer ' s table. Data type: VARCHAR(20). This field
may contain a NULL value.
SOLUTION:
USE Solar_System
ALTER TABLE dbo.Astronomers
ADD Object_Discovered VARCHAR(20)NULL;

3. Update the values in the new Object_Discovered column.


Value for William Herschel: Uranus
Value for Clyde Tombaugh: Pluto

SOLUTION:
USE Solar_System

UPDATE dbo.Astronomers
SET Object_Discovered ='Uranus'
WHERE Lname ='Herschel';

UPDATE dbo.Astronomers
SET Object_Discovered ='Pluto'
WHERE Lname ='Tombaugh';
4. Add a record to the Astronomer ' s table. (Remember to enter
dates as a string in the format YYYY-MM-DD.)
Values
AsId—3
Fname—Galileo
Lname—Galilei
Born—February 15, 1564
Died—January 8, 1642
Object_Discovered—Io

SOLUTION:
USE Solar_System
INSERT INTO dbo.Astronomers
VALUES ('03','Galileo','Galilei','2/15/1564','1/8/1642','Io');

5. Create a View of the Astronomer ' s table you created in


Exercise 1. The view should contain the AsId, Fname, and
Lname columns from the table.

SOLUTION:
CREATE VIEW dbo.View_Astronomers
AS
SELECT AsId, Fname, Lname
FROM dbo.Astronomers
GO

6. Remove from the Astronomers table the record you just


added.
SOLUTION:

DELETE FROM dbo.Astronomers


WHERE Fname ='Galileo';
or
DELETE FROM dbo.Astronomers
WHERE AsId ='03';

You can use column in the WHERE clause that uniquely identifies
the record to be deleted. In this example, we knew there was only
one record containing the first name Galileo. To be certain you are
deleting the correct record from a table with a large number of
records, either use the Primary Key column as the identifier, or
multiple criteria such as first and last names (for example,
WHERE Fname = ' firstname ' AND Lname = ' lastname ' ).

Now execute the query:


SELECT *
FROM dbo.View_Astronomers
Note that the record you just deleted from the dbo.Astronomers
table is now gone from the view dbo.View_Astronomers. Since
Views are dynamically created objects, when anything changes in
the tables they are created from, those changes are automatically
reflected in the View.

7. Drop the Astronomers table from the database.


SOLUTION:
USE Solar_System
DROP TABLE dbo.Astronomers;

Notice that the View you created from this table appears to
remain, however if you try to execute SELECT * FROM
dbo.View.Astronomers, you will receive the following error:

This is because the dbo.Astronomers table, from which the View


is created, no longer exists. You will re-create the table in
Exercise 8.

8. Re-create the Astronomers table from the query you saved in


Exercise 1.
SOLUTION:
On the Menu Bar, click File > Open > File..., click on the name of
your query in the Projects folder, then click OK. Once the file
appears in the query window, press the F5 key to execute it.
Refresh the tables under Solar_System in the Object Explorer to
verify the table exists. You may also run the query SELECT *
FROM dbo.Astronomers.
If you now run the query SELECT * FROM
dbo.View_Astronomers, you will no longer receive an error.
9. Create a View named dbo.Review1_Exercise9 that contains
the name of each planet from dbo.Planets that matches a
planet name in dbo.Moons and the name of each moon that
has a diameter greater than 500.
SOLUTION:
CREATE VIEW dbo.Review1_Exercise9
AS
SELECT p.Planet, m.Moon
FROM dbo.Planets AS p, dbo.Moons AS m
WHERE p.PID = m.PID AND m.Diameter_km > 500
GO
When the resulting view will contain the following 16 records:
Review 6 Solutions
1. Display the first name, the length of the first name, the last
name, and the length of the last name for each astronomer in
the dbo.Astronomers table you re-created in Review 5. Be
sure to use an alias for the calculated column names.
SOLUTION:
SELECT Fname,
LEN(Fname) AS "Length",
Lname,
LEN(Lname) AS "Length"
FROM dbo.Astronomers;

2. Using the CHARINDEX( ) function, display all moons having


"i" as the second character in the name. (Case-sensitivity is
not required.) Instead of the column name Moon, use the
alias "Moon with 'i' as 2nd char". (Save this query, you'll
need it for Exercise 3.)
SOLUTION:
SELECT Moon AS "Moon with 'i' as 2nd char"
FROM dbo.Moons
WHERE CHARINDEX('i', Moon) = 2;
3. Modify the query in Exercise 2 above to display one column
with the name of the moon concatenated with the name of its
Planet. Insert the string ' = Moon of ' between the two. Use
the column alias "Moons and their Planets" for the result.
(Save this query; you will need it in Exercise 4.)
SOLUTION:
SELECT Moon + ' = Moon of ' + Planet
AS "Moons and their Planets"
FROM dbo.Planets AS p, dbo.Moons AS m
WHERE p.PID = m.PID
AND CHARINDEX('i', Moon) = 2
ORDER BY Moon;

4. Re-do the query from Exercise 3 using SUBSTRING instead


of CHARINDEX to achieve the same results displayed in
Exercise 3.
SOLUTION:
SELECT Moon + ' = Moon of ' + Planet
AS "Moons and their Planets"
FROM dbo.Planets AS p, dbo.Moons AS m
WHERE p.PID = m.PID
AND SUBSTRING(Moon, 2, 1) = 'i'
ORDER BY Moon;

5. (OPTIONAL) The equation F=ma (Force = mass x


acceleration) is well known in physics. We have that
information in our Planet_Masses table, with Weight_lb being
equivalent to Force, Gravity_mps2 equivalent to the
acceleration, and the Mass of the Person as the calculated
value Weight_lb / Gravity_mps2.
You will display the following:
PID
Weight_lb multiplied by 2.2 (to get kilograms) using the
alias "Force (Newtons)"
Weight_lb multiplied by 2.2 divided by Gravity_mps2 using
the alias "Mass of Person (kg)"
Gravity_mps2 using the alias "Acceleration (m/sec**2)"
SOLUTION:
SELECT PID, weight_lb * 2.2 AS "Force (Newtons)",
weight_lb * 2.2 / Gravity_mps2 AS "Mass of Person (kg)",
Gravity_mps2 AS "Acceleration (m/sec**2"
FROM dbo.Planet_Masses;
Review 7 Solutions
1. Using a subquery in a WHERE clause, display the name,
diameter, and moon ID of each moon having a diameter
greater than 50% of that of Mars.

SOLUTION:

USE Solar_System
SELECT Moon, Diameter_km
FROM dbo.Moons
WHERE Diameter_km >
(SELECT Diameter_km * .5
FROM dbo.Planets
WHERE Planet = 'Mars')
;

2. Display the minimum and maximum planetary distances from


the sun along with the name of the planet at each of these
distances. (HINT: Use two INs linked by an OR and separate
subqueries in the WHERE clause for the MIN and MAX
functions.)

SOLUTION:

SELECT Planet, Distance_AU


FROM dbo.Planets
WHERE Distance_AU IN
(SELECT MIN(Distance_AU) FROM dbo.Planets)
OR
Distance_AU IN
(SELECT MAX(Distance_AU) FROM dbo.Planets)
;

3. Using a Simple CASE statement, query the dbo.Planets table


to display the name of each Planet and a new column that
displays either YES or No depending on the value in the
Num_Moons column. If the number of moons is zero (0), then
display “No”. Otherwise, display “YES”.
SOLUTION

USE Solar_System
SELECT Planet, Moons=CASE Num_Moons
WHEN 0 THEN 'No'
ELSE 'YES'
END
FROM dbo.Planets;

4. (CHALLENGE QUESTION!) Using a Searched CASE


statement, query the dbo.Planets and dbo.Planet_Masses
table to display the name of each Planet and a new column
that displays “You weigh more”, “You weigh less” or “Weight
unchanged” depending on whether the Weight_lb column in
the planet_masses table is greater than the Weight_lb value
for the planet Earth in the planet_masses table. Sort in
ascending order by the PID value in the planets table. Give
the new column the alias “Weight”.
HINTS:
You are querying two tables, so use either an implicit or
explicit inner join (the solution shows an implicit inner
join).
Use a subquery as part of the WHEN clause, similar to
the example shown in the lesson.
SOLUTION

USE Solar_System
SELECT Planet, Weight=CASE
WHEN pm.Weight_lb > (SELECT Weight_lb
FROM dbo.Planet_Masses
WHERE PID = '3')
THEN 'You weigh more'
WHEN pm.Weight_lb < (SELECT Weight_lb
FROM dbo.Planet_Masses
WHERE PID = '3')
THEN 'You weigh less'
ELSE 'Weight unchanged'
END
FROM dbo.Planets AS p, dbo.Planet_Masses AS pm
WHERE p.PID = pm.PID
ORDER BY p.PID;
5. In Lesson 1 you were introduced to system views that
allowed you to retrieve a copious amounts of data about
tables, columns, etc. using INFORMATION_SCHEMA
subsets. For this exercise, you will create a stored procedure
called sp_review_7_3 that retrieves the following information
from the Solar_System database in three successive queries:
Query #1: Display column constraints (HINT: Query
CONSTRAINT_COLUMN_USAGE from
INFORMATION.SCHEMA.CONSTRAINTS.)
Query #2: Display table information. (HINT: Query TABLES from
INFORMATION.SCHEMA.CONSTRAINTS.)
Query #3: Execute the sp_tables procedure, retrieving only the
information related to
tables owned by the dbo user.

SOLUTION:
CREATE PROCEDURE sp_review_7_3
AS
BEGIN

SELECT *
FROM INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE;

SELECT *
FROM INFORMATION_SCHEMA.TABLES;

EXEC sp_tables @table_owner=dbo;


END
GO

6. Modify the sp_planet_categories report created earlier in the


lesson to create a report called sp_planet_sizes. Instead of
displaying distance categories, you will show two tables: one
displaying planets with a diameter greater than earth, the
other displaying planets with a diameter smaller than Earth.
Sort by diameter in ascending order.
HINT: You will need to use a subquery in the WHERE clause
similar to that in the example Subqueries on a Single Table.

SOLUTION:
SOLUTION (The symbol prefaces each line with a change; changed lines also
in red text):
CREATE PROCEDURE sp_planet_sizes
AS
BEGIN
DECLARE @tableHTML nVarchar(max)
DECLARE @subject nVarchar(max)
SET @subject = 'Planetary Report'
SET @tableHTML =
N'<html><head><title>Query Results</title></head><body>' +
N'<table width="75%" style="color: red; border: 1 solid black; padding: 1; text-
align: center">' +
N'<caption style="font-size: 24pt">Planets With Diameter Greater Than
Earth</caption>' +
N'<tr><th> Planet </th><th> Diameter(Km) </th><th> Distance(AU) </th>' +
N'<th> Distance(MKm) </th><th> Rotation(days) </th><th> Orbit(years) </th>
<th> # Moons </th><th> Position </th>'+
N'</tr>' +
CAST ( ( SELECT td = Planet, '',
td = Diameter_km, '',
td = Distance_AU, '',
td = Distance_Mkm, '',
td = Rotation_days, '',
td = Orbit_years, '',
td = Moons, '',
td = Position, ''
FROM dbo.Planets
WHERE Diameter_km >
(SELECT Diameter_km FROM dbo.Planets
WHERE Planet = ‘Earth')
ORDER BY Diameter_km
FOR XML PATH('tr'), TYPE
) AS NVARCHAR(MAX) ) +
N'</table>' +

N'<table width="75%" style="color: blue; border: 1 solid black; padding: 1; text-


align: center">' +
N'<caption style="font-size: 24pt">Planets With Diameter Less Than
Earth</caption>' +
N'<tr><th> Planet </th><th> Diameter(Km) </th><th> Distance(AU) </th>' +
N'<th> Distance(MKm) </th><th> Rotation(days) </th><th> Orbit(years) </th>
<th> # Moons </th><th> Position </th>'+
N'</tr>' +
CAST ( ( SELECT td = Planet, '',
td = Diameter_km, '',
td = Distance_AU, '',
td = Distance_Mkm, '',
td = Rotation_days, '',
td = Orbit_years, '',
td = Moons, '',
td = Position, ''
FROM dbo.Planets
WHERE Diameter_km <
(SELECT Diameter_km FROM dbo.Planets
WHERE Planet = ‘Earth')
ORDER BY Diameter_km
FOR XML PATH('tr'), TYPE
) AS NVARCHAR(MAX) ) +
N'</table>' +
N'</body><’html>’
SELECT @tableHTML
END
GO
Review 8 Solutions
1. Add a CHECK constraint to the Sample_Constraints table that
does not allow the column Pname to contain any of the
characters 0 through 9 in any position.
SOLUTION:
ALTER TABLE Sample_Constraints WITH NOCHECK
ADD CONSTRAINT CK_Pname_Alpha
CHECK (Pname NOT LIKE '[0-9]%' AND
Pname NOT LIKE '%[0-9]%' AND
Pname Not LIKE '%[0-9]'
);
2. Create a table in Excel with columns headers ID (this will be
your primary key), L_name, F_name, Gender, and Profession.
Insert at least 5 rows of data (you may leave any column blank
except ID and L_name). Save the Excel file in your
C:\SQL_ADV folder as Review8_Exercise1.xlsx.
SAMPLE TABLE:

3. Import the Excel data Review8_Exercise1.xlsx into the


Solar_System database. Your destination table name will be
dbo.StarGate.
SOLUTION:
1st. Right-click Solar_System in the Object Explorer,
select Tasks, then select Import Data as shown below:
2nd. Skip the Welcome page of the SQL Server Import
and Export Wizard and go directory to the Choose a Data
Source step. Set the data source as Microsoft Excel,
navigate to your file so that it appears in the Excel file path
box, and select First row has column names (as shown
below).

3rd. Click Next.


4th. In the Destination box of the Choose a Destination
page, select SQL Server Native Client 10.0. The server you
are logged into should be preselected. Select Use Windows
Authentication, then make sure the correct database is
displayed in the database box.

5th. Click Next.


6th. On the Specify Table Copy or Query page, select
Copy data from one or more tables or views.
7th. Click Next.
8th. On the Select Source Tables and View page, select
‘Sheet1$’ in the Source column, then in the Destination
column, change [dbo].[Sheet1$] to [dbo].[StarGate].

9th. Click Next.


10th. On the Save and Run Package page, select Run
immediately.
11th. Click Next to see a summary of your settings, or click
Finish to complete the wizard.
You should receive information similar to the following,
indicating the execution was successful:

4. Alter the ID column in the dbo.StarGate table so that the data


type is INT and NULL values are not allowed.
ALTER TABLE dbo.StarGate
ALTER COLUMN ID INT NOT NULL;

5. Execute the following as a single Transaction (include error


checking and an error handling section):
1. Alter the dbo.StarGate table to add a PRIMARY KEY
constraint on the ID column.
2. Try to insert the following record (HINT: you need an
error check after this statement):
INSERT INTO dbo.StarGate
VALUES (1,'Mitchell', 'Cameron','M','Farscape Refugee')
3. Create a Composite Index on the L_name and F_name
columns in the table you imported in Exercise #3.
4. Display the contents of the newly created table.
POSSIBLE SOLUTION:
BEGIN TRANSACTION

ALTER TABLE dbo.StarGate


ADD CONSTRAINT pk_StarGate PRIMARY KEY (ID)

IF ( @@error <> 0) GOTO EPIC_FAIL

INSERT INTO dbo.StarGate


VALUES (1,'Mitchell', 'Cameron','M','Farscape Refugee')

IF ( @@error <> 0) GOTO EPIC_FAIL

CREATE INDEX r3_e2


ON dbo.StarGate (L_name, F_name)

IF ( @@error <> 0) GOTO EPIC_FAIL

SELECT * FROM dbo.StarGate

IF ( @@error <> 0) GOTO EPIC_FAIL

COMMIT
RETURN

EPIC_FAIL:
PRINT '*** Your transaction is being rolled back due to an error ***'
ROLLBACK TRANSACTION
Running the above query produces the following error
messages because you are trying to use the number 1 as a
Primary Key more than once:

6. Fix the error in the Transaction from Exercise 5 and re-run the
query.
SOLUTION:
In the Transaction above, change the 1 to 6 in the INSERT INTO
statement:
INSERT INTO dbo.StarGate
VALUES (6,'Mitchell', 'Cameron','M','Farscape Refugee')

7. Remove the Index you created on the dbo.StarGate table.


SOLUTION:
DROP INDEX r3_e2
ON dbo.StarGate;
or
DROP INDEX dbo.StarGate.r3_e2;
Appendix D: Lesson Files
Rather than type in the queries shown in this appendix (some of them are
quite lengthy), you can download them as a ZIP file from the author’s
website.

1. Go to http://www.charlotteswebworld.com/downloads.html.
2. Right-click on the Saved Queries link, then save the zip file to the
default Projects folder or the folder of your choice.
NOTE:
C:\Users\<your user name>\Documents\SQL Server Management
Studio\Projects
is the default folder used by SQL Server Management Studio to save and
open files.

3. Navigate to the folder where you saved the file and unzip the file. You
will then be able to open and execute the saved queries in SQL
Server Management Studio.

CONTENTS OF ZIP FILE:

CreateAndPopulatePlanetsTable.sql
USE Solar_System
CREATE TABLE Planets
(
PID CHAR(1) NOT NULL PRIMARY KEY,
Planet VARCHAR(20) NOT NULL,
Diameter_km INT NULL,
Distance_AU DECIMAL(10,2) NULL,
Distance_Mkm INT NULL,
Rotation_days DECIMAL(10,2) NULL,
Orbit_years DECIMAL(10,2) NULL,
Num_Moons INT NULL
);

INSERT INTO dbo.Planets


VALUES ('1', 'Mercury', 4878, 0.38, 58, 58.6, 0.24, 0),
('2', 'Venus', 12100, 0.72, 108, 243.0, 0.62, 0),
('3', 'Earth', 12742, 1, 150, 1, 1, 1),
('4', 'Mars', 6792, 1.52, 228, 1.03, 1.89, 2),
('5', 'Jupiter', 139822, 5.20, 778, 0.41, 11.86, 50),
('6', 'Saturn', 116464, 9.53, 1429, 0.43, 29.46, 53),
('7', 'Uranus', 50724, 19.19, 2871, 0.75, 84.01, 27),
('8', 'Neptune', 49248, 30.06, 4504, 0.67, 164.79, 13),
('9', 'Pluto', 2324, 39.5, 5906, 6.39, 248, 5);

UpdatePlanetsTable.sql
/* Run the alter table query before trying to update, since
you have to add the column before you add the data:

ALTER TABLE dbo.Planets


ADD Density_gpcm3 FLOAT NULL;

*/

UPDATE dbo.Planets
SET Density_gpcm3 = 5.427 WHERE Planet ='Mercury'

UPDATE dbo.Planets
SET Density_gpcm3 = 5.243 WHERE Planet ='Venus'

UPDATE dbo.Planets
SET Density_gpcm3 = 5.513 WHERE Planet ='Earth'

UPDATE dbo.Planets
SET Density_gpcm3 = 3.934 WHERE Planet ='Mars'

UPDATE dbo.Planets
SET Density_gpcm3 = 1.326 WHERE Planet ='Jupiter'

UPDATE dbo.Planets
SET Density_gpcm3 = 0.687 WHERE Planet ='Saturn'

UPDATE dbo.Planets
SET Density_gpcm3 = 1.270 WHERE Planet ='Uranus'

UPDATE dbo.Planets
SET Density_gpcm3 = 1.638 WHERE Planet ='Neptune'

UPDATE dbo.Planets
SET Density_gpcm3 = 2.050 WHERE Planet ='Pluto';
UpdatePlanetsTable-2.sql
/* Execute this query separately before updating the table.

USE Solar_System
ALTER TABLE dbo.Planets
ADD Class CHAR(2) NULL;

*/

USE Solar_System
UPDATE dbo.Planets
SET Class = 'SL' WHERE Planet = 'Mercury'

UPDATE dbo.Planets
SET Class = 'SM' WHERE Planet = 'Venus'

UPDATE dbo.Planets
SET Class = 'SL' WHERE Planet = 'Earth'

UPDATE dbo.Planets
SET Class = 'SL' WHERE Planet = 'Mars'

UPDATE dbo.Planets
SET Class = 'GL' WHERE Planet ='Jupiter'

UPDATE dbo.Planets
SET Class = 'GL' WHERE Planet ='Saturn'

UPDATE dbo.Planets
SET Class = 'GM' WHERE Planet ='Uranus'

UPDATE dbo.Planets
SET Class = 'GL' WHERE Planet ='Neptune'

UPDATE dbo.Planets
SET Class = 'SM' WHERE Planet ='Pluto';

CreateAndPopulateMoonsTable.sql
USE Solar_System
CREATE TABLE Moons
(
Moon VARCHAR(20)NOT NULL PRIMARY KEY,
Diameter_km DECIMAL(10,2 )NULL,
PID CHAR(1) NOT NULL,
Discovery_Id INT NULL,
Distance_km INT NULL
);

INSERT INTO dbo.Moons


VALUES ('Moon', 3476,'3', 1, 384400),

('Phobos', 22.7,'4', 1, 9377),


('Deimos', 12.6,'4', 2, 23460),
('Io', 3630,'5', 1, 421800),
('Europa', 3138,'5', 2, 671000),
('Ganymede', 5262,'5', 3, 1070400),
('Callisto', 4800,'5', 4, 1882700),
('Almalthea', 166.9,'5', 5, 181400),
('Himalia', 170,'5', 6, 11461000),
('Elara', 86,'5', 7, 11741000),
('Pasiphae', 60,'5', 8, 23624000),
('Sinope', 38,'5', 9, 23939000),
('Lysithea', 36,'5', 10, 11717000),
('Carme', 46,'5', 11, 23404000),
('Ananke', 28,'5', 12, 21276000),
('Leda', 20,'5', 13, 11165000),
('Thebe', 98.6,'5', 14, 221900),
('Adrastea', 16.4,'5', 15, 129000),
('Metis', 43,'5', 16, 128000),
('Callirrhoe', 8.6,'5', 17, 24102000),
('Themisto', 8,'5', 18, 7507000),
('Megaclite', 5.4,'5', 19, 23808000),
('Taygete', 5,'5', 20, 23363000),
('Chaldene', 3.8,'5', 21, 23179000),
('Harpalyke', 4.4,'5', 22, 21104000),
('Kalyke', 5.2,'5', 23, 23564000),
('Iocaste', 5.2,'5', 24, 21272000),
('Erinome', 3.2,'5', 25, 23283000),
('Isonoe', 3.8,'5', 26, 23231000),
('Praxidike', 6.8,'5', 27, 21148000),
('Autonoe', 4,'5', 28, 24033000),
('Thyone', 4,'5', 29, 21192000),
('Hermippe', 4,'5', 30, 21300000),
('Aitne', 3,'5', 31, 23315000),
('Eurydome', 3,'5', 32, 23148000),
('Euanthe', 3,'5', 33, 21038000),
('Euporie', 2,'5', 34, 19339000),
('Orthosie', 2,'5', 35, 21164000),
('Sponde', 2,'5', 36, 23790000),
('Kale', 2,'5', 37, 23302000),
('Pasithee', 2,'5', 38, 23090000),
('Hegemone', 3,'5', 39, 23566000),
('Mneme', 2,'5', 40, 21036000),
('Aoede', 4,'5', 41, 23969000),
('Thelxinoe', 2,'5', 42, 21165000),
('Arche', 3,'5', 43, 23355000),
('Kallichore', 2,'5', 44, 23273000),
('Helike', 4,'5', 45, 21064000),
('Carpo', 3,'5', 46, 17078000),
('Eukelade', 4,'5', 47, 23322000),
('Cyllene',NULL,'5', 48,NULL),
('Kore', 2,'5', 49, 24486000),
('Herse', 2,'5', 50, 23405000),
('Mimas', 396.4,'6', 1, 185539),
('Enceladus', 502.2,'6', 2, 238037),
('Tethys', 1066,'6', 3, 294672),
('Dione', 1123.4,'6', 4, 377415),
('Rhea', 1528.6,'6', 5, 527068),
('Titan', 5150,'6', 6, 1221865),
('Hyperion', 270,'6', 7, 1500934),
('Iapetus', 147.1,'6', 8, 3560351),
('Erriapus', 10,'6', 9, 17611400),
('Phoebe', 213,'6', 10 ,12947913),
('Janus', 179,'6', 11, 151460),
('Epimetheus', 116.2,'6', 12, 151410),
('Helene', 35.2,'6', 13, 377420),
('Telesto', 24.8,'6', 14, 294710),
('Calypso', 21.4,'6', 15, 294710),
('Kiviuq', 16,'6', 16, 11311100),
('Atlas', 30.2,'6', 17, 137670),
('Prometheus', 46.2,'6', 18, 139380),
('Pandora', 81.4,'6', 19, 141720),
('Pan', 18.2,'6', 20, 133580),
('Ymir', 18,'6', 21, 23140400),
('Paaliaq', 22,'6', 22, 15024000),
('Tarvos', 15,'6', 23, 18263200),
('Ijiraq', 12,'6', 24, 11367400),
('Suttungr', 7,'6', 25, 19476700),
('Mundifari', 7,'6', 26, 18667900),
('Albiorix', 32,'6', 27, 16401100),
('Skathi', 8,'6', 28, 15614300),
('Siarnaq', 40,'6', 29, 18015400),
('Thrymr', 7,'6', 30, 20439600),
('Narvi', 7,'6', 31, 19417300),
('Methone', 3.2,'6', 32, 194440),
('Pallene', 5,'6', 33, 212280),
('Polydeuces', 2.6,'6', 34, 377200),
('Daphnis', 7.6,'6', 35, 136500),
('Aegir', 6,'6', 36, 20749800),
('Bebhionn', 6,'6', 37, 17117800),
('Bergelmir', 6,'6', 38, 19338300),
('Bestla', 7,'6', 39, 20278000),
('Farbauti', 5,'6', 40, 20387000),
('Fenrir', 4,'6', 41, 22454800),
('Fornjot', 6,'6', 42, 25151200),
('Hati', 6,'6', 43, 19775000),
('Hyrrokkin', 6,'6', 44, 18437300),
('Kari', 6,'6', 45, 22077100),
('Loge', 6,'6', 46, 23065500),
('Skoll', 6,'6', 47, 17663300),
('Surtur', 6,'6', 48, 22920400),
('Greip', 6,'6', 49, 18442200),
('Jarnsaxa', 6,'6', 50, 19356400),
('Tarqeq', 6,'6', 51, 17909900),
('Anthe', 1.8,'6', 52, 197700),
('Aegaeon', 0.6,'6', 53, 167500),
('Cordelia', 40.2,'7', 1, 49800),
('Ophelia', 42.8,'7', 2, 53800),
('Bianca', 51.4,'7', 3, 59200),
('Cressida', 79.6,'7', 4, 61800),
('Desdemona', 64,'7', 5, 62700),
('Juliet', 93.6,'7', 6, 64400),
('Portia', 135.2,'7', 7, 66100),
('Rosalind', 72,'7', 8, 69900),
('Mab', 48,'7', 9, 97736 ),
('Belinda', 80.6,'7', 10, 75300),
('Perdita', 30,'7', 11, 76417),
('Puck', 162,'7', 12, 86000),
('Cupid', 36,'7', 13, 74392),
('Miranda', 471.6,'7', 14, 129900),
('Francisco', 22,'7', 15, 4282900),
('Ariel', 1158,'7', 16, 190900),
('Umbriel', 1170,'7', 17, 266000),
('Titania', 5150,'7', 18, 436300),
('Oberon', 1523,'7', 19, 583500),
('Caliban', 72,'7', 20, 7231100),
('Stephano', 32,'7', 21, 8007400),
('Trinculo', 18,'7', 22, 8505200),
('Sycorax', 150,'7', 23, 12179400),
('Margaret', 20,'7', 24, 14146700),
('Propero', 50,'7', 25, 16276800),
('Setebos', 48,'7', 26, 17420400),
('Ferdinand', 20,'7', 27, 20430000),
('Triton', 2707,'8', 1, 354759),
('Nereid', 340,'8', 2, 5513818),
('Naiad', 66,'8', 3, 48227),
('Thalassa', 80,'8', 4, 50000),
('Despina', 150,'8', 5, 52526),
('Galatea', 176,'8', 6, 61953),
('Larissa', 194,'8', 7, 73548),
('Proteus', 420,'8', 8, 117646),
('Halimede', 62,'8', 9, 16611000),
('Psamathe', 40,'8', 10, 48096000),
('Sao', 44,'8', 11, 22228000),
('Laomedeia', 42,'8', 12, 23567000),
('Neso', 60,'8', 13, 49285000),
('Charon', 1212,'9', 1, 19600),
('Nix', 50,'9', 2, 39200),
('Hydra', 100,'9', 3, 57600),
('P4', 23.5,'9', 4, 48400),
('P5', 17.5,'9', 5, 95000);

UpdateMoonsTable.sql
USE Solar_System
UPDATE dbo.Moons
SET Diameter_km = 2,
Distance_km = 23800000
WHERE Moon ='Cyllene';
UPDATE dbo.Moons
SET Moon = 'Kerberos'
WHERE Moon ='P4';

UPDATE dbo.Moons
SET Moon = 'Styx'
WHERE Moon ='P5';

CreateAndPopulatePlanetMassesTable.sql
USE Solar_System
CREATE TABLE Planet_Masses
(
PID VARCHAR(1)NOT NULL PRIMARY KEY,
Base_kg FLOAT NOT NULL,
Exponent FLOAT NOT NULL,
Gravity_mps2 FLOAT NULL
);

INSERT INTO dbo.Planet_Masses


VALUES ('1', 3.3010, 23, 3.7),
('2', 4.8673, 24, 8.87),
('3', 5.9722, 24, 9.80665),
('4', 6.4169, 23, 3.71),
('5', 1.8981, 27, 24.79),
('6', 5.6832, 26, 10.4),
('7', 8.6810, 25, 8.87),
('8', 1.0241, 26, 11.15),
('9', 1.3090, 22, 0.66);

UpdatePlanetMassesTable.sql
/* Execute this query separately before updating the table data.

USE Solar_System
ALTER TABLE dbo.Planet_Masses
ADD Weight_lb INT NULL;

*/

USE Solar_System
UPDATE dbo.Planet_Masses
SET Weight_lb = 38 WHERE PID ='1'

UPDATE dbo.Planet_Masses
SET Weight_lb = 91 WHERE PID ='2'
UPDATE dbo.Planet_Masses
SET Weight_lb = 100 WHERE PID ='3'

UPDATE dbo.Planet_Masses
SET Weight_lb = 38 WHERE PID ='4'

UPDATE dbo.Planet_Masses
SET Weight_lb = 253 WHERE PID ='5'

UPDATE dbo.Planet_Masses
SET Weight_lb = 107 WHERE PID ='6'

UPDATE dbo.Planet_Masses
SET Weight_lb = 91 WHERE PID ='7'

UPDATE dbo.Planet_Masses
SET Weight_lb = 114 WHERE PID ='8'

UPDATE dbo.Planet_Masses
SET Weight_lb = 7 WHERE PID ='9';

CreateProcedure_sp_find_closest.sql
CREATE PROCEDURE sp_find_closest
AS
BEGIN

SELECT

(SELECT Planet
FROM dbo.Planets AS p1
WHERE m1.PID = p1.PID) AS "Name of Planet",

MIN(m1.Distance_km) AS "Distance to Closest Moon (km)",

(SELECT Moon FROM dbo.Moons


WHERE Distance_km IN (MIN(m1.Distance_km)))
AS "Name of Closest Moon"

FROM dbo.Moons AS m1
GROUP BY PID;

END
GO
CreateProcedure_sp_planet_categories.sql
CREATE PROCEDURE sp_planet_categories
AS
BEGIN
DECLARE @tableHTML nVarchar(max)
DECLARE @subject nVarchar(max)
SET @subject = 'Planetary Report'
SET @tableHTML =
N'<html><head><title>Query Results</title></head><body>' +
N'<table width="75%" style="color: red; border: 1 solid black; padding: 1; text-align:
center">' +
N'<caption style="font-size: 24pt">Inner Planets</caption>' +
N'<tr><th> Planet </th><th> Diameter(Km) </th><th> Distance(AU) </th>' +
N'<th> Distance(MKm) </th><th> Rotation(days) </th><th> Orbit(years) </th><th> #
Moons </th><th> Density </th>'+
N'</tr>' +
CAST ( ( SELECT td = Planet, '',
td = Diameter_km, '',
td = Distance_AU, '',
td = Distance_Mkm, '',
td = Rotation_days, '',
td = Orbit_years, '',
td = Num_Moons, '',
td = Density_gpcm3, ''
FROM dbo.Planets
WHERE PID <= 4
ORDER BY PID
FOR XML PATH('tr'), TYPE
) AS NVARCHAR(MAX) ) +
N'</table>' +

N'<table width="75%" style="color: blue; border: 1 solid black; padding: 1; text-align:


center">' +
N'<caption style="font-size: 24pt">Outer Planets</caption>' +
N'<tr><th> Planet </th><th> Diameter(Km) </th><th> Distance(AU) </th>' +
N'<th> Distance(MKm) </th><th> Rotation(days) </th><th> Orbit(years) </th><th> #
Moons </th><th> Density </th>'+
N'</tr>' +
CAST ( ( SELECT td = Planet, '',
td = Diameter_km, '',
td = Distance_AU, '',
td = Distance_Mkm, '',
td = Rotation_days, '',
td = Orbit_years, '',
td = Num_Moons, '',
td = Density_gpcm3, ''
FROM dbo.Planets
WHERE PID > 4
ORDER BY PID
FOR XML PATH('tr'), TYPE
) AS NVARCHAR(MAX) ) +
N'</table>' +
N'</body></html>'

SELECT @tableHTML
END
GO
Appendix E: Additional
Resources
Free DBMS Downloads
Microsoft SQL Server 2008 R2 SP2 Express Edition (includes
SQL Server 2008 R2 SP2 Management Studio Express):
http://www.microsoft.com/en-us/download/details.aspx?id=30438
Download pubs database:
http://www.microsoft.com/en-us/download/details.aspx?id=23654
MySQL for Windows:
http://dev.mysql.com/downloads/windows/
Oracle Database 11g Express Edition
http://www.oracle.com/technetwork/database/database-
technologies/express-edition/overview/index.html
IBM’s DB2 Express-C:
http://www-01.ibm.com/software/data/db2/express-
c/download.html

You might also like