【SQL边干边学系列】04中级问题(续)


前言

在这里插入图片描述
该系列教程,将会从实际问题出发,边干边学,逐步深入讲解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
 )

未完待续

中级问题到此结束。下一篇开始讨论高级问题。


如果喜欢这篇文章,请不要忘记关注🧡、点赞👍和收藏📔哦!

  • 38
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

架构师昌哥

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值