直接用substring提取身份证出生日期常出错,因实际数据常含15位旧号、空格、字母、缺失值等非标准格式;必须先校验length=18且全为数字,再substring(id_card,7,8),最后用str_to_date等转日期类型才能正确排序计算。

为什么直接用 SUBSTRING 提取身份证出生日期常出错?
因为中国大陆18位身份证号的出生日期固定占第7到第14位(共8个字符),但很多人误以为要靠 CHARINDEX 去“查找”某个分隔符——身份证里根本没有分隔符。强行套用 CHARINDEX 不仅多余,还可能因逻辑错误返回空或越界结果。
真正需要 CHARINDEX 的场景是:你拿到的是混合格式数据(比如“张三_11010119900307251X”或“ID:11010119900307251X”),此时才需先定位数字起始位置。
- 标准18位身份证:直接
SUBSTRING(id_card, 7, 8)即可,无需CHARINDEX - 非标准字符串包裹身份证:先用
CHARINDEX找第一个数字位置,再结合LEN或正则(如支持)截取连续18位数字 - SQL Server 中若字段含空格或换行,务必先
TRIM(SQL Server 2017+)或RTRIM(LTRIM()),否则SUBSTRING可能从空格开始数位
SQL Server 中正确提取出生日期的写法
假设表 users 有字段 id_card,且已确认为合规18位字符串:
SELECT id_card, SUBSTRING(id_card, 7, 4) + '-' + SUBSTRING(id_card, 11, 2) + '-' + SUBSTRING(id_card, 13, 2) AS birth_date FROM users WHERE LEN(TRIM(id_card)) = 18 AND id_card NOT LIKE '%[^0-9Xx]%';
这里做了三件事:按位置拆年月日、拼成 YYYY-MM-DD 格式、加校验过滤非法值。注意:
-
SUBSTRING第二参数是起始位置(从1开始),不是索引(0-based) - 第18位可能是
X(罗马数字10),但出生日期段(7–14位)只含数字,无需特殊处理 - 没加
ISDATE()校验,因为19900230这种无效日期也能通过SUBSTRING,得靠业务层或后续TRY_CONVERT(DATE, ...)捕获
MySQL 或 PostgreSQL 怎么办?
它们没有 CHARINDEX(MySQL 用 LOCATE,PostgreSQL 用 POSITION),但同样不需要它来处理标准身份证。核心差异在函数名和空值处理:
- MySQL:
SUBSTR(id_card, 7, 8)(SUBSTR和SUBSTRING等价),注意LENGTH要用字节长度判断时需配合utf8mb4设置 - PostgreSQL:
SUBSTRING(id_card FROM 7 FOR 8),且建议用REGEXP_REPLACE预清洗,例如REGEXP_REPLACE(TRIM(id_card), '[^0-9Xx]', '', 'g')提纯数字 - 所有方言中,如果字段允许 NULL,必须加
WHERE id_card IS NOT NULL,否则SUBSTRING(NULL, 7, 8)直接返回 NULL,不报错也不提示
最容易被忽略的验证环节
光截出8位字符串不等于得到了合法出生日期。常见漏掉的点:
- 没检查第17位奇偶性校验(虽不影响日期提取,但能辅助识别伪造号)
- 把
19900230当有效日期用了——2月不可能有30日,但SUBSTRING完全不管这个 - 忽略港澳台或外籍人士可能使用非18位证件号,
WHERE LEN(...) = 18会直接丢弃这些记录,而不是报错提醒 - 前端传入的身份证号末尾带不可见字符(如
\r\n),TRIM在部分旧版 SQL Server 中不处理 Unicode 空白,得用REPLACE(REPLACE(id_card, CHAR(13), ''), CHAR(10), '')











