身份证第17位奇数为男、偶数为女;第7至14位为出生日期(YYYYMMDD格式),可直接提取并转为标准日期。

身份证号里怎么提取性别和生日
18位身份证号的结构是固定的:6位地址码 + 8位出生日期(YYYYMMDD) + 3位顺序码 + 1位校验码。其中第17位(倒数第二位)决定性别:奇数为男,偶数为女;第7到第14位就是出生日期,可直接转为DATE类型。
注意:15位老身份证已基本淘汰,不建议兼容;若真遇到,需补‘19’前缀并转换为18位再处理——但绝大多数系统现在只收18位,强制校验更稳妥。
SQL Server里用触发器自动填充性别和生日字段
假设表结构为:CREATE TABLE users (id INT IDENTITY, id_card VARCHAR(18), gender CHAR(1), birthday DATE);,目标是在INSERT时根据id_card自动设gender和birthday。
关键点不是“能不能做”,而是“要不要在数据库层做”。触发器可行,但有隐患:
- 插入时
id_card为空或格式错误(如含字母、长度不对),SUBSTRING会报错或返回NULL,需提前校验 -
CONVERT(DATE, ...)对非法日期(如20230230)直接抛异常,必须用TRY_CONVERT(SQL Server 2012+) - 触发器逻辑无法被ORM或批量导入工具绕过,但调试困难、性能不可见,不如应用层统一处理
最小可用触发器示例:
CREATE TRIGGER tr_fill_gender_birthday ON users
AFTER INSERT
AS
BEGIN
UPDATE u
SET
gender = CASE WHEN TRY_CAST(SUBSTRING(i.id_card, 17, 1) AS INT) % 2 = 1 THEN 'M' ELSE 'F' END,
birthday = TRY_CONVERT(DATE, SUBSTRING(i.id_card, 7, 8))
FROM users u
INNER JOIN inserted i ON u.id = i.id
WHERE i.id_card IS NOT NULL AND LEN(i.id_card) = 18;
END;MySQL 8.0+用生成列更安全简洁
比起触发器,生成列(GENERATED COLUMN)是声明式方案,不执行运行时逻辑,无性能开销,且数据一致性由引擎保证。
建表时直接定义:
CREATE TABLE users (
id INT PRIMARY KEY AUTO_INCREMENT,
id_card VARCHAR(18),
gender CHAR(1) GENERATED ALWAYS AS (
CASE
WHEN LENGTH(id_card) = 18 AND id_card REGEXP '^[0-9]{18}$'
THEN IF(CONVERT(SUBSTRING(id_card, 17, 1), SIGNED) % 2 = 1, 'M', 'F')
ELSE NULL
END
) STORED,
birthday DATE GENERATED ALWAYS AS (
CASE
WHEN LENGTH(id_card) = 18 AND id_card REGEXP '^[0-9]{18}$'
THEN STR_TO_DATE(SUBSTRING(id_card, 7, 8), '%Y%m%d')
ELSE NULL
END
) STORED
);要点:
-
REGEXP '^[0-9]{18}$'过滤非纯数字,避免STR_TO_DATE静默失败 -
STORED表示物理存储,查询快;用VIRTUAL则每次读取都计算(适合低频场景) - 生成列不能被
INSERT/UPDATE显式赋值,防止人工覆盖逻辑结果
PostgreSQL用表达式索引或函数封装更灵活
PG没有原生生成列(12+支持GENERATED ALWAYS AS,但兼容性不如MySQL),常用方式是写函数+默认值或视图。
推荐在应用插入前调用函数解析,或建视图隐藏逻辑:
CREATE OR REPLACE FUNCTION id_card_gender(card TEXT)
RETURNS CHAR(1) AS $$
SELECT CASE WHEN length($1) = 18 AND $1 ~ '^\d{18}$'
THEN CASE WHEN (substr($1, 17, 1)::int % 2) = 1 THEN 'M' ELSE 'F' END
ELSE NULL END;
$$ LANGUAGE SQL IMMUTABLE;
<p>-- 视图暴露解析后字段
CREATE VIEW users_with_meta AS
SELECT *,
id_card_gender(id_card) AS gender,
TO_DATE(SUBSTR(id_card, 7, 8), 'YYYYMMDD') AS birthday
FROM users
WHERE id_card ~ '^\d{18}$';注意:TO_DATE对非法字符串会报错,所以视图加了WHERE过滤;若要容忍脏数据,改用NULLIF配合TO_DATE(..., 'YYYYMMDD') DEFAULT NULL组合。
真正麻烦的从来不是“怎么写那几行SQL”,而是身份证号本身可能不合法、前端没校验、历史数据混着15位和18位、港澳台证件格式不同……自动填充只是最后一环,前置的数据清洗和约束才决定它稳不稳。

















