身份证第7–14位为出生日期,但须先校验18位格式合法(LENGTH=18且正则匹配)、排除NULL/空值;再截取转换为DATE类型;最后用数据库特有函数(如MySQL的TIMESTAMPDIFF)准确计算年龄,避免年份相减误差。

身份证号第7到第14位就是出生日期,但得先校验格式
中国大陆18位身份证号的第7–14位是YYYYMMDD格式的出生日期(如19920815),但直接截取可能出错:有人用15位旧号(无年份前两位)、或字段含空格/字母、或长度不足。所以第一步不是SUBSTR,而是过滤掉非法值。
实操建议:
- 用
LENGTH(id_card) = 18和id_card REGEXP '^[0-9]{17}[0-9Xx]$'(MySQL)或id_card ~ '^[0-9]{17}[0-9Xx]$'(PostgreSQL)做基础校验 - 避免对
NULL或空字符串执行SUBSTR,加WHERE id_card IS NOT NULL AND TRIM(id_card) != '' - 15位身份证需补“19”前缀再处理,但业务上建议统一升级为18位,不推荐在SQL里动态补全
用SUBSTR + CAST/CONVERT把生日字符串转成DATE类型
不同数据库对字符串转日期的函数名和格式要求不同,硬写'YYYYMMDD'格式通常能跨库兼容,但必须显式指定格式或依赖默认解析规则。
实操建议:
- MySQL:
STR_TO_DATE(SUBSTR(id_card, 7, 8), '%Y%m%d')—— 必须用STR_TO_DATE,CAST(... AS DATE)在部分版本会失败 - PostgreSQL:
TO_DATE(SUBSTR(id_card, 7, 8), 'YYYYMMDD')—— 注意大小写,yyyy-mm-dd会报错 - SQL Server:
CONVERT(DATE, SUBSTRING(id_card, 7, 4) + '-' + SUBSTRING(id_card, 11, 2) + '-' + SUBSTRING(id_card, 13, 2))—— 因为CONVERT不支持YYYYMMDD直转,得先拼成YYYY-MM-DD
计算年龄不能只用YEAR(NOW()) - YEAR(birth_date)
用年份相减会高估年龄,比如生日还没到就多算一岁。正确做法是对比「今天」和「今年生日」是否已过。
实操建议:
- MySQL:用
TIMESTAMPDIFF(YEAR, birth_date, CURDATE())—— 这是唯一可靠方案,自动处理月份日逻辑 - PostgreSQL:用
EXTRACT(YEAR FROM AGE(CURRENT_DATE, birth_date))::INT - SQL Server:用
DATEDIFF(YEAR, birth_date, GETDATE()) - CASE WHEN MONTH(birth_date) > MONTH(GETDATE()) OR (MONTH(birth_date) = MONTH(GETDATE()) AND DAY(birth_date) > DAY(GETDATE())) THEN 1 ELSE 0 END - 别用
ROUND(DATEDIFF(...)/365.25),闰年误差累积明显,尤其对婴幼儿或老人
实际查询语句要兼顾可读性与NULL安全
把上面几步串起来时,嵌套过深会让SQL难以调试,且一旦某步返回NULL(如截取失败),整个AGE计算就会变成NULL,但你可能希望标为“未知”。
实操建议:
- 用CTE或子查询拆开步骤,例如先
SELECT id_card, SUBSTR(id_card, 7, 8) AS yyyymmdd,再在外层转换和计算 - 最终年龄列用
COALESCE(age, -1)或CASE WHEN age IS NULL THEN '未知' ELSE age END显式标记异常 - 如果查大批量数据,注意
SUBSTR+STR_TO_DATE等函数无法使用索引,WHERE条件里别对id_card做函数操作;生日应单独存为DATE字段并建索引
真正麻烦的不是截取或转换,而是身份证号本身质量——OCR识别错误、手工录入缺位、港澳台证件混入、一代证未换二代证……这些都会让任何SQL逻辑返回错误结果。业务系统里,年龄最好由前端或ETL层标准化后写入,而不是每次查询都现场算。










