文章目录
前言
该系列教程,将会从实际问题出发,边干边学,逐步深入讲解SQL的各方面知识。
你需要完成所有的问题吗?绝对不是。介绍性的问题相当简单,所以你可以直接跳过到“中级问题”部分。如果你不是初学者,但不确定应该从哪里开始,请在“入门问题”部分看看问题和预期结果,并确保你理解这些概念。如果已经理解了这些概念,请开始阅读“中级问题”部分。
你想从这本书中复制代码并在你的服务器上运行?我建议你手动输入,而不是复制粘贴。为什么要去麻烦地重新打字呢?科学表明,打字的行为会在你的脑中留下更深刻的印象。当你只是复制和粘贴时,代码只是直接从你电脑里的一个窗口转到另一个窗口,而不会给你留下多少印象。但是当你把它打出来时,你必须集中精力,这非常有助于保留信息。
一旦你完成了所有的问题,将拥有一些在数据分析和高级Select语句使用方面非常有用的技能。当然,这并不是SQL的全部内容。还有修改数据(更新、插入、删除)、DDL(数据定义语言,即如何创建和修改数据库对象)、编程(如存储过程)和许多其他主题。
该系列教程中,只涉及到了使用Select语句检索数据的问题,这几乎是所有其他数据库主题的基础开端。
回顾
上篇文章👉《【SQL边干边学系列】03中级问题》 中讨论了部分中级问题,这篇文章我们接着讨论剩余的中级问题。
中级问题
25.高昂运费
运往某些国家的运费很高。我们希望为客户提供更多的选择,以便降低他们的运费。返回总体平均运费最高的三个国家,按平均运费降序排列。
-- 预期结果
ShipCountry AverageFreight
--------------- ---------------------
Austria 184.7875
Ireland 145.0126
USA 112.8794
(3 row(s) affected)
提示如下
我们将使用订单表(Orders),并使用货运(Freight)和运往国家( ShipCountry)字段。
你需要按ShipCountry进行分组,并使用Avg函数,使用如下的语句:
Select
ShipCountry
,AverageFreight = avg(freight)
From Orders
Group By ShipCountry
Order By AverageFreight desc;
现在你只需要显示前3行。
26.2015年的高昂运费
我们将继续讨论上述关于高昂运费的问题。现在,我们只想看到2015年的订单。
-- 预期结果
ShipCountry AverageFreight
--------------- ---------------------
Austria 178.3642
Switzerland 117.1775
France 113.991
(3 row(s) affected)
提示如下:
你需要在上一个问题的查询中添加一个Where子句,过滤OrderDate字段。
在筛选日期时,你需要知道OrderDate字段是DateTime还是Date。
27.高昂运维 - 使用between
对上述问题的另一个(不正确的)答案是:
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;
注意,当你运行这个语句时,它将返回错误的结果。
上面的(错误的)答案缺失的订单ID是什么?为什么不显示2015年12月31日的订单呢?
运行此查询,并查看2015年12月31日左右的行。你注意到了什么?特别看一下Freight字段。
select * from orders order by OrderDate
28.去年的高昂运费
我们继续处理高昂运费的问题。我们现在希望得到平均运费最高的三个国家。但是,我们不希望对特定年份进行筛选,而是使用最后12个月的订单数据,使用订单表(Orders)中订单日期(OrderDate)的最后一个作为结束日期。
-- 预期结果
ShipCountry AverageFreight
--------------- ---------------------
Ireland 200.21
Austria 186.4596
USA 119.3032
(3 row(s) affected)
提示如下
首先,获取Orders中的最后一个OrderDate。你应该使用这样的语句:Select Max(OrderDate) from Orders
现在你需要计算从最后一个OrderDate往前数1年的日期。你应该使用这样的语句:Select Dateadd(yy, -1, (Select Max(OrderDate) from Orders))
。现在你只需要把它放在where子句中。
29.库存清单
我们正在做库存,按照以下格式显示所有订单的信息。按订OrderID和Product ID排序。
-- 预期结果
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
(total 2155 rows)
提示 :你需要连接4个表,并且只显示那些必要的字段。
30.没有任何订单的客户
有些客户从未真正下过订单。显示这些客户。
-- 预期结果
Customers_CustomerID Orders_CustomerID
-------------------- -----------------
FISSA NULL
PARIS NULL
(2 row(s) affected)
提示如下
其中一种方法是使用左连接,也被称为左外连接。
Select
Customers_CustomerID = Customers.CustomerID
,Orders_CustomerID = Orders.CustomerID
From Customers
left join Orders
on Orders.CustomerID = Customers.CustomerID
这是一个很好的开始。它显示了“Customers”表中的所有记录,以及“Orders”表中的匹配记录。但是,我们只希望那些在Orders表中 CustomerID 为null的记录,需要使用过滤器实现。
31.没有任何订单的客户,员工ID为4
一名员工(员工编号EmployeeID为4)下的订单最多。然而,也有一些顾客从未向她下过订单。只展示那些从未向她下过订单的顾客。
-- 预期结果
CustomerID CustomerID
---------- ----------
SEVES NULL
THEBI NULL
LAZYK NULL
GROSR NULL
PARIS NULL
FISSA NULL
SPECD NULL
LAUGB NULL
PRINI NULL
VINET NULL
FRANR NULL
CONSH NULL
NORTS NULL
PERIC NULL
DUMON NULL
SANTG NULL
(16 row(s) affected)
提示如下
基于之前的问题,你可能会认为应该这样做:
Select
Customers.CustomerID
,Orders.CustomerID
From Customers
left join Orders
on Orders.CustomerID = Customers.CustomerID
Where
Orders.CustomerID is null
and Orders.EmployeeID = 4
但是,这样不会返回任何结果。
请注意,对于外部连接,where子句上的过滤器将作用与连接之后。
答案
25.高昂运费
答案
Select Top 3
ShipCountry
,AverageFreight = Avg(freight)
From Orders
Group By ShipCountry
Order By AverageFreight desc;
讨论
只显示一定数量的记录的最简单和最常用的方法是使用Top。另一种方法是使用偏移量,如下所示。
Select
ShipCountry
,AverageFreight = AVG(freight)
From Orders
Group By ShipCountry
Order by AverageFreight DESC
OFFSET 0 ROWS FETCH FIRST 3 ROWS ONLY
26.2015年的高昂运费
答案
Select Top 3
ShipCountry
,AverageFreight = avg(freight)
From Orders
Where
OrderDate >= '20150101'
and OrderDate < '20160101'
Group By ShipCountry
Order By AverageFreight desc;
讨论
写where子句的另一种方法是:
Where
OrderDate >= '1/1/2015'
and OrderDate < '1/1/2016'
这里还有另一种方式:
Select Top 3
ShipCountry
,AverageFreight = avg(freight)
From Orders
Where
year(OrderDate) = 2015 -- 使用Year函数
Group By ShipCountry
Order By AverageFreight desc;
27.高昂运维 - 使用between
答案
导致不同结果的OrderID是10806。
讨论
如果OrderDate是一个Date,而不是DateTime,那么这个SQL就可以正常工作。
OrderDate between '1/1/2015' and '12/31/2015'
然而,由于它是一个DateTime字段,它给出了一个错误的答案,因为它没有考虑到OrderDate在2015年12月31日当天的记录。
请注意,对于DateTime字段,12/31/2015
等价于2015-12-31 00:00:00.000
。
28.去年的高昂运费
答案
Select TOP (3)
ShipCountry
,AverageFreight = Avg(freight)
From Orders
Where
OrderDate >= Dateadd(yy, -1, (Select max(OrderDate) from Orders))
Group by ShipCountry
Order by AverageFreight desc;
讨论
使用这样的SQL可以生成动态日期范围,这对于大多数数据分析工作是至关重要的。大多数报告和查询都需要是灵活的,没有硬编码日期值的方式。
29.库存清单
答案
Select
Employees.EmployeeID
,Employees.LastName
,Orders.OrderID
,Products.ProductName
,OrderDetails.Quantity
From Employees
join Orders
on Orders.EmployeeID = Employees.EmployeeID
join OrderDetails
on Orders.OrderID = OrderDetails.OrderID
join Products
on Products.ProductID = OrderDetails.ProductID
Order by
Orders.OrderID
,Products.ProductID
讨论
这个问题更多的是使用多个表的Join。你可以用 Inner Join来替换Join,但大多数人只是使用Join。
30.没有任何订单的客户
答案
Select
Customers_CustomerID = Customers.CustomerID
,Orders_CustomerID = Orders.CustomerID
From Customers
left join Orders
on Orders.CustomerID = Customers.CustomerID
Where
Orders.CustomerID is null
讨论
有很多方法可以得到相同的结果。
在上面,我们使用了Left Join。当性能相等时,我更喜欢Not In方法,如下所示。
Select CustomerID
From Customers
Where
CustomerID not in (select CustomerID from Orders)
另一个选择是使用Not Exists。这需要一个相关子查询。
Select CustomerID
From Customers
Where Not Exists
(
Select CustomerID
from Orders
where
Orders.CustomerID = Customers.CustomerID
)
31.没有任何订单的客户,员工ID为4
答案
Select
Customers.CustomerID
,Orders.CustomerID
From Customers
left join Orders
on Orders.CustomerID = Customers.CustomerID
and Orders.EmployeeID = 4
Where
Orders.CustomerID is null
讨论
因为Where子句中的过滤器是应用在连接之后的结果上,所以我们需要在连接子句中EmployeeID = 4
过滤器,而不是在Where子句中。
运行下面的查询并查看结果。它应该能让你更好地了解“is null”是如何工作的。请注意,Where子句已经被注释掉了。
Select
Customers.CustomerID
,Orders.CustomerID
,Orders.EmployeeID
From Customers
left join Orders
on Orders.CustomerID = Customers.CustomerID
and Orders.EmployeeID = 4
-- Where
-- Orders.CustomerID is null
解决这类问题最常见的方法是使用左连接。然而,下面是另一种替代方案。
-- Not In 方案
Select CustomerID
From Customers
Where
CustomerID not in (select CustomerID from Orders where EmployeeID = 4)
-- Not Exists 方案
Select CustomerID
From Customers
Where Not Exists
(
Select CustomerID
from Orders
where Orders.CustomerID = Customers.CustomerID
and EmployeeID = 4
)
未完待续
中级问题到此结束。下一篇开始讨论高级问题。
如果喜欢这篇文章,请不要忘记关注🧡、点赞👍和收藏📔哦!