文章目录
- day 01
- 1 查询
- 2 连接
高频 SQL 50 题(基础版)
day 01
1 查询
1757. 可回收且低脂的产品
+-------------+---------+
| Column Name | Type |
+-------------+---------+
| product_id | int |
| low_fats | enum |
| recyclable | enum |
+-------------+---------+
product_id 是该表的主键(具有唯一值的列)。
low_fats 是枚举类型,取值为以下两种 ('Y', 'N'),其中 'Y' 表示该产品是低脂产品,'N' 表示不是低脂产品。
recyclable 是枚举类型,取值为以下两种 ('Y', 'N'),其中 'Y' 表示该产品可回收,而 'N' 表示不可回收。
select product_id from Products where low_fats="Y" and recyclable="Y";
584. 寻找用户推荐人
输入:
Customer 表:
+----+------+------------+
| id | name | referee_id |
+----+------+------------+
| 1 | Will | null |
| 2 | Jane | null |
| 3 | Alex | 2 |
| 4 | Bill | null |
| 5 | Zack | 1 |
| 6 | Mark | 2 |
+----+------+------------+
输出:
+------+
| name |
+------+
| Will |
| Jane |
| Bill |
| Zack |
+------+
select name from Customer
where referee_id!=2 or referee_id is null;
MySQL 使用三值逻辑 —— TRUE, FALSE 和 UNKNOWN。任何与 NULL 值进行的比较都会与第三种值 UNKNOWN 做比较。这个“任何值”包括 NULL 本身!这就是为什么 MySQL 提供 IS NULL 和 IS NOT NULL 两种操作来对 NULL 特殊判断。
因此,在 WHERE 语句中我们需要做一个额外的条件判断 `referee_id IS NULL’。
595. 大的国家
World 表:
±------------±--------+
| Column Name | Type |
±------------±--------+
| name | varchar |
| continent | varchar |
| area | int |
| population | int |
| gdp | bigint |
±------------±--------+
name 是该表的主键(具有唯一值的列)。
这张表的每一行提供:国家名称、所属大陆、面积、人口和 GDP 值。
如果一个国家满足下述两个条件之一,则认为该国是 大国 :
面积至少为 300 万平方公里(即,3000000 km2),或者
人口至少为 2500 万(即 25000000)
编写解决方案找出 大国 的国家名称、人口和面积。
按 任意顺序 返回结果表。
select name,population,area from World
where area>=3000000 or population>=25000000;
1148. 文章浏览 I
<font style="color:rgba(38, 38, 38, 0.75);background-color:rgb(240, 240, 240);">Views</font>
表:
+---------------+---------+
| Column Name | Type |
+---------------+---------+
| article_id | int |
| author_id | int |
| viewer_id | int |
| view_date | date |
+---------------+---------+
此表可能会存在重复行。(换句话说,在 SQL 中这个表没有主键)
此表的每一行都表示某人在某天浏览了某位作者的某篇文章。
请注意,同一人的 author_id 和 viewer_id 是相同的。
请查询出所有浏览过自己文章的作者
结果按照 <font style="color:rgba(38, 38, 38, 0.75);background-color:rgb(240, 240, 240);">id</font>
升序排列。
查询结果的格式如下所示:
示例 1:
输入:
Views 表:
+------------+-----------+-----------+------------+
| article_id | author_id | viewer_id | view_date |
+------------+-----------+-----------+------------+
| 1 | 3 | 5 | 2019-08-01 |
| 1 | 3 | 6 | 2019-08-02 |
| 2 | 7 | 7 | 2019-08-01 |
| 2 | 7 | 6 | 2019-08-02 |
| 4 | 7 | 1 | 2019-07-22 |
| 3 | 4 | 4 | 2019-07-21 |
| 3 | 4 | 4 | 2019-07-21 |
+------------+-----------+-----------+------------+
输出:
+------+
| id |
+------+
| 4 |
| 7 |
+------+
select
distinct author_id as id
from Views
where author_id=viewer_id
order by id
1683. 无效的推文
表:<font style="color:rgba(38, 38, 38, 0.75);background-color:rgb(240, 240, 240);">Tweets</font>
+----------------+---------+
| Column Name | Type |
+----------------+---------+
| tweet_id | int |
| content | varchar |
+----------------+---------+
在 SQL 中,tweet_id 是这个表的主键。
这个表包含某社交媒体 App 中所有的推文。
查询所有无效推文的编号(ID)。当推文内容中的字符数严格大于 <font style="color:rgba(38, 38, 38, 0.75);background-color:#FFFFFF;">15</font>
时,该推文是无效的。
以任意顺序返回结果表。
查询结果格式如下所示:
输入:
Tweets 表:
+----------+----------------------------------+
| tweet_id | content |
+----------+----------------------------------+
| 1 | Vote for Biden |
| 2 | Let us make America great again! |
+----------+----------------------------------+
输出:
+----------+
| tweet_id |
+----------+
| 2 |
+----------+
解释:
推文 1 的长度 length = 14。该推文是有效的。
推文 2 的长度 length = 32。该推文是无效的。
select tweet_id
from Tweets
where char_length(content)>15;
对于SQL表,用于计算字符串中字符数的最佳函数是 CHAR_LENGTH(str),它返回字符串 str 的长度。
另一个常用的函数 LENGTH(str) 在这个问题中也适用,因为列 content 只包含英文字符,没有特殊字符。否则,LENGTH() 可能会返回不同的结果,因为该函数返回字符串 str 的字节数,某些字符包含多于 1 个字节。
以字符 ‘¥’ 为例:CHAR_LENGTH() 返回结果为 1,而 LENGTH() 返回结果为 2,因为该字符串包含 2 个字节。
2 连接
1378. 使用唯一标识码替换员工ID
展示每位用户的 唯一标识码(unique ID );如果某位员工没有唯一标识码,使用 null 填充即可。
创建表并插入数据
首先,我们需要创建两个表 Employees 和 EmployeeUNI,并插入相应的数据。
-- 创建 Employees 表
CREATE TABLE Employees (
id INT PRIMARY KEY,
name VARCHAR(100)
);
-- 插入数据到 Employees 表
INSERT INTO Employees (id, name) VALUES
(1, 'Alice'),
(7, 'Bob'),
(11, 'Meir'),
(90, 'Winston'),
(3, 'Jonathan');
-- 创建 EmployeeUNI 表
CREATE TABLE EmployeeUNI (
id INT,
unique_id INT,
PRIMARY KEY (id, unique_id)
);
-- 插入数据到 EmployeeUNI 表
INSERT INTO EmployeeUNI (id, unique_id) VALUES
(3, 1),
(11, 2),
(90, 3);
执行查询
接下来,我们使用 LEFT JOIN 将这两个表连接起来,以获取每个员工的唯一标识码。如果某个员工没有唯一标识码,则 unique_id 字段会显示为 NULL。
select
u.unique_id,
e.name
from
Employees e
left join
EmployeeUNI u on e.id = u.id;
LEFT JOIN 确保了 Employees 表中的所有记录都包含在结果中。
如果某个员工在 EmployeeUNI 表中没有对应的记录,那么 unique_id 字段将显示为 NULL。
1068. 产品销售分析 I
-- 创建 Sales 表
CREATE TABLE Sales (
sale_id INT,
product_id INT,
year INT,
quantity INT,
price INT,
PRIMARY KEY (sale_id, year)
);
-- 插入数据到 Sales 表
INSERT INTO Sales (sale_id, product_id, year, quantity, price) VALUES
(1, 100, 2008, 10, 5000),
(2, 100, 2009, 12, 5000),
(7, 200, 2011, 15, 9000);
-- 创建 Product 表
CREATE TABLE Product (
product_id INT PRIMARY KEY,
product_name VARCHAR(100)
);
-- 插入数据到 Product 表
INSERT INTO Product (product_id, product_name) VALUES
(100, 'Nokia'),
(200, 'Apple'),
(300, 'Samsung');
select
p.product_name,s.year,s.price
from
Sales s
left join
Product p
on
s.product_id=p.product_id
INNER JOIN:我们将 Sales 表和 Product 表通过 product_id 字段进行连接。这样可以确保只有当 Sales 表中的 product_id 在 Product 表中存在时,才会返回相应的记录。
选择字段:我们选择了 Product 表中的 product_name 和 Sales 表中的 year 和 price 字段。
1581. 进店却未进行过交易的顾客
-- 创建 Visits 表
CREATE TABLE Visits (
visit_id INT,
customer_id INT,
PRIMARY KEY (visit_id)
);
-- 插入数据到 Visits 表
INSERT INTO Visits (visit_id, customer_id) VALUES
(1, 23),
(2, 9),
(4, 30),
(5, 54),
(6, 96),
(7, 54),
(8, 54);
-- 创建 Transactions 表
CREATE TABLE Transactions (
transaction_id INT,
visit_id INT,
amount INT,
PRIMARY KEY (transaction_id)
);
-- 插入数据到 Transactions 表
INSERT INTO Transactions (transaction_id, visit_id, amount) VALUES
(2, 5, 310),
(3, 5, 300),
(9, 5, 200),
(12, 1, 910),
(13, 2, 970);
select
v.customer_id,
count(v.visit_id) as count_no_trans
from
Visits v
left join
Transactions t
on v.visit_id=t.visit_id
where
t.visit_id is null
group by
v.customer_id;
LEFT JOIN:将 Visits 表和 Transactions 表通过 visit_id 字段进行左连接。这样可以确保所有 Visits 表中的记录都会出现在结果中,即使它们在 Transactions 表中没有对应的记录。
WHERE t.visit_id IS NULL:这个条件用于筛选出那些在 Transactions 表中没有对应记录的访问记录,即那些没有进行交易的访问。
GROUP BY v.customer_id:我们按 customer_id 分组,以便统计每个顾客的无交易访问次数。
COUNT(v.visit_id):计算每个顾客的无交易访问次数。
197. 上升的温度
找出那些温度比前一天高的日期,并返回这些日期对应的 <font style="color:rgb(44, 44, 54);">id</font>
。我们可以使用自连接(self join)来比较每一天的温度与其前一天的温度。
-- 创建 Weather 表
CREATE TABLE Weather (
id INT PRIMARY KEY,
recordDate DATE,
temperature INT
);
-- 插入数据到 Weather 表
INSERT INTO Weather (id, recordDate, temperature) VALUES
(1, '2015-01-01', 10),
(2, '2015-01-02', 25),
(3, '2015-01-03', 20),
(4, '2015-01-04', 30);
select w1.id
from
Weather w1
join
Weather w2
on w1.recordDate=date_add(w2.recordDate,interval 1 day)
where
w1.temperature > w2.temperature
- JOIN:我们将
<font style="color:rgb(44, 44, 54);background-color:#FAFAFA;">Weather</font>
表与自身进行连接,连接条件是<font style="color:rgb(44, 44, 54);background-color:#FAFAFA;">w1.recordDate</font>
等于<font style="color:rgb(44, 44, 54);background-color:#FAFAFA;">w2.recordDate</font>
的下一天(即<font style="color:rgb(44, 44, 54);background-color:#FAFAFA;">w1.recordDate = DATE_ADD(w2.recordDate, INTERVAL 1 DAY)</font>
)。这样可以确保我们在比较每一天的温度时,能够找到前一天的记录。 - WHERE w1.temperature > w2.temperature:这个条件用于筛选出那些温度比前一天高的记录。
- SELECT w1.id:我们只需要返回符合条件的记录的
<font style="color:rgb(44, 44, 54);background-color:#FAFAFA;">id</font>
。
1661. 每台机器的进程平均运行时间
- 计算每个进程的运行时间:对于每个进程,我们需要找到它的开始时间和结束时间,并计算它们之间的差值。
- 按机器分组:将计算出的运行时间按机器分组,计算每台机器的总运行时间和进程数量。
- 计算平均耗时:对于每台机器,计算总运行时间除以进程数量,并将结果四舍五入到三位小数。
-- 创建 Activity 表
CREATE TABLE Activity (
machine_id INT,
process_id INT,
activity_type ENUM('start', 'end'),
timestamp FLOAT,
PRIMARY KEY (machine_id, process_id, activity_type)
);
-- 插入数据到 Activity 表
INSERT INTO Activity (machine_id, process_id, activity_type, timestamp) VALUES
(0, 0, 'start', 0.712),
(0, 0, 'end', 1.520),
(0, 1, 'start', 3.140),
(0, 1, 'end', 4.120),
(1, 0, 'start', 0.550),
(1, 0, 'end', 1.550),
(1, 1, 'start', 0.430),
(1, 1, 'end', 1.420),
(2, 0, 'start', 4.100),
(2, 0, 'end', 4.512),
(2, 1, 'start', 2.500),
(2, 1, 'end', 5.000);
select a1.machine_id,
round(avg(a2.timestamp-a1.timestamp),3) as processing_time
from Activity a1
join Activity a2
on a1.machine_id=a2.machine_id and a1.process_id=a2.process_id
where
a1.activity_type='start' and a2.activity_type='end'
group by
a1.machine_id;
- JOIN:我们将
<font style="color:rgb(44, 44, 54);background-color:#FAFAFA;">Activity</font>
表与自身进行连接,连接条件是<font style="color:rgb(44, 44, 54);background-color:#FAFAFA;">a1.machine_id = a2.machine_id</font>
和<font style="color:rgb(44, 44, 54);background-color:#FAFAFA;">a1.process_id = a2.process_id</font>
。这样可以确保我们在比较每个进程的开始时间和结束时间时,能够找到对应的记录。 - WHERE a1.activity_type = ‘start’ AND a2.activity_type = ‘end’:这个条件用于筛选出开始时间和结束时间的记录。
- a2.timestamp - a1.timestamp:计算每个进程的运行时间。
- AVG(a2.timestamp - a1.timestamp):计算每台机器上所有进程的平均运行时间。
- ROUND(…, 3):将结果四舍五入到三位小数。
- GROUP BY a1.machine_id:按机器分组,以便计算每台机器的平均运行时间。
+------------+-----------------+
| machine_id | processing_time |
+------------+-----------------+
| 0 | 0.894 |
| 1 | 0.995 |
| 2 | 1.456 |
+------------+-----------------+
577. 员工奖金
-- 创建 Employee 表
CREATE TABLE Employee (
empId INT PRIMARY KEY,
name VARCHAR(100),
supervisor INT,
salary INT
);
-- 插入数据到 Employee 表
INSERT INTO Employee (empId, name, supervisor, salary) VALUES
(3, 'Brad', null, 4000),
(1, 'John', 3, 1000),
(2, 'Dan', 3, 2000),
(4, 'Thomas', 3, 4000);
-- 创建 Bonus 表
CREATE TABLE Bonus (
empId INT PRIMARY KEY,
bonus INT
);
-- 插入数据到 Bonus 表
INSERT INTO Bonus (empId, bonus) VALUES
(2, 500),
(4, 2000);
select
e.name,b.bonus
from Employee e
left join Bonus b
on e.empId=b.empId
where
b.bonus<1000 or b.bonus is null;
● LEFT JOIN:我们将 Employee 表和 Bonus 表通过 empId 字段进行左连接。这样可以确保所有 Employee 表中的记录都会出现在结果中,即使它们在 Bonus 表中没有对应的记录。
● WHERE b.bonus < 1000 OR b.bonus IS NULL:这个条件用于筛选出那些奖金少于 1000 或者没有奖金记录的员工。
● SELECT e.name, b.bonus:我们选择员工的姓名和奖金数额。