You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

多列含不同长度重复组的表转换为1NF的SQL实现方法

将多值列表转换为1NF的SQL方案

1NF的核心要求是所有列的值必须是不可再分的原子值,不能存在逗号分隔的多值组合。针对你的表中courses、hobbies、fav_colors三个多值列,最合理的做法是拆分出主表和多个关联表,既符合1NF规范,又避免数据冗余。以下是主流数据库的具体实现代码:

前提假设

假设你的原始表名为user_profiles,结构与示例一致。


一、MySQL 8.0+ 实现

1. 创建用户主表(存储唯一用户)

-- 创建用户主表,用自增ID作为主键(避免name重复问题)
CREATE TABLE users (
    user_id INT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(50) NOT NULL UNIQUE
);

-- 从原表提取唯一用户插入主表
INSERT INTO users (name)
SELECT DISTINCT name FROM user_profiles;

2. 拆分courses列到关联表

-- 创建用户-课程关联表
CREATE TABLE user_courses (
    user_id INT,
    course VARCHAR(50) NOT NULL,
    PRIMARY KEY (user_id, course), -- 避免同一用户重复记录同一课程
    FOREIGN KEY (user_id) REFERENCES users(user_id)
);

-- 递归拆分逗号分隔的课程数据并插入
WITH RECURSIVE split_courses AS (
    SELECT 
        name,
        SUBSTRING_INDEX(courses, ',', 1) AS course,
        SUBSTRING(courses, LENGTH(SUBSTRING_INDEX(courses, ',', 1)) + 2) AS remaining_courses
    FROM user_profiles
    WHERE courses IS NOT NULL AND courses != ''
    UNION ALL
    SELECT 
        name,
        SUBSTRING_INDEX(remaining_courses, ',', 1) AS course,
        SUBSTRING(remaining_courses, LENGTH(SUBSTRING_INDEX(remaining_courses, ',', 1)) + 2) AS remaining_courses
    FROM split_courses
    WHERE remaining_courses IS NOT NULL AND remaining_courses != ''
)
INSERT INTO user_courses (user_id, course)
SELECT u.user_id, TRIM(sc.course) -- 去除字符串前后空格
FROM split_courses sc
JOIN users u ON sc.name = u.name;

3. 拆分hobbies列到关联表

-- 创建用户-爱好关联表
CREATE TABLE user_hobbies (
    user_id INT,
    hobby VARCHAR(50) NOT NULL,
    PRIMARY KEY (user_id, hobby),
    FOREIGN KEY (user_id) REFERENCES users(user_id)
);

-- 递归拆分爱好数据并插入
WITH RECURSIVE split_hobbies AS (
    SELECT 
        name,
        SUBSTRING_INDEX(hobbies, ',', 1) AS hobby,
        SUBSTRING(hobbies, LENGTH(SUBSTRING_INDEX(hobbies, ',', 1)) + 2) AS remaining_hobbies
    FROM user_profiles
    WHERE hobbies IS NOT NULL AND hobbies != ''
    UNION ALL
    SELECT 
        name,
        SUBSTRING_INDEX(remaining_hobbies, ',', 1) AS hobby,
        SUBSTRING(remaining_hobbies, LENGTH(SUBSTRING_INDEX(remaining_hobbies, ',', 1)) + 2) AS remaining_hobbies
    FROM split_hobbies
    WHERE remaining_hobbies IS NOT NULL AND remaining_hobbies != ''
)
INSERT INTO user_hobbies (user_id, hobby)
SELECT u.user_id, TRIM(sh.hobby)
FROM split_hobbies sh
JOIN users u ON sh.name = u.name;

4. 拆分fav_colors列到关联表

-- 创建用户-偏好颜色关联表
CREATE TABLE user_fav_colors (
    user_id INT,
    fav_color VARCHAR(50) NOT NULL,
    PRIMARY KEY (user_id, fav_color),
    FOREIGN KEY (user_id) REFERENCES users(user_id)
);

-- 递归拆分颜色数据并插入
WITH RECURSIVE split_colors AS (
    SELECT 
        name,
        SUBSTRING_INDEX(fav_colors, ',', 1) AS fav_color,
        SUBSTRING(fav_colors, LENGTH(SUBSTRING_INDEX(fav_colors, ',', 1)) + 2) AS remaining_colors
    FROM user_profiles
    WHERE fav_colors IS NOT NULL AND fav_colors != ''
    UNION ALL
    SELECT 
        name,
        SUBSTRING_INDEX(remaining_colors, ',', 1) AS fav_color,
        SUBSTRING(remaining_colors, LENGTH(SUBSTRING_INDEX(remaining_colors, ',', 1)) + 2) AS remaining_colors
    FROM split_colors
    WHERE remaining_colors IS NOT NULL AND remaining_colors != ''
)
INSERT INTO user_fav_colors (user_id, fav_color)
SELECT u.user_id, TRIM(sc.fav_color)
FROM split_colors sc
JOIN users u ON sc.name = u.name;

