Professional Documents
Culture Documents
Charlotte McGary
Copyright © 2014 Charlotte McGary. All Rights Reserved.
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:
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.
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):
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.
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:
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.
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 database_name
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.
FROM table_name
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;
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:
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].
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:
Find all titles whose price is between $10 and $25 and sort in ascending order
by price:
or
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.
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.
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:
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 '_ _ _ %';
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;
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.
Query #1:
SELECT 2 + 4 * 3; Result is 14.
Query #2:
SELECT (2 + 4) * 3; Result is 18.
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:
SELECT COUNT(*)
FROM dbo.titles;
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 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:
The results will correctly show that 19 authors wrote the 25 books in the
database.
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:
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.
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:
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).
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.
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.
If you divide one INT data type by another INT data type, the result may
surprise you, as in the following example:
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).
USE pubs
SELECT title, price,
price * 0.095 AS "9.5% Tax",
price + (price * 0.095) AS "Total"
FROM dbo.titles;
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;
Notice that the CONVERT function automatically rounds the number during the
conversion.
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 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:
The GROUP BY clause allows you to retrieve the number of titles for each
publisher in one query:
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.
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.
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:
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:
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:
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:
Now consider the following example, which displays all titles having more
than one author:
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;
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):
However, if you were to try to add the title to the SELECT statement in the
GROUP BY query, you would receive an error:
The OVER (PARTITION BY) clause allows you to add the title without an
error:
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.
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.
However, it would require less typing to assign an alias to each of the table
names as follows:
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.
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.
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.
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.
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 following query produces the same results with an explicit Inner Join:
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:
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.
As you can see, the outer join displayed the additional 5 rows containing
NULL values.
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.
In the following queries, the INNER JOIN, LEFT OUTER JOIN, and RIGHT
OUTER JOIN all produce different results:
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.)
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;
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:
UNION
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.
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.
Function Description
GETDATE() Retrieves the current system date and time
DATEPART() Returns specified part of date
SELECT GETDATE();
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:
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;
If you just want to compare years, we could have selected the year with
DATEPART instead of using CONVERT.
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:
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.
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:
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:
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:
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:
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
);
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.
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.
Let’s add fields named Position and Density to our Planets table:
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:
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”.
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:
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!
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:
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.
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.
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.)
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.
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.
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';
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);
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.
The following example displays all columns in the Planets table, sorted in
ascending order by Position:
SELECT * FROM dbo.Planets
ORDER BY Density_gpcm3;
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:
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;
Creating a View
To create a View, you use the CREATE VIEW statement as follows:
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.
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.
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.
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
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.
Case-insensitive query:
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:
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.
You can find the length of any string using the LEN() function, whose syntax is
as follows:
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( ).
USE Solar_System
SELECT Planet, PID AS "Position"
FROM dbo.Planets
WHERE SUBSTRING(Planet, LEN(Planet)-1, 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;
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;
USE Solar_System
SELECT Planet, CHARINDEX('p', Planet) AS "Index of 'p'"
FROM dbo.Planets
WHERE Planet LIKE '%p%'
ORDER BY Planet;
USE Solar_System
SELECT 'Planet' AS "Planet/Moon",
Planet AS "Name"
FROM dbo.Planets
UNION
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:
To produce the desired results, you must convert the distance to the VARCHAR
data type. Modify the preceding query as follows:
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:
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.
Next, we’ll multiply the output from the POWER function by Mass_kg:
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.
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:
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:
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.
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.
SELECT Planet
FROM dbo.Planets
WHERE NOT PID IN
(SELECT PID FROM dbo.Moons);
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 ;
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;
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.
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 ;
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.
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:
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;
Rename a column
USE Solar_System
GO
SP_RENAME 'table_name.old_col_name', 'new_col_name', 'COLUMN'
GO
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");
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.
EXEC sp_MSforeachtable
'SELECT "?" AS TableName, COUNT(*) AS NumberOfRows FROM ?';
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.)
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:
END
GO
SELECT
(SELECT Planet
FROM dbo.Planets AS p1
WHERE m1.PID = p1.PID) AS "Name of Planet",
END
GO
To run the stored procedure, open a new query window and execute the
following statement:
EXEC dbo.sp_find_closest;
When you run the above query, the “SELECT @z” statement displays the
contents of the variable @z in the results window.
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:
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:
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:
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.
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:
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.
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
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.
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 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:
Testing Constraints
To test the validity of data entered into the table you just created, we'll try to
enter invalid data.
(1 row(s) affected)
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.
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".
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.
USE Solar_System
CREATE TABLE Sample_Constraints_2
(
PID VARCHAR(2) NOT NULL PRIMARY KEY,
Pname VARCHAR(20) NOT NULL,
Position FLOAT NOT NULL
);
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:
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]')
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:
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.
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:
Janeway has a NULL value in the name column because that column in that
record of the spreadsheet was blank.
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).
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)
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.
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.).
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.
Deleting an Index
To delete an index, use the DROP INDEX command as follows:
or
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:
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.
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
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.
5. In the Feature Selection window, select Select All, then click Next.
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.
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
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;
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
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.
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.
UNION
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:
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.
SOLUTION:
USE Solar_System
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:
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 ' ).
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.
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.
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;
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:
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;
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;
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>' +
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;
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')
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.
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:
*/
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
);
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
);
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",
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>' +
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