在企业数据处理流程中,ETL是连接原始数据源和最终分析应用的核心环节,而数据质量则是ETL过程的核心指标之一。如果脏数据、异常数据没有被及时拦截,最终输出的分析结果会出现偏差,甚至导致业务决策失误。很多团队会引入专门的数据质量工具,但实际上SQL语言已经足够覆盖绝大多数ETL场景下的数据质量检查需求,而且不需要额外增加技术栈复杂度,直接在数据库内就能完成校验逻辑。

数据质量检查的核心维度
在设计SQL检查规则之前,首先需要明确数据质量的核心评估维度,不同维度的校验逻辑对应不同的SQL实现方式,常见的核心维度包括以下几点:
- 完整性:检查数据是否存在缺失值,比如必填字段是否为空,关联表的数据是否缺失
- 准确性:检查数据是否符合业务定义的格式和范围,比如手机号格式、年龄范围、枚举值是否合法
- 一致性:检查不同表、不同字段之间的数据逻辑是否统一,比如订单金额和明细金额之和是否匹配
- 唯一性:检查数据是否存在重复记录,比如主键是否重复,业务唯一约束是否被违反
- 时效性:检查数据是否在规定时间内更新,比如昨日数据是否已经同步到目标表
基于SQL的完整性检查规则实现
完整性是最基础的数据质量检查项,通常针对必填字段和非空约束展开,以下是几个典型场景的实现示例。
必填字段空值检查
假设我们有一张用户表user_info,其中user_id、user_name、phone是必填字段,需要检查这些字段是否存在空值:
-- 检查用户表必填字段空值情况
SELECT
'user_info' AS table_name,
'user_id' AS column_name,
COUNT(*) AS null_count
FROM user_info
WHERE user_id IS NULL
UNION ALL
SELECT
'user_info' AS table_name,
'user_name' AS column_name,
COUNT(*) AS null_count
FROM user_info
WHERE user_name IS NULL
UNION ALL
SELECT
'user_info' AS table_name,
'phone' AS column_name,
COUNT(*) AS null_count
FROM user_info
WHERE phone IS NULL;这段SQL会分别统计三个必填字段的空值数量,如果返回的null_count大于0,说明存在完整性问题,需要触发告警或者拦截数据。
关联数据缺失检查
在ETL过程中经常需要关联多张表,比如订单表order_info关联用户表user_info,需要检查订单对应的用户是否存在:
-- 检查订单表中不存在对应用户的异常数据
SELECT
o.order_id,
o.user_id,
'订单对应用户不存在' AS error_msg
FROM order_info o
LEFT JOIN user_info u ON o.user_id = u.user_id
WHERE u.user_id IS NULL;基于SQL的准确性检查规则实现
准确性检查需要结合业务规则定义,不同字段的校验逻辑差异较大,以下是几个通用场景的实现。
格式合法性检查
比如手机号需要符合11位数字的格式,身份证需要符合18位或者15位的格式,可以用正则函数实现:
-- 检查用户表中手机号格式是否合法,以MySQL为例
SELECT
user_id,
phone,
'手机号格式错误' AS error_msg
FROM user_info
WHERE phone IS NOT NULL
AND phone NOT REGEXP '^1[3-9][0-9]{9}$';
-- 检查身份证格式,支持15位和18位(最后一位可以是X)
SELECT
user_id,
id_card,
'身份证格式错误' AS error_msg
FROM user_info
WHERE id_card IS NOT NULL
AND id_card NOT REGEXP '^[1-9][0-9]{14}$|^[1-9][0-9]{16}[0-9Xx]$';如果使用的是不支持正则的数据库,也可以用字符串函数组合实现,比如检查手机号长度是否为11,第一位是否为1:
-- 非正则方式检查手机号格式,适配更多数据库
SELECT
user_id,
phone,
'手机号格式错误' AS error_msg
FROM user_info
WHERE phone IS NOT NULL
AND (LENGTH(phone) != 11 OR LEFT(phone, 1) != '1' OR phone NOT REGEXP '^[0-9]+$');范围合法性检查
比如年龄需要在0到120之间,订单金额不能为负数,这类检查可以直接用条件判断实现:
-- 检查用户年龄范围是否合法
SELECT
user_id,
age,
'年龄超出合理范围' AS error_msg
FROM user_info
WHERE age IS NOT NULL
AND (age < 0 OR age > 120);
-- 检查订单金额是否为负数
SELECT
order_id,
order_amount,
'订单金额不能为负数' AS error_msg
FROM order_info
WHERE order_amount < 0;枚举值合法性检查
如果字段的取值是固定的枚举范围,比如订单状态只能是0(待支付)、1(已支付)、2(已取消)、3(已完成),可以检查是否存在不在枚举范围内的值:
-- 检查订单状态枚举值是否合法
SELECT
order_id,
order_status,
'订单状态枚举值错误' AS error_msg
FROM order_info
WHERE order_status IS NOT NULL
AND order_status NOT IN (0, 1, 2, 3);基于SQL的一致性和唯一性检查规则实现
一致性检查
一致性检查通常针对跨表或者跨字段的逻辑匹配,比如订单的总金额应该等于所有订单明细的金额之和:
-- 检查订单总金额和明细金额是否一致
SELECT
o.order_id,
o.order_amount AS total_amount,
d.detail_sum AS detail_amount,
'订单总金额与明细金额不匹配' AS error_msg
FROM order_info o
LEFT JOIN (
SELECT
order_id,
SUM(item_amount) AS detail_sum
FROM order_detail
GROUP BY order_id
) d ON o.order_id = d.order_id
WHERE o.order_amount != d.detail_sum
OR (o.order_amount IS NULL AND d.detail_sum IS NOT NULL)
OR (o.order_amount IS NOT NULL AND d.detail_sum IS NULL);另外还可以检查同表内的逻辑一致性,比如订单的支付时间不能早于下单时间:
-- 检查订单支付时间是否早于下单时间
SELECT
order_id,
order_time,
pay_time,
'支付时间早于下单时间' AS error_msg
FROM order_info
WHERE pay_time IS NOT NULL
AND order_time IS NOT NULL
AND pay_time < order_time;唯一性检查
唯一性检查主要针对主键或者业务唯一键,比如用户表的user_id应该是唯一的,订单表的order_id应该是唯一的:
-- 检查用户表user_id是否重复
SELECT
user_id,
COUNT(*) AS repeat_count,
'user_id重复' AS error_msg
FROM user_info
GROUP BY user_id
HAVING COUNT(*) > 1;
-- 检查订单表order_id是否重复
SELECT
order_id,
COUNT(*) AS repeat_count,
'order_id重复' AS error_msg
FROM order_info
GROUP BY order_id
HAVING COUNT(*) > 1;如果是组合唯一键,比如订单明细表中同一个订单同一个商品的记录应该唯一,可以在GROUP BY中传入多个字段:
-- 检查订单明细表组合唯一键是否重复(order_id+product_id)
SELECT
order_id,
product_id,
COUNT(*) AS repeat_count,
'同一订单同一商品记录重复' AS error_msg
FROM order_detail
GROUP BY order_id, product_id
HAVING COUNT(*) > 1;基于SQL的时效性检查规则实现
时效性检查主要用于验证ETL同步的及时性,比如需要检查昨日的数据是否已经全部同步到目标表:
-- 检查昨日数据是否同步,假设数据中有dt分区字段,格式为yyyy-MM-dd
SELECT
'order_info' AS table_name,
dt,
COUNT(*) AS data_count,
CASE
WHEN dt = DATE_FORMAT(DATE_SUB(CURDATE(), INTERVAL 1 DAY), '%Y-%m-%d') AND COUNT(*) = 0
THEN '昨日数据未同步'
ELSE '正常'
END AS check_result
FROM order_info
WHERE dt = DATE_FORMAT(DATE_SUB(CURDATE(), INTERVAL 1 DAY), '%Y-%m-%d')
GROUP BY dt;如果是实时ETL场景,还可以检查数据的更新时间是否在最近1小时内:
-- 检查最近1小时内是否有数据更新
SELECT
'order_info' AS table_name,
MAX(update_time) AS last_update_time,
CASE
WHEN MAX(update_time) < DATE_SUB(NOW(), INTERVAL 1 HOUR)
THEN '超过1小时未更新数据'
ELSE '更新正常'
END AS check_result
FROM order_info;数据质量检查结果的处理
完成SQL检查规则编写后,还需要考虑检查结果的处理方式,通常有三种常见的处理策略:
- 拦截阻断:如果检查到核心字段的致命错误,比如主键重复、必填字段大量空值,直接终止ETL流程,避免脏数据流入下游
- 告警通知:如果是非致命错误,比如少量格式异常,记录错误数据并发送告警给数据负责人,人工介入处理
- 自动修复:部分可修复的错误可以自动处理,比如空值填充默认值、格式错误的数据尝试转换,修复后重新校验
可以将所有检查规则封装成存储过程,每次ETL运行后自动调用,将检查结果写入专门的data_quality_check_log表,方便后续追溯和统计:
-- 创建数据质量检查日志表
CREATE TABLE IF NOT EXISTS data_quality_check_log (
log_id INT PRIMARY KEY AUTO_INCREMENT,
check_time DATETIME DEFAULT CURRENT_TIMESTAMP,
table_name VARCHAR(100),
column_name VARCHAR(100),
check_type VARCHAR(50),
error_count INT,
error_detail TEXT,
handle_status VARCHAR(20) DEFAULT '待处理'
);
-- 将空值检查结果写入日志表
INSERT INTO data_quality_check_log (table_name, column_name, check_type, error_count, error_detail)
SELECT
'user_info' AS table_name,
'user_name' AS column_name,
'完整性检查' AS check_type,
COUNT(*) AS error_count,
CONCAT('用户表user_name字段存在', COUNT(*), '条空值记录') AS error_detail
FROM user_info
WHERE user_name IS NULL;总结
SQL语言完全可以覆盖ETL过程中绝大多数数据质量检查需求,开发者只需要根据业务定义的质量维度,编写对应的校验SQL即可,不需要额外引入复杂工具。在实际落地时,建议将所有检查规则标准化,形成可复用的规则库,同时结合日志表和告警机制,实现数据质量检查的自动化和常态化。随着数据量的增长,还可以针对大表优化SQL查询性能,比如添加合适的索引、分批执行检查任务,避免检查过程影响ETL的整体效率。