二、PostgreSQL 实现

PostgreSQL提供了string_to_array和unnest函数,拆分多值列更简洁:

1. 创建用户主表

CREATE TABLE users (
    user_id SERIAL PRIMARY KEY,
    name VARCHAR(50) NOT NULL UNIQUE
);

INSERT INTO users (name)
SELECT DISTINCT name FROM user_profiles;

2. 拆分courses列

CREATE TABLE user_courses (
    user_id INT,
    course VARCHAR(50) NOT NULL,
    PRIMARY KEY (user_id, course),
    FOREIGN KEY (user_id) REFERENCES users(user_id)
);

INSERT INTO user_courses (user_id, course)
SELECT u.user_id, TRIM(unnest(string_to_array(up.courses, ','))) AS course
FROM user_profiles up
JOIN users u ON up.name = u.name
WHERE up.courses IS NOT NULL AND up.courses != '';

3. 拆分hobbies列

CREATE TABLE user_hobbies (
    user_id INT,
    hobby VARCHAR(50) NOT NULL,
    PRIMARY KEY (user_id, hobby),
    FOREIGN KEY (user_id) REFERENCES users(user_id)
);

INSERT INTO user_hobbies (user_id, hobby)
SELECT u.user_id, TRIM(unnest(string_to_array(up.hobbies, ','))) AS hobby
FROM user_profiles up
JOIN users u ON up.name = u.name
WHERE up.hobbies IS NOT NULL AND up.hobbies != '';

4. 拆分fav_colors列

CREATE TABLE user_fav_colors (
    user_id INT,
    fav_color VARCHAR(50) NOT NULL,
    PRIMARY KEY (user_id, fav_color),
    FOREIGN KEY (user_id) REFERENCES users(user_id)
);

INSERT INTO user_fav_colors (user_id, fav_color)
SELECT u.user_id, TRIM(unnest(string_to_array(up.fav_colors, ','))) AS fav_color
FROM user_profiles up
JOIN users u ON up.name = u.name
WHERE up.fav_colors IS NOT NULL AND up.fav_colors != '';

三、SQL Server 实现

SQL Server使用STRING_SPLIT函数拆分多值列:

1. 创建用户主表

CREATE TABLE users (
    user_id INT IDENTITY(1,1) PRIMARY KEY,
    name VARCHAR(50) NOT NULL UNIQUE
);

INSERT INTO users (name)
SELECT DISTINCT name FROM user_profiles;

2. 拆分courses列

CREATE TABLE user_courses (
    user_id INT,
    course VARCHAR(50) NOT NULL,
    PRIMARY KEY (user_id, course),
    FOREIGN KEY (user_id) REFERENCES users(user_id)
);

INSERT INTO user_courses (user_id, course)
SELECT u.user_id, TRIM(ss.value) AS course
FROM user_profiles up
JOIN users u ON up.name = u.name
CROSS APPLY STRING_SPLIT(up.courses, ',') ss
WHERE up.courses IS NOT NULL AND up.courses != '';

3. 拆分hobbies列

CREATE TABLE user_hobbies (
    user_id INT,
    hobby VARCHAR(50) NOT NULL,
    PRIMARY KEY (user_id, hobby),
    FOREIGN KEY (user_id) REFERENCES users(user_id)
);

INSERT INTO user_hobbies (user_id, hobby)
SELECT u.user_id, TRIM(ss.value) AS hobby
FROM user_profiles up
JOIN users u ON up.name = u.name
CROSS APPLY STRING_SPLIT(up.hobbies, ',') ss
WHERE up.hobbies IS NOT NULL AND up.hobbies != '';

4. 拆分fav_colors列

CREATE TABLE user_fav_colors (
    user_id INT,
    fav_color VARCHAR(50) NOT NULL,
    PRIMARY KEY (user_id, fav_color),
    FOREIGN KEY (user_id) REFERENCES users(user_id)
);

INSERT INTO user_fav_colors (user_id, fav_color)
SELECT u.user_id, TRIM(ss.value) AS fav_color
FROM user_profiles up
JOIN users u ON up.name = u.name
CROSS APPLY STRING_SPLIT(up.fav_colors, ',') ss
WHERE up.fav_colors IS NOT NULL AND up.fav_colors != '';

可选:扁平化1NF表(不推荐)

如果需要一个完全扁平化的1NF表(会产生大量数据冗余,仅适合临时需求),可以通过关联三个拆分后的表生成:

-- MySQL示例,其他数据库语法类似
CREATE TABLE flattened_1nf AS
SELECT 
    u.name,
    uc.course,
    uh.hobby,
    ufc.fav_color
FROM users u
JOIN user_courses uc ON u.user_id = uc.user_id
JOIN user_hobbies uh ON u.user_id = uh.user_id
JOIN user_fav_colors ufc ON u.user_id = ufc.user_id;

内容的提问来源于stack exchange,提问作者emekadavid

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.08 01:31:01