You are on page 1of 276

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


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 0 If you have fields storing red,
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 planet
from1-24 (uses up 4 bytes/7 (currently 6,973,738,433), you
digits) or 25-53 (uses 8 would need a data type that can
bytes/15 digits). Therefore hold a number larger than the
the values can range from maximum INT value. In this
-1.79E + 308 through -2.23E case, you could use the FLOAT
- 308, 0 and 2.23E + 308 data type.
through 1.79E + 308.
DECIMAL(p,s) Fixed precision decimal If want to store or display a real
value with a maximum of s number value with a fixed
digits to the right of the number of digits after the
decimal point a maximum of decimal point, you would need
p digits total (p = number of to use the DECIMAL data type.
digits left of decimal point + A value such as Pi displayed to
s). Values can range from decimal places (3.141592)
-10^38 + 1 through 10^38 – would need a data type of at
1. 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 31, registered for a class, use the
9999 and time ranging from DATETIME data type.
00:00:00 through
23:59:59.997
DATE Date only, ranging from For a field containing a person’s
0001-01-01 through 9999- date of birth, you can use the
12-31. DATE data type.
TIME Time only, ranging from For a field containing the time
00:00:00.0000000 through in 24-hour format, use the
23:59:59.9999999. TIME data type.
MONEY Monetary value (8 bytes) For a field containing values
ranging from related to the US government’s
‑922,337,203,685,477.5808 deficit, we can only hope we
through never need a data type larger
922,337,203,685,477.5807. than MONEY.
SMALLMONEY Monetary value (4 bytes) For a field containing entries in
ranging from ‑214,748.3648 a personal checking account,
through 214,748.3647 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 have
KEY only one Primary Key.
FOREIGN A column in one table that references a Primary Key
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