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

MySQL无公共列横向合并表:sakila actor姓氏分类查询替代方案

问题:拆分actor表中重复与非重复的last_name列

我在MySQL的sakila数据库中练习,其中actor表包含last_name字段。需求是:将仅出现一次的last_name放在not_repeated列,出现多次的放在repeated列;如果某列行数更多,另一列对应行填充null。我已经用row_number()实现了一个方案,现在想找其他可行的SQL写法。

表结构及测试数据

-- 表定义
CREATE TABLE actor
(
    actor_id    smallint unsigned auto_increment
        primary key,
    first_name  varchar(45)                         not null,
    last_name   varchar(45)                         not null,
    last_update timestamp default CURRENT_TIMESTAMP not null on update CURRENT_TIMESTAMP
)
    charset = utf8;

CREATE INDEX idx_actor_last_name
    ON actor (last_name);

-- 插入测试数据
INSERT INTO actor (actor_id, first_name, last_name, last_update)
VALUES  (1, 'PENELOPE', 'GUINESS', '2006-02-15 04:34:33'),
        (2, 'NICK', 'BERGEN', '2006-02-15 04:34:33'),
        (3, 'ED', 'BERRY', '2006-02-15 04:34:33'),
        (4, 'JENNIFER', 'DAVIS', '2006-02-15 04:34:33'),
        (5, 'JOHNNY', 'OLIVIER', '2006-02-15 04:34:33'),
        (6, 'BETTE', 'GABLE', '2006-02-15 04:34:33'),
        (7, 'GRACE', 'MOSTEL', '2006-02-15 04:34:33'),
        (8, 'MATTHEW', 'GABLE', '2006-02-15 04:34:33'),
        (9, 'JOE', 'SWANK', '2006-02-15 04:34:33'),
        (10, 'CHRISTIAN', 'GABLE', '2006-02-15 04:34:33'),
        (11, 'ZERO', 'CAGE', '2006-02-15 04:34:33'),
        (12, 'KARL', 'BERRY', '2006-02-15 04:34:33'),
        (13, 'UMA', 'WOOD', '2006-02-15 04:34:33'),
        (14, 'VIVIEN', 'BERGEN', '2006-02-15 04:34:33'),
        (15, 'CUBA', 'OLIVIER', '2006-02-15 04:34:33');

预期输出

not_repeatedrepeated
CAGEBERGEN
DAVISBERRY
GUINESSGABLE
MOSTELOLIVIER
SWANKnull
WOODnull

现有解决方案(基于row_number())

WITH group_last_name AS
         (SELECT last_name,
                 CASE
                     WHEN count(*) > 1 THEN 'many'
                     ELSE 'once'
                     END AS frequency
          FROM actor
          GROUP BY last_name),
     onceTbl AS
         (SELECT last_name,
                 row_number() OVER (PARTITION BY frequency) AS idx
          FROM group_last_name
          WHERE frequency = 'once'),
     manyTbl AS
         (SELECT last_name,
                 row_number() OVER (PARTITION BY frequency) AS idx
          FROM group_last_name
          WHERE frequency = 'many')
SELECT onceTbl.last_name AS not_repeated,
       manyTbl.last_name AS repeated
FROM manyTbl
         LEFT OUTER JOIN onceTbl ON onceTbl.idx = manyTbl.idx
UNION
SELECT onceTbl.last_name AS not_repeated,
       manyTbl.last_name AS repeated
FROM onceTbl
         LEFT OUTER JOIN manyTbl ON onceTbl.idx = manyTbl.idx;

其他可行实现方法

方法一:使用用户变量生成行号(兼容MySQL 5.x版本)

如果你的MySQL版本不支持窗口函数(8.0以下),可以用用户变量手动生成行号:

-- 1. 统计每个last_name的出现次数,临时表存储
CREATE TEMPORARY TABLE name_counts AS
SELECT last_name, COUNT(*) AS cnt
FROM actor
GROUP BY last_name;

-- 2. 生成非重复last_name的带行号列表
CREATE TEMPORARY TABLE not_repeated_list AS
SELECT last_name, @row1 := @row1 + 1 AS idx
FROM name_counts, (SELECT @row1 := 0) AS init
WHERE cnt = 1
ORDER BY last_name;

-- 3. 生成重复last_name的带行号列表
CREATE TEMPORARY TABLE repeated_list AS
SELECT last_name, @row2 := @row2 + 1 AS idx
FROM name_counts, (SELECT @row2 := 0) AS init
WHERE cnt > 1
ORDER BY last_name;

-- 4. 连接两个列表,获取最终结果
SELECT nr.last_name AS not_repeated, r.last_name AS repeated
FROM not_repeated_list nr
LEFT JOIN repeated_list r ON nr.idx = r.idx
UNION
SELECT nr.last_name AS not_repeated, r.last_name AS repeated
FROM repeated_list r
LEFT JOIN not_repeated_list nr ON r.idx = nr.idx
ORDER BY not_repeated, repeated;

方法二:简化窗口函数写法

基于窗口函数,但简化CTE结构,减少子查询层级:

WITH once_names AS (
    SELECT last_name, ROW_NUMBER() OVER (ORDER BY last_name) AS idx
    FROM actor
    GROUP BY last_name
    HAVING COUNT(*) = 1
),
repeated_names AS (
    SELECT last_name, ROW_NUMBER() OVER (ORDER BY last_name) AS idx
    FROM actor
    GROUP BY last_name
    HAVING COUNT(*) > 1
)
-- 用LEFT JOIN + UNION模拟FULL OUTER JOIN
SELECT o.last_name AS not_repeated, r.last_name AS repeated
FROM once_names o
LEFT JOIN repeated_names r ON o.idx = r.idx
UNION
SELECT o.last_name AS not_repeated, r.last_name AS repeated
FROM repeated_names r
LEFT JOIN once_names o ON r.idx = o.idx
ORDER BY not_repeated, repeated;

方法三:利用GROUP BY和条件聚合

通过分组行号,将两类last_name聚合到同一行:

WITH all_names AS (
    SELECT 
        last_name,
        COUNT(*) OVER (PARTITION BY last_name) AS cnt,
        -- 分别给两类名字生成独立的行号
        CASE WHEN COUNT(*) OVER (PARTITION BY last_name) = 1 
             THEN ROW_NUMBER() OVER (ORDER BY last_name) END AS once_idx,
        CASE WHEN COUNT(*) OVER (PARTITION BY last_name) > 1 
             THEN ROW_NUMBER() OVER (ORDER BY last_name) END AS many_idx
    FROM actor
    GROUP BY last_name
)
SELECT 
    MAX(CASE WHEN cnt = 1 THEN last_name END) AS not_repeated,
    MAX(CASE WHEN cnt > 1 THEN last_name END) AS repeated
FROM all_names
-- 用COALESCE统一行号,确保两类名字按位置对齐
GROUP BY COALESCE(once_idx, many_idx)
ORDER BY COALESCE(once_idx, many_idx);

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.01 05:08:10