大家好,数据库表中的数据经常会很杂乱。数据可能包含缺失值、重复记录、异常值、不一致的数据输入等,在使用SQL进行分析之前清洗数据是非常重要的。
当学习SQL时,可以随意地创建数据库表,更改它们,根据需要更新和删除记录。但在实际操作中,几乎从不会这样,因为可能没有权限更改表、更新和删除记录。但如果有数据库的读取权限,可以运行大量的SELECT查询。
本文将创建一个数据库表,在其中填充记录,并了解如何使用SQL清洗数据。
创建一个名为employees
的员工表,如下所示:
-- 创建employees表
CREATE TABLE employees (
employee_id INT PRIMARY KEY,
employee_name VARCHAR(50),
salary DECIMAL(10, 2),
hire_date VARCHAR(20),
department VARCHAR(50)
);
接下来,向表中插入一些虚构的样本记录:
-- 插入20个样本记录
INSERT INTO employees (employee_id, employee_name, salary, hire_date, department) VALUES
(1, 'Amy West', 60000.00, '2021-01-15', 'HR'),
(2, 'Ivy Lee', 75000.50, '2020-05-22', 'Sales'),
(3, 'joe smith', 80000.75, '2019-08-10', 'Marketing'),
(4, 'John White', 90000.00, '2020-11-05', 'Finance'),
(5, 'Jane Hill', 55000.25, '2022-02-28', 'IT'),
(6, 'Dave West', 72000.00, '2020-03-12', 'Marketing'),
(7, 'Fanny Lee', 85000.50, '2018-06-25', 'Sales'),
(8, 'Amy Smith', 95000.25, '2019-11-30', 'Finance'),
(9, 'Ivy Hill', 62000.75, '2021-07-18', 'IT'),
(10, 'Joe White', 78000.00, '2022-04-05', 'Marketing'),
(11, 'John Lee', 68000.50, '2018-12-10', 'HR'),
(12, 'Jane West', 89000.25, '2017-09-15', 'Sales'),
(13, 'Dave Smith', 60000.75, '2022-01-08', NULL),
(14, 'Fanny White', 72000.00, '2019-04-22', 'IT'),
(15, 'Amy Hill', 84000.50, '2020-08-17', 'Marketing'),
(16, 'Ivy West', 92000.25, '2021-02-03', 'Finance'),
(17, 'Joe Lee', 58000.75, '2018-05-28', 'IT'),
(18, 'John Smith', 77000.00, '2019-10-10', 'HR'),
(19, 'Jane Hill', 81000.50, '2022-03-15', 'Sales'),
(20, 'Dave White', 70000.25, '2017-12-20', 'Marketing');
这里使用了一小部分名字和姓氏作为样本,并为记录构建了姓名字段。
注意:本教程中的所有查询都是针对MySQL的,但可以自由选择使用喜欢的关系型数据库管理系统(RDBMS)。
数据记录中的缺失值总是一个问题,因此必须对其进行相应的处理。
一种简单的方法是删除包含一个或多个字段缺失值的所有记录。然而,除非确定没有其他更好的处理缺失值的方法,否则不应该这样做。
在employees
表中,可以看到department
列中有一个NULL值(参见employee_id
为13
的行),表示该字段缺失:
SELECT * FROM employees;
可以使用COALESCE()
函数将NULL值替换为Unknown
字符串:
SELECT
employee_id,
employee_name,
salary,
hire_date,
COALESCE(department, 'Unknown') AS department
FROM employees;
运行上述查询应该会给出以下结果:
数据库表中的重复记录可能会扭曲分析结果。在数据库表中选择了employee_id
作为主键,因此在employee_data
表中不会有重复的员工记录。
仍然可以使用SELECT DISTINCT
语句:
SELECT DISTINCT * FROM employees;
如预期所示,结果集包含了所有的20条记录:
可以注意到,hire_date
列目前是VARCHAR类型,而不是日期类型。为了在处理日期时更方便,使用STR_TO_DATE()
函数,如下所示:
SELECT
employee_id,
employee_name,
salary,
STR_TO_DATE(hire_date, '%Y-%m-%d') AS hire_date,
department
FROM employees;
在这里只选择了hire_date
列,而没有对日期值执行任何操作。因此,查询的输出结果应与前一个查询的结果相同。
如果想执行诸如给值添加偏移日期之类的操作,那么该函数可能会有所帮助。
一个或多个数值字段中的异常值可能会影响分析结果,因此应该检查并清除异常值,以过滤掉不相关的数据。判断哪些值构成异常值需要领域知识,还需要利用领域知识和历史数据。
假设知道salary
列的上限为100000,因此,salary
列中的任何条目最多只能是100000,而大于此值的条目则是异常值。
可以通过运行以下查询来检查这样的记录:
SELECT *
FROM employees
WHERE salary > 100000;
如图所示,salary
列中的所有条目都是有效的。因此结果集为空:
数据输入和格式不一致的情况很常见,尤其是在日期和字符串列中。
在employees
表中,可以看到员工joe smith
对应的记录不是以标题大小写形式显示的。
但是,为了保持一致性,选择所有以标题大小写格式显示的姓名。需要将CONCAT()
函数与UPPER()
和SUBSTRING()
函数结合使用,如下所示:
SELECT
employee_id,
CONCAT(
UPPER(SUBSTRING(employee_name, 1, 1)), -- Capitalize the first letter of the first name
LOWER(SUBSTRING(employee_name, 2, LOCATE(' ', employee_name) - 2)), -- Make the rest of the first name lowercase
' ',
UPPER(SUBSTRING(employee_name, LOCATE(' ', employee_name) + 1, 1)), -- Capitalize the first letter of the last name
LOWER(SUBSTRING(employee_name, LOCATE(' ', employee_name) + 2)) -- Make the rest of the last name lowercase
) AS employee_name_title_case,
salary,
hire_date,
department
FROM employees;
在谈论异常值时,希望对salary
列设置上限为100000,并将任何超过100000的薪资条目视为异常值。但同样也不能在salary
列中有任何负值。因此,可以运行以下查询来验证所有员工记录的salary
列值是否都在0和100000之间:
SELECT
employee_id,
employee_name,
salary,
hire_date,
department
FROM employees
WHERE salary < 0 OR salary > 100000;
如图所示,salary
列值都在0和100000之间。因此结果集为空:
派生新列本质上并不是数据清洗的步骤。在实际操作中,可能需要使用现有列派生出对分析更有帮助的新列。
例如,员工表包含一个hire_date
列。更有帮助的字段可能是一个years_of_service
列,表示员工在公司任职的年限。
以下查询会计算当前年份与hire_date
中年份值的差值,从而计算出years_of_service
:
SELECT
employee_id,
employee_name,
salary,
hire_date,
department,
YEAR(CURDATE()) - YEAR(hire_date) AS years_of_service
FROM employees;
应该会看到以下输出:
与我们运行的其他查询一样,这不会修改原始表。要向原始表中添加新列,需要拥有ALTER数据库表的权限。
综上,希望大家理解相关的数据清洗任务如何提高数据质量并促进更相关的分析,同时学习如何检查缺失值、重复记录、不一致的格式、异常值等。可以尝试创建自己的关系型数据库表,并运行一些查询来执行常见的数据清洗任务。