SQL 57
SQL 57
ISBN: 978-1540422651
Ordering Information:
Special discounts are available on quantity purchases
by corporations, associations, and others. For details,
contact the publisher at
[email protected].
How to use this book
This edition of SQL Practice Problems assumes that
you have some basic background knowledge about
relational databases and tables. However, I’ve added
some beginner level questions to gradually introduce
the various parts of the SQL Select statement for those
with less experience in SQL.
A note on the database used—the database used for
these problems, which you will set up in the in the
Installation Instructions, is not the standard Northwind
database. There have been multiple modifications
made to it, including additional tables, and modified
data, to support the problems in this book. Do not try
to use the standard Northwind sample database that
came with previous installations of SQL Server, many
of the problems will not work.
Do you need to finish all the problems? Absolutely
not. The introductory problems are fairly simple, so
you may want to skip directly to the Intermediate
Problems section. If you’re not a beginner, but not sure
where you should start, just take a look at the
problems and expected results in the Introductory
Problems section and make sure you understand the
concepts. If you do, start working on the Intermediate
Problems section.
If you’re uncertain about how to start on a problem,
the hints are designed to gradually walk you through
how to approach each problem. Try hard to solve the
problems first without the hints! The information will
stick better if you can do that. But if you’re stuck, the
hints will get you starting in thinking with a data
mindset.
If there’s code you want to copy from this book and
run on your server—believe it or not, I recommend that
you actually type it out, instead of copying and
pasting. Why go to the hassle of re-typing something?
Science shows that the act of typing establishes it
more firmly in your mind. Sometimes when you just
copy and paste, the code just goes directly from one
window in your computer to another, without making
much impression on your memory. But when you type
it out, you have to focus much more, and that helps
tremendously with retaining the information.
Should you search online for answers, examples, etc.?
Absolutely. I expect you do research online throughout
the book, and in many places it’s necessary. I do not
include all the syntax in this book. In my day-to-day
work as a data engineer, I would be lost without being
able to do online research. Sometimes I search online
just for a reminder of a certain syntax, sometimes for
examples of a particular type of code, and sometimes
for approaches to specific problems. Learning to find
answers online effectively can cut your problem-
solving time dramatically.
Once you finish all the questions, you’ll have some
very useful skills in data analysis and advanced Select
statement usage. This isn’t all there is to SQL, of
course. There’s also the syntax that let’s you actually
modify data (update, insert, delete), DDL (data
definition language, i.e. how to create and modify
database objects), programming concept such as stored
procedures, and of course many other topics.
In this book, I’m only presenting problems involving
retrieving data with Select statements, because that’s
an area where it’s hard for people to get solid practice
with real life data problems, without actually working
as a data engineer or programmer. It’s also a critical
first step for almost any of the other database topics.
Any feedback would be greatly appreciated. For any
questions or issues, please send email to
[email protected] and I will be
happy to respond.
Thank you for purchasing this book!
Table of Contents
How to use this book
Setup
Introductory Problems
1. Which shippers do we have?
2. Certain fields from Categories
3. Sales Representatives
4. Sales Representatives in the United States
5. Orders placed by specific EmployeeID
6. Suppliers and ContactTitles
7. Products with “queso” in ProductName
8. Orders shipping to France or Belgium
9. Orders shipping to any country in Latin America
10. Employees, in order of age
11. Showing only the Date with a DateTime field
12. Employees full name
13. OrderDetails amount per line item
14. How many customers?
15. When was the first order?
16. Countries where there are customers
17. Contact titles for customers
18. Products with associated supplier names
19. Orders and the Shipper that was used
Intermediate Problems
20. Categories, and the total products in each
category
21. Total customers per country/city
22. Products that need reordering
23. Products that need reordering, continued
24. Customer list by region
25. High freight charges
26. High freight charges - 2015
27. High freight charges with between
28. High freight charges - last year
29. Inventory list
30. Customers with no orders
31. Customers with no orders for EmployeeID 4
Advanced Problems
32. High-value customers
33. High-value customers - total orders
34. High-value customers - with discount
35. Month-end orders
36. Orders with many line items
37. Orders - random assortment
38. Orders - accidental double-entry
39. Orders - accidental double-entry details
40. Orders - accidental double-entry details, derived
table
41. Late orders
42. Late orders - which employees?
43. Late orders vs. total orders
44. Late orders vs. total orders - missing employee
45. Late orders vs. total orders - fix null
46. Late orders vs. total orders - percentage
47. Late orders vs. total orders - fix decimal
48. Customer grouping
49. Customer grouping - fix null
50. Customer grouping with percentage
51. Customer grouping - flexible
52. Countries with suppliers or customers
53. Countries with suppliers or customers, version 2
54. Countries with suppliers or customers - version
3
55. First order in each country
56. Customers with multiple orders in 5 day period
57. Customers with multiple orders in 5 day period,
version 2
ANSWERS
Introductory Problems
1. Which shippers do we have?
2. Certain fields from Categories
3. Sales Representatives
4. Sales Representatives in the United States
5. Orders placed by specific EmployeeID
6. Suppliers and ContactTitles
7. Products with “queso” in ProductName
8. Orders shipping to France or Belgium
9. Orders shipping to any country in Europe
10. Employees, in order of age
11. Showing only the Date with a DateTime field
12. Employees full name
13. OrderDetails amount per line item
14. How many customers?
15. When was the first order?
16. Countries where there are customers
17. Contact titles for customers
18. Products with associated supplier names
19. Orders and the Shipper that was used
Intermediate Problems
20. Categories, and the total products in each
category
21. Total customers per country/city
22. Products that need reordering
23. Products that need reordering, continued
24. Customer list by region
25. High freight charges
26. High freight charges - 2015
27. High freight charges with between
28. High freight charges - last year
29. Inventory list
30. Customers with no orders
31. Customers with no orders for EmployeeID 4
Advanced Problems
32. High-value customers
33. High-value customers - total orders
34. High-value customers - with discount
35. Month-end orders
36. Orders with many line items
37. Orders - random assortment
38. Orders - accidental double-entry
39. Orders - accidental double-entry details
40. Orders - accidental double-entry details, derived
table
41. Late orders
42. Late orders - which employees?
43. Late orders vs. total orders
44. Late orders vs. total orders - missing employee
45. Late orders vs. total orders - fix null
46. Late orders vs. total orders - percentage
47. Late orders vs. total orders - fix decimal
48. Customer grouping
49. Customer grouping - fix null
50. Customer grouping with percentage
51. Customer grouping - flexible
52. Countries with suppliers or customers
53. Countries with suppliers or customers, version 2
54. Countries with suppliers or customers - version
3
55. First order in each country
56. Customers with multiple orders in 5 day period
57. Customers with multiple orders in 5 day period,
version 2
Setup
This section will help you install Microsoft SQL
Server 2016, SQL Server Management Studio (SSMS)
and also walk you through setting up the practice
database. The setup of Microsoft SQL Server 2016 and
SSMS will take about 45 minutes, with about 5
minutes of interaction here and there. It may take one
or two reboots of your system, depending on which
version of certain support files you have (dot.net
framework).
SQL Server 2016 will run with more recent versions of
Windows, including Windows 8 and Windows 10.
Please review this requirements page
(https://msdn.microsoft.com/en-
us/library/ms143506.aspx) for full details.
Install Steps
Pre-setup - To download the backup file necessary in
step 3, as well as a PDF version of this book and a
SQL setup script to use if you already have SQL
Server 2012 or SQL Server 2014, go to
www.SQLPracticeProblems.com. Click on the “Buy
Now” button. Don’t worry, you don’t actually have to
buy anything. Use the 100% off coupon code
“KCSPurch” to bypass the credit card information, and
get a download link sent directly to your email for free.
You may want to consider viewing this book via the
PDF instead of the paper or Kindle copy, since you’ll
be able to click on the links, and copy and paste code
more easily.
Note: If you already have SQL Server 2012 or 2014
installed, and don’t want to install SQL Server 2016,
you don’t need to. There’s a setup script called
Northwind2012.sql (also works for SQL Server 2014)
included in the zipped file that will allow you to use
your existing version. Open that file, and follow the
instructions. You can then skip all the below steps.
(3 row(s) affected)
Hint
The standard format for a select statement that returns
all columns and all rows is “Select * from
TableName”.
2. Certain fields from Categories
In the Categories table, selecting all the fields using
this SQL:
Select * from Categories
…will return 4 columns. We only want to see two
columns, CategoryName and Description.
Expected Results
CategoryName Description
--------------- ----------------------------------------------------------
Beverages Soft drinks, coffees, teas, beers, and ales
Condiments Sweet and savory sauces, relishes, spreads, and seasonings
Confections Desserts, candies, and sweet breads
Dairy Products Cheeses
Grains/Cereals Breads, crackers, pasta, and cereal
Meat/Poultry Prepared meats
Produce Dried fruit and bean curd
Seafood Seaweed and fish
(8 row(s) affected)
Hint
Instead of * in the Select statement, specify the column
names with a comma between them
3. Sales Representatives
We’d like to see just the FirstName, LastName, and
HireDate of all the employees with the Title of Sales
Representative. Write a SQL statement that returns
only those employees.
Expected Results
FirstName LastName HireDate
---------- -------------------- -----------------------
Nancy Davolio 2010-05-01 00:00:00.000
Janet Leverling 2010-04-01 00:00:00.000
Margaret Peacock 2011-05-03 00:00:00.000
Michael Suyama 2011-10-17 00:00:00.000
Robert King 2012-01-02 00:00:00.000
Anne Dodsworth 2012-11-15 00:00:00.000
(6 row(s) affected)
Hint
To filter out only certain rows from a table, use a
Where clause. The format for a where clause with a
string filter is:
Where
FieldName = 'Filter Text'
4. Sales Representatives in the United
States
Now we’d like to see the same columns as above, but
only for those employees that both have the title of
Sales Representative, and also are in the United States.
Expected Results
FirstName LastName HireDate
---------- -------------------- -----------------------
Nancy Davolio 2010-05-01 00:00:00.000
Janet Leverling 2010-04-01 00:00:00.000
Margaret Peacock 2011-05-03 00:00:00.000
(3 row(s) affected)
Hint
To apply multiple filters in a where clause, use “and”
to separate the filters.
5. Orders placed by specific EmployeeID
Show all the orders placed by a specific employee. The
EmployeeID for this Employee (Steven Buchanan) is
5.
Expected Results
OrderID OrderDate
----------- -----------------------
10248 2014-07-04 08:00:00.000
10254 2014-07-11 02:00:00.000
10269 2014-07-31 00:00:00.000
10297 2014-09-04 21:00:00.000
10320 2014-10-03 12:00:00.000
10333 2014-10-18 18:00:00.000
10358 2014-11-20 05:00:00.000
10359 2014-11-21 14:00:00.000
10372 2014-12-04 10:00:00.000
10378 2014-12-10 00:00:00.000
10397 2014-12-27 17:00:00.000
10463 2015-03-04 13:00:00.000
10474 2015-03-13 16:00:00.000
10477 2015-03-17 02:00:00.000
10529 2015-05-07 01:00:00.000
10549 2015-05-27 03:00:00.000
10569 2015-06-16 15:00:00.000
10575 2015-06-20 22:00:00.000
10607 2015-07-22 09:00:00.000
10648 2015-08-28 22:00:00.000
10649 2015-08-28 00:00:00.000
10650 2015-08-29 06:00:00.000
10654 2015-09-02 07:00:00.000
10675 2015-09-19 06:00:00.000
10711 2015-10-21 03:00:00.000
10714 2015-10-22 03:00:00.000
10721 2015-10-29 08:00:00.000
10730 2015-11-05 07:00:00.000
10761 2015-12-02 08:00:00.000
10812 2016-01-02 02:00:00.000
10823 2016-01-09 17:00:00.000
10841 2016-01-20 21:00:00.000
10851 2016-01-26 00:00:00.000
10866 2016-02-03 01:00:00.000
10869 2016-02-04 09:00:00.000
10870 2016-02-04 12:00:00.000
10872 2016-02-05 06:00:00.000
10874 2016-02-06 14:00:00.000
10899 2016-02-20 09:00:00.000
10922 2016-03-03 02:00:00.000
10954 2016-03-17 16:00:00.000
11043 2016-04-22 17:00:00.000
(2 row(s) affected)
Hint
In an earlierproblem, we were looking for exact
matches — where our filter matched the value in the
field exactly. Here, we’re looking for rows where the
ProductName field has the value “queso” somewhere
in it.
Use the “like” operator, with wildcards, in the answer.
Feel free to do some research online to find examples.
8. Orders shipping to France or Belgium
Looking at the Orders table, there’s a field called
ShipCountry. Write a query that shows the OrderID,
CustomerID, and ShipCountry for the orders where the
ShipCountry is either France or Belgium.
Expected Results
OrderID CustomerID ShipCountry
----------- ---------- ---------------
10248 VINET France
10251 VICTE France
10252 SUPRD Belgium
10265 BLONP France
10274 VINET France
10295 VINET France
10297 BLONP France
10302 SUPRD Belgium
10311 DUMON France
10331 BONAP France
10334 VICTE France
10340 BONAP France
10350 LAMAI France
10358 LAMAI France
(9 row(s) affected)
Hint
You’ll need to use the Order by clause here for sorting
the results. Look online for examples.
11. Showing only the Date with a DateTime
field
In the output of the query above, showing the
Employees in order of BirthDate, we see the time of
the BirthDate field, which we don’t want. Show only
the date portion of the BirthDate field.
Expected Results
FirstName LastName Title DateOnlyBirthDate
---------- -------------------- ------------------------------ -----------------
Margaret Peacock Sales Representative 1955-09-19
Nancy Davolio Sales Representative 1966-12-08
Andrew Fuller Vice President, Sales 1970-02-19
Steven Buchanan Sales Manager 1973-03-04
Laura Callahan Inside Sales Coordinator 1976-01-09
Robert King Sales Representative 1978-05-29
Michael Suyama Sales Representative 1981-07-02
Janet Leverling Sales Representative 1981-08-30
Anne Dodsworth Sales Representative 1984-01-27
(9 row(s) affected)
Hint
Use the Convert function to convert the BirthDate
column (originally a DateTime column) to a Date
column.
12. Employees full name
Show the FirstName and LastName columns from the
Employees table, and then create a new column called
FullName, showing FirstName and LastName joined
together in one column, with a space in-between.
Expected Results
FirstName LastName FullName
---------- -------------------- -------------------------------
Nancy Davolio Nancy Davolio
Andrew Fuller Andrew Fuller
Janet Leverling Janet Leverling
Margaret Peacock Margaret Peacock
Steven Buchanan Steven Buchanan
Michael Suyama Michael Suyama
Robert King Robert King
Laura Callahan Laura Callahan
Anne Dodsworth Anne Dodsworth
(9 row(s) affected)
Hint
Joining two fields like this is called concatenation.
Look online for examples of string concatenation with
SQL Server.
13. OrderDetails amount per line item
In the OrderDetails table, we have the fields UnitPrice
and Quantity. Create a new field, TotalPrice, that
multiplies these two together. We’ll ignore the
Discount field for now.
In addition, show the OrderID, ProductID, UnitPrice,
and Quantity. Order by OrderID and ProductID.
Expected Results
OrderID ProductID UnitPrice Quantity TotalPrice
----------- ----------- --------------------- -------- ---------------------
10248 11 14.00 12 168.00
10248 42 9.80 10 98.00
10248 72 34.80 5 174.00
10249 14 18.60 9 167.40
10249 51 42.40 40 1696.00
10250 41 7.70 10 77.00
10250 51 42.40 35 1484.00
10250 65 16.80 15 252.00
10251 22 16.80 6 100.80
10251 57 15.60 15 234.00
10251 65 16.80 20 336.00
(1 row(s) affected)
Hint
In order to get the total number of customers, we need
to use what’s called an aggregate function. Look online
for an aggregate function that would work for this
problem.
15. When was the first order?
Show the date of the first order ever made in the
Orders table.
Expected Results
FirstOrder
-----------------------
2014-07-04 08:00:00.000
(1 row(s) affected)
Hint
There’s a aggregate function called Min that you need
to use for this problem.
16. Countries where there are customers
Show a list of countries where the Northwind company
has customers.
Expected Results
Country
---------------
Argentina
Austria
Belgium
Brazil
Canada
Denmark
Finland
France
Germany
Ireland
Italy
Mexico
Norway
Poland
Portugal
Spain
Sweden
Switzerland
UK
USA
Venezuela
(8 row(s) affected)
Hint
To solve this problem, you need to combine a join, and
a group by.
A good way to start is by creating a query that shows
the CategoryName and all ProductIDs associated with
it, without grouping. Then, add the Group by
21. Total customers per country/city
In the Customers table, show the total number of
customers per Country and City.
Expected Results
Country City TotalCustomer
--------------- --------------- -------------
UK London 6
Mexico México D.F. 5
Brazil Sao Paulo 4
Brazil Rio de Janeiro 3
Spain Madrid 3
Argentina Buenos Aires 3
France Paris 2
USA Portland 2
France Nantes 2
Portugal Lisboa 2
Finland Oulu 1
Italy Reggio Emilia 1
France Reims 1
Brazil Resende 1
Canada Montréal 1
Germany München 1
Germany Münster 1
Germany Aachen 1
USA Albuquerque 1
USA Anchorage 1
Denmark Århus 1
Spain Barcelona 1
Venezuela Barquisimeto 1
Italy Bergamo 1
Germany Berlin 1
Switzerland Bern 1
USA Boise 1
Sweden Bräcke 1
Germany Brandenburg 1
Belgium Bruxelles 1
(2 row(s) affected)
Hint
For the first part of the Where clause, you should have
something like this:
UnitsInStock + UnitsOnOrder <= ReorderLevel
24. Customer list by region
A salesperson for Northwind is going on a business
trip to visit customers, and would like to see a list of
all customers, sorted by region, alphabetically.
However, he wants the customers with no region (null
in the Region field) to be at the end, instead of at the
top, where you’d normally find the null values. Within
the same region, companies should be sorted by
CustomerID.
Expected Results
CustomerID CompanyName Region
---------- ---------------------------------------- ---------------
OLDWO Old World Delicatessen AK
BOTTM Bottom-Dollar Markets BC
LAUGB Laughing Bacchus Wine Cellars BC
LETSS Let's Stop N Shop CA
HUNGO Hungry Owl All-Night Grocers Co. Cork
GROSR GROSELLA-Restaurante DF
SAVEA Save-a-lot Markets ID
ISLAT Island Trading Isle of Wight
LILAS LILA-Supermercado Lara
THECR The Cracker Box MT
RATTC Rattlesnake Canyon Grocery NM
(3 row(s) affected)
Hint
We'll be using the Orders table, and using the Freight
and ShipCountry fields.
Hint
You'll want to group by ShipCountry, and use the Avg
function. Don't worry about showing only the top 3
rows until you have the grouping and average freight
set up.
Hint
You should have something like this:
Select
ShipCountry
,AverageFreight = avg(freight)
From Orders
Group By ShipCountry
Order By AverageFreight desc;
(3 row(s) affected)
Hint
You need to add a Where clause to the query from the
previous problem. The field to filter on is OrderDate.
Hint
When filtering on dates, you need to know whether the
date field is a DateTime, or a Date field. Is OrderDate
a Datetime or a Date field?
27. High freight charges with between
Another (incorrect) answer to the problem above is
this:
Select Top 3
ShipCountry
,AverageFreight = avg(freight)
From Orders
Where
OrderDate between '1/1/2015' and '12/31/2015'
Group By ShipCountry
Order By AverageFreight desc;
(3 row(s) affected)
Hint
First, get the last OrderDate in Orders. Write a simple
select statement to get the highest value in the
OrderDate field using the Max aggregate function.
Hint
You should have something like this:
Select Max(OrderDate) from Orders
Now you need to get the date 1 year before the last
order date. Create a simple select statement that
subtracts 1 year from the last order date
You can use the DateAdd function for this. Note that
within DateAdd, you can use the subquery you created
above. Look online for some examples if you need to.
Hint
You should have something like this:
Select Dateadd(yy, -1, (Select Max(OrderDate) from Orders))
Now you just need to put it in the where clause.
29. Inventory list
We're doing inventory, and need to show information
like the below, for all orders. Sort by OrderID and
Product ID.
Expected Results
EmployeeID LastName OrderID ProductName
Quantity
----------- -------------------- ----------- ---------------------------------------- --
------
5 Buchanan 10248 Queso Cabrales 12
5 Buchanan 10248 Singaporean Hokkien Fried Mee
10
5 Buchanan 10248 Mozzarella di Giovanni 5
6 Suyama 10249 Tofu 9
6 Suyama 10249 Manjimup Dried Apples 40
4 Peacock 10250 Jack's New England Clam
Chowder 10
4 Peacock 10250 Manjimup Dried Apples 35
4 Peacock 10250 Louisiana Fiery Hot Pepper Sauce
15
3 Leverling 10251 Gustaf's Knäckebröd 6
3 Leverling 10251 Ravioli Angelo 15
3 Leverling 10251 Louisiana Fiery Hot Pepper Sauce
20
4 Peacock 10252 Sir Rodney's Marmalade 40
4 Peacock 10252 Geitost 25
4 Peacock 10252 Camembert Pierrot 40
3 Leverling 10253 Gorgonzola Telino 20
3 Leverling 10253 Chartreuse verte 42
3 Leverling 10253 Maxilaku 40
…
(total 2155 rows)
Hint
You'll need to do a join between 4 tables, displaying
only those fields that are necessary.
30. Customers with no orders
There are some customers who have never actually
placed an order. Show these customers.
Expected Results
Customers_CustomerID Orders_CustomerID
-------------------- -----------------
FISSA NULL
PARIS NULL
(2 row(s) affected)
Hint
One way of doing this is to use a left join, also known
as a left outer join.
Hint
Select
Customers_CustomerID = Customers.CustomerID
,Orders_CustomerID = Orders.CustomerID
From Customers
left join Orders
on Orders.CustomerID = Customers.CustomerID
Select
Customers.CustomerID
,Orders.CustomerID
From Customers
left join Orders
on Orders.CustomerID = Customers.CustomerID
Where
Orders.CustomerID is null
and Orders.EmployeeID = 4
(6 row(s) affected)
Hint
First, let's get the necessary fields for all orders made
in the year 2016. Don't bother grouping yet, just work
on the Where clause. You'll need the CustomerID,
CompanyName from Customers; OrderID from Orders;
and Quantity and unit price from OrderDetails. Order
by the total amount of the order, in descending order.
Hint
You should have something like this:
Select
Customers.CustomerID
,Customers.CompanyName
,Orders.OrderID
,Amount = Quantity * UnitPrice
From Customers
join Orders
on Orders.CustomerID = Customers.CustomerID
join OrderDetails
on Orders.OrderID = OrderDetails.OrderID
Where
OrderDate >= '20160101'
and OrderDate < '20170101'
This gives you the total amount for each Order Detail
item in 2016 orders, at the Order Detail level. Now,
which fields do you need to group on, and which need
to be summed?
Hint
Select
Customers.CustomerID
,Customers.CompanyName
,Orders.OrderID
,TotalOrderAmount = sum(Quantity * UnitPrice)
From Customers
Join Orders
on Orders.CustomerID = Customers.CustomerID
Join OrderDetails
on Orders.OrderID = OrderDetails.OrderID
Where
OrderDate >= '20160101'
and OrderDate < '20170101'
Group By
Customers.CustomerID
,Customers.CompanyName
,Orders.OrderID
(9 row(s) affected)
Hint
This query is almost identical to the one above, but
there's just a few lines you need to delete or comment
out, to group at a different level.
34. High-value customers - with discount
Change the above query to use the discount when
calculating high-value customers. Order by the total
amount which includes the discount.
Expected Result
CustomerID CompanyName TotalsWithoutDiscount
TotalsWithDiscount
---------- ------------------------------ --------------------- ----------------------
ERNSH Ernst Handel 42598.90 41210.6500244141
QUICK QUICK-Stop 40526.99 37217.3150024414
SAVEA Save-a-lot Markets 42806.25 36310.1097793579
HANAR Hanari Carnes 24238.05 23821.1999893188
RATTC Rattlesnake Canyon Grocery 21725.60
21238.2704410553
HUNGO Hungry Owl All-Night Grocers 22796.34
20402.119934082
KOENE Königlich Essen 20204.95 19582.7739868164
WHITC White Clover Markets 15278.90
15278.8999862671
FOLKO Folk och fä HB 15973.85 13644.0674972534
SUPRD Suprêmes délices 11862.50 11644.5999984741
BOTTM Bottom-Dollar Markets 12227.40
11338.5500488281
(5 row(s) affected)
Hint
You might start out with something like this:
Select
OrderID
,ProductID
,Quantity
From OrderDetails
Where Quantity >= 60
(8 row(s) affected)
Hint
The answer from the problem above is a good starting
point. You'll need to join to the Employee table to get
the last name, and also add Count to show the total
late orders.
43. Late orders vs. total orders
Andrew, the VP of sales, has been doing some more
thinking some more about the problem of late orders.
He realizes that just looking at the number of orders
arriving late for each salesperson isn't a good idea. It
needs to be compared against the total number of
orders per salesperson. Return results like the
following:
Expected Result
EmployeeID LastName AllOrders LateOrders
----------- -------------------- ----------- -----------
1 Davolio 123 3
2 Fuller 96 4
3 Leverling 127 5
4 Peacock 156 10
6 Suyama 67 3
7 King 72 4
8 Callahan 104 5
9 Dodsworth 43 5
(8 row(s) affected)
Hint
You can use more than one CTE in a query. That
would be a straightforward way of solving this
problem.
Hint
Here are 2 SQL statements that could be put into CTEs
and put together into a final SQL statement.
-- Late orders
Select
EmployeeID
,TotalOrders = Count(*)
From Orders
Where
RequiredDate <= ShippedDate
Group By
EmployeeID
-- Total orders
Select
EmployeeID
,TotalOrders = Count(*)
From Orders
Group By
EmployeeID
44. Late orders vs. total orders - missing
employee
There's an employee missing in the answer from the
problem above. Fix the SQL to show all employees
who have taken orders.
Expected Result
EmployeeID LastName AllOrders LateOrders
----------- -------------------- ----------- -----------
1 Davolio 123 3
2 Fuller 96 4
3 Leverling 127 5
4 Peacock 156 10
5 Buchanan 42 NULL
6 Suyama 67 3
7 King 72 4
8 Callahan 104 5
9 Dodsworth 43 5
(9 row(s) affected)
Hint
How many rows are returned when you run just the
AllOrders CTE? How about when you run just the
LateOrders CTE?
Hint
You'll want to add a left join (also known as a left
outer join), to make sure that we show a row, even if
there are no late orders.
45. Late orders vs. total orders - fix null
Continuing on the answer for above query, let's fix the
results for row 5 - Buchanan. He should have a 0
instead of a Null in LateOrders.
Expected Result
EmployeeID LastName AllOrders LateOrders
----------- -------------------- ----------- -----------
1 Davolio 123 3
2 Fuller 96 4
3 Leverling 127 5
4 Peacock 156 10
5 Buchanan 42 0
6 Suyama 67 3
7 King 72 4
8 Callahan 104 5
9 Dodsworth 43 5
(9 row(s) affected)
Hint
Find a function to test if a value is null, and return a
different value when it is.
46. Late orders vs. total orders - percentage
Now we want to get the percentage of late orders over
total orders.
Expected Result
EmployeeID LastName AllOrders LateOrders PercentLateOrders
----------- -------------------- ----------- ----------- -------------------
1 Davolio 123 3 0.0243902439024
2 Fuller 96 4 0.0416666666666
3 Leverling 127 5 0.0393700787401
4 Peacock 156 10 0.0641025641025
5 Buchanan 42 0 0.0000000000000
6 Suyama 67 3 0.0447761194029
7 King 72 4 0.0555555555555
8 Callahan 104 5 0.0480769230769
9 Dodsworth 43 5 0.1162790697674
(9 row(s) affected)
Hint
By dividing late orders by total orders, you should be
able to get the percentage of orders that are late.
However, there's a common problem people run into,
which is that an integer divided by an integer returns
an integer. For instance, if you run the following SQL
to divide 3 by 2:
select 3/2
You’ll get 1 instead of 1.5, because it will return the
closest integer.
Do some research online to find the answer to this
issue.
47. Late orders vs. total orders - fix decimal
So now for the PercentageLateOrders, we get a
decimal value like we should. But to make the output
easier to read, let's cut the PercentLateOrders off at 2
digits to the right of the decimal point.
Expected Result
EmployeeID LastName AllOrders LateOrders PercentLateOrders
----------- -------------------- ----------- ----------- -------------------
1 Davolio 123 3 0.02
2 Fuller 96 4 0.04
3 Leverling 127 5 0.04
4 Peacock 156 10 0.06
5 Buchanan 42 0 0.00
6 Suyama 67 3 0.04
7 King 72 4 0.06
8 Callahan 104 5 0.05
9 Dodsworth 43 5 0.12
(9 row(s) affected)
Hint
One straightforward way of doing this would be to
explicitly convert PercentageLateOrders to a specific
Decimal data type. With the Decimal datatype, you can
specify how many digits you want to the right of the
decimal point
Hint
The calculation PercentLateOrders is getting a little
long and complicated, and it can be tricky to get all the
commas and parenthesis correct.
One way to simplify it is to break it down with an
actual value instead of a calculation.
For instance:
Select convert(decimal(10,2), 0.0243902439024)
48. Customer grouping
Andrew Fuller, the VP of sales at Northwind, would
like to do a sales campaign for existing customers.
He'd like to categorize customers into groups, based on
how much they ordered in 2016. Then, depending on
which group the customer is in, he will target the
customer with different sales materials.
The customer grouping categories are 0 to 1,000, 1,000
to 5,000, 5,000 to 10,000, and over 10,000.
A good starting point for this query is the answer from
the problem “High-value customers - total orders. We
don’t want to show customers who don’t have any
orders in 2016.
Order the results by CustomerID.
Expected Result
CustomerID CompanyName TotalOrderAmount
CustomerGroup
---------- ---------------------------------------- --------------------- -------------
ALFKI Alfreds Futterkiste 2302.20 Medium
ANATR Ana Trujillo Emparedados y helados 514.40 Low
ANTON Antonio Moreno Taquería 660.00 Low
AROUT Around the Horn 5838.50 High
BERGS Berglunds snabbköp 8110.55 High
BLAUS Blauer See Delikatessen 2160.00 Medium
BLONP Blondesddsl père et fils 730.00 Low
BOLID Bólido Comidas preparadas 280.00 Low
BONAP Bon app' 7185.90 High
BOTTM Bottom-Dollar Markets 12227.40 Very High
BSBEV B's Beverages 2431.00 Medium
CACTU Cactus Comidas para llevar 1576.80 Medium
CHOPS Chop-suey Chinese 4429.40 Medium
(4 row(s) affected)
Hint
As a starting point, you can use the answer from the
problem “Customer grouping - fix null”.
Hint
We no longer need to show the CustomerID and
CompanyName in the final output. However, we need
to count how many customers are in each
CustomerGrouping. You can create another CTE level
in order to get the counts in each CustomerGrouping
for the final output.
51. Customer grouping - flexible
Andrew, the VP of Sales is still thinking about how
best to group customers, and define low, medium,
high, and very high value customers. He now wants
complete flexibility in grouping the customers, based
on the dollar amount they've ordered. He doesn’t want
to have to edit SQL in order to change the boundaries
of the customer groups.
How would you write the SQL?
There's a table called CustomerGroupThreshold that
you will need to use. Use only orders from 2016.
Expected Result
(The expected results are the same as for the original
problem, it’s just that we’re getting the answer
differently.)
CustomerID CompanyName TotalOrderAmount
CustomerGroupName
---------- ---------------------------------------- --------------------- --------------
------
ALFKI Alfreds Futterkiste 2302.20 Medium
ANATR Ana Trujillo Emparedados y helados 514.40 Low
ANTON Antonio Moreno Taquería 660.00 Low
AROUT Around the Horn 5838.50 High
BERGS Berglunds snabbköp 8110.55 High
BLAUS Blauer See Delikatessen 2160.00 Medium
BLONP Blondesddsl père et fils 730.00 Low
BOLID Bólido Comidas preparadas 280.00 Low
BONAP Bon app' 7185.90 High
BOTTM Bottom-Dollar Markets 12227.40 Very High
BSBEV B's Beverages 2431.00 Medium
CACTU Cactus Comidas para llevar 1576.80 Medium
CHOPS Chop-suey Chinese 4429.40 Medium
COMMI Comércio Mineiro 513.75 Low
Now, take the output of this, and using a CTE and the
DateDiff function, filter for rows which match our
criteria.
Introductory Problems
1. Which shippers do we have?
Answer
Select
*
From Shippers
Discussion
This is a basic select statement, returning all rows, just
to get you warmed up.
Most of the time, a simple select statement like this is
written all on one line, like this:
Select * From Shippers
But because we’ll be getting more complex quickly,
we’ll start out with formatting it with separate lines for
each clause, which we’ll be doing in future questions.
2. Certain fields from Categories
Answer
Select
CategoryName
,Description
from Categories
Discussion
Instead of doing a “Select *”, we specify the column
names, and only get those columns returned.
3. Sales Representatives
Answer
Select
FirstName
,LastName
,HireDate
From Employees
Where
Title = 'Sales Representative'
Discussion
This is a simple filter against a string datatype. When
comparing a value to a string datatype, you need to
enclose the value in single quotes.
What happens when you don’t? Try running the
following:
Select
FirstName
,LastName
,HireDate
From Employees
Where
Title = Sales Representative
Select
FirstName
,LastName
,FullName = FirstName + ' ' + LastName
From Employees
Discussion
This is another example of the computed column. In
this case, instead of applying a function to a field,
we’re concatenating two fields.
Another way to do concatenation, as of SQL Server
2012, is using the Concat function, as below.
Select
FirstName
,LastName
,FullName = concat(FirstName , ' ' , LastName)
From Employees