包含未知列的动态透视
在类别事先未知时生成透视列。
包含未知列的动态透视 是 CoddyKit 上的免费 Coding Interview Prep 课时。 这是第 4 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Coding Interview Prep 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Coding Interview Prep 课程共包含 4 节课。
困难的透视问题
所有静态透视,无论是 CASE 聚合、SQL Server 的 PIVOT,还是 PostgreSQL 的 crosstab,都有一个共同的限制:编写查询时必须列出输出列。
但是,如果类别未知,例如每周都会变化的产品名称,或者每个活跃月份对应一列,该怎么办?这就是动态透视。它是高级面试题,因为普通 SQL 无法返回列列表在运行时才确定的结果。
为什么仅靠 SQL 无法做到
SQL 在结果集层面采用静态类型:规划器必须在执行前知道列及其类型。单个查询无法表达为您恰好找到的每个值创建一列。
因此,通用方法是分两步生成 SQL 文本:首先查询不重复的类别,然后根据这些类别构建透视查询字符串并执行该字符串。
第 1 步:收集类别
第一步是执行一个普通查询,列出将要变成列的不重复值。通常应先对这些值排序,以保持列布局稳定。
这个结果会提供给字符串构建步骤。在实际系统中,您会先运行该查询、捕获这些行,再根据它们组装下一个查询。
SELECT DISTINCT quarter
FROM sales
ORDER BY quarter;
-- e.g. Q1, Q2, Q3, Q4第 2 步:构建列列表
接下来,将这些值转换为以逗号分隔的 CASE 表达式列表(或者转换为 PIVOT 所需的方括号名称)。数据库提供了字符串聚合函数,可以直接在 SQL 中完成这一步。
在 PostgreSQL 中是 string_agg;在 MySQL 中是 GROUP_CONCAT;在 SQL Server 中是 STRING_AGG 或较早的 FOR XML PATH 技巧。
-- Postgres: build the SELECT-list fragment
SELECT string_agg(
format('SUM(CASE WHEN quarter = %L THEN amount END) AS %I',
quarter, quarter),
', '
)
FROM (SELECT DISTINCT quarter FROM sales ORDER BY 1) q;第 3 步:组装并执行
将生成的片段拼接成完整的查询字符串,然后使用动态执行来运行它:在 PL/pgSQL 中使用 EXECUTE,在 SQL Server 中使用 sp_executesql,或者在 MySQL 中使用 PREPARE/EXECUTE。
这就是动态透视的核心:SQL 编写 SQL,然后执行它。
-- SQL Server pattern
DECLARE @cols NVARCHAR(MAX), @sql NVARCHAR(MAX);
SELECT @cols = STRING_AGG(QUOTENAME(quarter), ',')
FROM (SELECT DISTINCT quarter FROM sales) q;
SET @sql = N'SELECT region, ' + @cols + '
FROM (SELECT region, quarter, amount FROM sales) s
PIVOT (SUM(amount) FOR quarter IN (' + @cols + ')) p;';
EXEC sp_executesql @sql;PostgreSQL 完整示例
在 PostgreSQL 中,您可以将这三个步骤放入 DO 块或函数中。使用 string_agg 构建列列表,将其插入查询,再使用 EXECUTE 运行查询。
由于结果列直到运行时才能确定,因此返回此类结果的函数通常会使用 RETURNS SETOF record,或者将行作为 json 返回,再由调用方展开这些行。
DO $do$
DECLARE
cols text;
qry text;
BEGIN
SELECT string_agg(
format('SUM(CASE WHEN quarter=%L THEN amount END) AS %I', quarter, quarter), ', ')
INTO cols
FROM (SELECT DISTINCT quarter FROM sales ORDER BY 1) q;
qry := format('SELECT region, %s FROM sales GROUP BY region', cols);
EXECUTE qry;
END $do$;使用预处理语句的 MySQL
MySQL 没有透视运算符,因此动态透视会使用 GROUP_CONCAT 构建条件聚合字符串,然后通过预处理语句运行它。
GROUP_CONCAT 有长度限制(group_concat_max_len)。面试官可能会提到这一点;如果类别很多,请将该限制调高。
SET @sql = NULL;
SELECT GROUP_CONCAT(DISTINCT
CONCAT('SUM(CASE WHEN quarter=''', quarter,
''' THEN amount END) AS ', QUOTE(quarter))
) INTO @sql FROM sales;
SET @sql = CONCAT('SELECT region, ', @sql,
' FROM sales GROUP BY region');
PREPARE st FROM @sql; EXECUTE st; DEALLOCATE PREPARE st;SQL 注入风险
由于您要将数据值拼接到可执行的 SQL 中,动态透视会带来注入风险。如果类别值包含引号或恶意文本,就可能破坏或劫持生成的查询。
请始终使用数据库引擎提供的安全辅助方法来转义标识符和字面量:在 PostgreSQL 中使用 format('%I', ...) 和 %L,在 SQL Server 中使用 QUOTENAME。绝不要将原始值直接粘贴到字符串中。
-- Safe quoting prevents injection / breakage
-- Postgres: %I identifier, %L literal
format('SUM(CASE WHEN k=%L THEN v END) AS %I', cat, cat)
-- SQL Server: QUOTENAME(cat)返回未知列
第二个难点是:调用方无法预先知道结果形状。面试官通常接受以下策略:
- 将行作为
JSON返回,再由应用层展开各个键。 - 让过程输出或构建查询,然后作为第二步运行该查询。
- 类别确定后,在应用代码(数据分析库、BI 工具)中完成最终透视。
没有一种简洁的方法可以通过一次静态调用返回任意列。
实例:按产品进行透视
假设产品会不断上下架,而报表需要为 sales 中当前存在的每个产品提供一个收入列。您无法将列列表硬编码,因此必须生成它。PostgreSQL 让这个过程清晰易懂:使用 string_agg 和安全引用构建 CASE 片段,将其插入查询,然后使用 EXECUTE。
请向面试官逐步说明:发现产品,将每个产品格式化为带引号的列名,完成组装并运行。同样的结构适用于任何数据库引擎;变化的只是辅助方法。
DO $do$
DECLARE cols text; qry text;
BEGIN
SELECT string_agg(
format('SUM(CASE WHEN product=%L THEN amount END) AS %I',
product, product), ', ')
INTO cols
FROM (SELECT DISTINCT product FROM sales ORDER BY 1) p;
qry := format('SELECT region, %s FROM sales GROUP BY region', cols);
EXECUTE qry;
END $do$;何时避免动态透视
优秀的候选人知道什么时候不应在 SQL 中这样做。动态 SQL 更难阅读、测试、保护和缓存。通常更好的回答是:
- 从 SQL 返回长格式数据,再在应用层或报表层进行透视。
- 如果类别集合很小且变化缓慢,请使用静态透视,并偶尔更新它。
只有在类别集合确实开放且不断变化时,才应使用动态透视。
快速检查
请测试您对动态透视存在的核心原因的理解。
回顾
动态透视用于处理未知的列集合:
- 静态透视会失败,因为结果列必须在执行前固定。
- 模式是:查询不重复的类别,构建透视 SQL 字符串,再动态执行它。
- 使用
string_agg/GROUP_CONCAT/STRING_AGG构建列列表。 - 转义值(
%I/%L、QUOTENAME)以避免 SQL 注入。 - 通常更简洁的做法是返回长格式数据,再在应用层进行透视。
常见问题解答
「包含未知列的动态透视」课时是免费的吗?
是的 — 「包含未知列的动态透视」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Coding Interview Prep 课程的其余内容,请升级到 CoddyKit PRO。 Coding Interview Prep 课程共包含 4 节课。
「包含未知列的动态透视」这节课中我会学到什么?
在类别事先未知时生成透视列。 你通过在浏览器中直接运行的动手代码来练习 Coding Interview Prep,全天候 AI 导师会在你学习这节课的过程中回答你的问题。
学习 Coding Interview Prep 需要有经验吗?
无需任何先前经验。CoddyKit 上的 Coding Interview Prep 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 4 节课,共 4 节。
「包含未知列的动态透视」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 Coding Interview Prep 课中编写并运行代码吗?
能。每节 Coding Interview Prep 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。
此课程中的所有课时
- 使用条件聚合进行透视
- 数据库厂商的 PIVOT 与交叉表语法
- 将列反透视为行
- 包含未知列的动态透视