ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

SQL不建临时表也能造数据:SELECT构造常量记录详解

SQL不建临时表也能造数据:SELECT构造常量记录详解 这个标题看着简单但其实特别实用。很多干了好几年开发的人遇到“查一条不在表里的记录”这种需求第一反应还是去建临时表或者写一堆冗余条件绕来绕去。但说到底就是一条 SELECT 语句用常量去造记录的问题——单条、多条各有各的写法也各有各的坑。我今天就把这个技术点彻底拆开聊顺便把那些网上很少讲清楚的细节补全。说实在的我在日常工作和带项目时已经数不清有多少次靠这个技巧救场了。报表缺个合计行、SQL 联调缺个返回结构、存储过程里要拼一个固定行集——全是它的用武之地。看完这篇你至少能少写一百行临时表代码。1. 为什么要在 SELECT 里构造常量记录先别急着看语法我想先聊聊这个需求的来源。不明白“为什么”你就记不住“怎么用”。1.1 先从一次业务救场说起印象很深的一次某个同事负责的报表模块上线前突然要加一个“地区汇总行”。当时报表是从数据库里多个维度聚合出来的结果但客户要求最底下有一行“全国总计”而且这行数据根本不是数据库里算出来的是业务方手工指定的一串常量——地区写死为“全国”金额写死为 0备注写死为“待确认”。那同事一开始打算建一张临时表把常量插进去再用 UNION 跟报表数据拼在一起。我一问他光是建表语句就写了三行还得考虑表结构变更、会话清理、权限问题。我给他改成了这样SELECT 全国 AS region, 0 AS amount, 待确认 AS remark UNION ALL SELECT region, amount, remark FROM report_data;他愣了两秒然后拍了下大腿。从那以后他写 SQL 的水平就上了一个台阶——原来数据不一定要来自表SELECT 本身就能“凭空造数据”。1.2 核心适用场景清单用多了之后我把这类需求做了个归类大概有这么几类场景你以后遇到可以直接套测试阶段手动构造单行数据验证查询结果结构对不对不需要真去 INSERT 一条再删掉。把固定参数作为常量行与业务查询结果通过 UNION 合并典型的就是上面那个“合计行”“汇总行”场景。在存储过程或函数里需要反复用到一个静态行集又不想建物理表或临时表。做数据迁移或接口对接时需要把映射关系硬编码在 SQL 里临时拼出一个映射表。调试 SQL 时想在结果集里手动加一列来源标识或者直接跑一段不依赖任何物理表的查询用来测试客户端工具连通性。说白了你只要记住一句话SELECT 是造数据的工具不只是查数据的工具。这对后续理解 UNION、子查询、VALUES 构造器这些东西都有帮助。1.3 相当于“内存里的临时表”我经常用一句话给团队新人解释这技术它相当于你不会去专门建文件夹而是用一张便利贴临时记几个数字。物理表是磁盘上的文件临时表是备课本上的一页纸而 SELECT 构造常量记录就是那张随手撕下来的便利贴——用最快的速度把数据“摆放”在查询结果里用完即弃不产生任何磁盘碎片。这个类比虽然生活化但准确点出了它的本质零持久化、零副作用、高灵活性。2. 单条常量记录语法很简单细节不简单我们先从最基础的单条常量记录说起。虽然语法短但展开讲有很多值得注意的细节。2.1 从万能写法开始在很多数据库里你可以直接用 SELECT 后面跟常量值不需要 FROM 子句。这是最直观的单条常量记录写法SELECT 1 AS id, 张三 AS name, 25 AS age;在 SQL Server 里这条能直接跑出结果一列叫 id值是 1一列叫 name值是“张三”一列叫 age值是 25。这就是一条完整的常量记录。MySQL、PostgreSQL、SQLite、达梦等很多数据库也都支持这种写法。它本质上就是个“无源查询”——数据不来自表而是直接写在语句里。但这里有两个坑等着你Oracle 不支持这种写法。Oracle 要求 SELECT 后面必须有 FROM所以你得补一个 FROM dual。对就是那个著名的 dual 虚表它专门用来支撑无表查询。达梦数据库兼容 Oracle 时也会有类似限制但达梦也提供了 DUAL 虚表和兼容 MySQL 的模式写法可能略有差异。所以跨数据库写这种语句前先确认它支不支持“无 FROM”的写法。2.2 为什么要写别名初学者最爱犯的毛病就是忘了起别名或者随便写一堆别名。实际上别名决定了结果集的列名这在后续嵌套、UNION、程序读值时特别关键。比如你写SELECT 1, 张三, 25;绝大多数数据库会给列起默认名但默认名可能不是你想要的比如 MySQL 直接不显示列名SQL Server 显示“无列名”。这种结果接到下游程序里字段名对不上程序直接报错。正确做法是显式指定别名而且要见名知意SELECT 1 AS id, 张三 AS name, 25 AS age;注意双引号和单引号有区别。很多数据库里双引号是给标识符列名、表名用的单引号才是字符串值。我见有人写 SELECT 1 AS id 在 MySQL 里默认也能跑因为 MySQL 对双引号的处理比较宽松但到了 PostgreSQL 或 Oracle 里双引号的行为就很严格了。为了少踩坑统一用不带引号的别名字符串常量用单引号这是最稳妥的。2.3 数据类型和隐式转换构造常量记录时数据库会主动判断每个常量的类型。你写 25 就是整数写 25.5 就是小数写 2025-01-01 其实是个字符串而不是日期类型。这会导致一个很隐蔽的问题当你把常量记录跟物理表数据 UNION 时类型不一致可能报错或者触发隐式转换造成性能问题。SELECT 1 AS id, 张三 AS name, 2025-01-01 AS join_date UNION ALL SELECT id, name, join_date FROM employees;如果 employees.join_date 是 DATE 类型而前面常量给的是字符串某些数据库会尝试转换某些直接报错。稳妥做法是显式转换-- SQL Server SELECT 1 AS id, 张三 AS name, CAST(2025-01-01 AS DATE) AS join_date UNION ALL SELECT id, name, join_date FROM employees;另外提醒一句UNION 在合并结果集时列名以第一个 SELECT 为准所以第一个 SELECT 的别名和类型决定了整个结果集的“长相”。这块要留心。2.4 常量里能不能写表达式真正的高手不会只写死值他们会在常量的位置写表达式——只要这个表达式的值在查询执行时能确定下来。SELECT GETDATE() AS current_time, USER_NAME() AS current_user, 100 * 2 AS doubled_value;执行这段数据库会把 GETDATE() 解析为当前时间、把 USER_NAME() 解析为当前登录名、把 100 * 2 算成 200。这些都是“常量记录”的变体——你在构造一条可能含有运行时信息的记录。这个技巧在日志审计、调试、上下文信息拼接时特别有用。你可以临时跑一条 SQL直接看到当前时间、当前用户、当前库、当前版本等环境信息不用去翻系统表。3. 多条常量记录UNION 与 VALUES 的博弈构造多条常量记录是这个标题的重点也是平时工作里含金量最高的一部分。3.1 方法一UNION ALL 拼接最传统、最通用、兼容性最好的方式就是用 UNION ALL 把多条单行 SELECT 串起来SELECT 华东 AS region, 1200 AS sales_amount UNION ALL SELECT 华南, 980 UNION ALL SELECT 华北, 760;这种写法有几点好处每条 SELECT 可以单独加别名第一条的别名作为结果集列名。每条 SELECT 的表达式自由度很高可以放常量、放函数、放子查询。绝大多数数据库天然支持。不过也有缺点行数多了以后SQL 文本会显得很长。每条记录都要写一遍 SELECT显得冗余。如果你只是拼接固定数据这种方法完全够用。但如果行数特别多比如几十行甚至上百行映射关系建议用下面的 VALUES 方式。3.2 方法二VALUES 构造器SQL 标准里提供了 VALUES 子句可以一次性构造多行记录。不同数据库用法有差异在 PostgreSQL 和 SQL Server 里可以这样写SELECT * FROM (VALUES (华东, 1200), (华南, 980), (华北, 760) ) AS t(region, sales_amount);在 MySQL 8.0 里VALUES 的写法略有不同通常配合别名列名使用SELECT * FROM (VALUES ROW(华东, 1200), ROW(华南, 980), ROW(华北, 760)) AS t(region, sales_amount);这种写法特别适合构造一张简单的“内存映射表”想一下你要把一批地区映射到销售负责人又不想建表VALUES 构造器就是天然的映射容器SELECT * FROM (VALUES (华东, 王经理), (华南, 李经理), (华北, 赵经理) ) AS t(region, manager);然后你就可以拿它去 JOIN 业务表了。3.3 关键差异UNION 与 UNION ALL这里必须重点强调一个新手特别容易踩的坑UNION 会去重UNION ALL 不会。如果你构造的记录里有重复行UNION 会默默去掉一个导致结果行数跟你预期不符。这个 bug 非常隐蔽很多时候你查半天才发现是 UNION 在捣鬼。-- 结果只有两行华东 被去重了 SELECT 华东 AS region, 1200 AS amount UNION SELECT 华南, 980 UNION SELECT 华东, 1200;如果业务上就是要保留重复行务必用 UNION ALL。绝大多数拼接场景里我们的预期是“保留所有记录”所以默认应该写 UNION ALL只有明确要去重时才写 UNION。3.4 加入排序、过滤与计算构造出来的常量记录本质上已经是结果集了所以它能做很多普通结果集能做的事。比如你可以在外面套一层查询做排序SELECT * FROM ( VALUES (华东, 1200), (华南, 980), (华北, 760) ) AS t(region, sales_amount) ORDER BY sales_amount DESC;也可以加 WHERE 条件过滤SELECT * FROM ( SELECT 华东 AS region, 1200 AS amount UNION ALL SELECT 华南, 980 UNION ALL SELECT 华北, 760 ) AS tmp WHERE amount 1000;甚至可以在构造时就计算SELECT region, sales_amount, sales_amount * 0.15 AS bonus FROM (VALUES (华东, 1200), (华南, 980)) AS t(region, sales_amount);这说明它不是一个玩具语法而是能嵌入到常规 SQL 流程里的“一等公民”。4. 实战案例从报表补全到存储过程硬编码光讲语法不够我把几个亲测有效的实战场景放出来你直接抄。4.1 案例一报表补全全天小时数做数据看板时常常有个需求按小时统计订单量但有些小时没有订单这些小时在结果里干脆不出现。如果图表上缺了几根柱子产品经理就会找上门。这时候就用到常量记录了。先生成一个 0 到 23 小时的固定序列SELECT * FROM (VALUES (0), (1), (2), (3), (4), (5), (6), (7), (8), (9), (10), (11), (12), (13), (14), (15), (16), (17), (18), (19), (20), (21), (22), (23) ) AS hours(hour_key);再左连接业务数据SELECT h.hour_key, ISNULL(SUM(o.order_amount), 0) AS order_total FROM hours h LEFT JOIN orders o ON DATEPART(HOUR, o.order_time) h.hour_key AND o.order_date 2025-01-01 GROUP BY h.hour_key ORDER BY h.hour_key;这样出来的结果就完整覆盖了 24 个小时没订单的小时也有一条 0 值记录。这在报表层面对用户特别友好。4.2 案例二存储过程里造固定映射表存储过程经常要做各种状态码的翻译。与其 JOIN 配置表不如直接用常量记录构造一个临时映射CREATE PROCEDURE proc_generate_report AS BEGIN SELECT t.status_code, t.status_name, COUNT(o.order_id) AS order_cnt FROM ( VALUES (1, 待支付), (2, 已支付), (3, 已发货), (4, 已完成) ) AS t(status_code, status_name) LEFT JOIN orders o ON o.status t.status_code GROUP BY t.status_code, t.status_name ORDER BY t.status_code; END;好处很明显不依赖配置表是否存在不会因为数据字典没初始化而出错存储过程自己就能兜底。这在快速迭代、表结构不稳的项目阶段尤其有用。4.3 案例三给查询结果打标某个查询需要区分“历史数据”和“当前数据”可以用常量加一个标记字段SELECT history AS source_type, id, name FROM archive_users UNION ALL SELECT current AS source_type, id, name FROM users;类似地如果你在处理多个渠道的数据也可以给每个渠道一个常量渠道名方便下游识别。4.4 案例四联调接口时模拟返回结构后端联调时经常需要快速确认接口返回结构是否正确。直接用数据库客户端跑一条常量记录模拟出和接口一致的结构比写单元测试快得多SELECT 0 AS code, success AS message, 虚拟用户 AS username, testexample.com AS email;4.5 案例五与 SELECT TOP / FETCH 组合的边界场景有些时候我们希望构造的记录行数和某个业务表行数保持一致或者取业务表前 N 条去关联常量记录。这时候可以结合变量或 ROW_NUMBER 用WITH numbered AS ( SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS rn FROM your_table ) SELECT rn, 序号- CAST(rn AS VARCHAR(10)) AS label FROM numbered WHERE rn 10;这在生成测试数据时非常实用等于把“常量记录生成”和“现有数据规模”绑定起来了。5. 跨数据库方言对比从 SQL Server 到达梦与 Oracle这节很重要因为你代码换一个数据库就跑不动的话再高级的语法也是白搭。我把常碰的几类数据库差异列一下。5.1 SQL Server 系SQL Server 对常量记录的支持比较灵活。单条直接 SELECT 不带 FROM 是可以的多条最推荐 VALUES 构造器 派生表SELECT * FROM (VALUES (1, a), (2, b)) AS t(id, name);SQL Server 里 VALUES 行构造器的列别名写在表别名后面这个跟 PostgreSQL 一致。还支持在 INSERT 里用 VALUES 一次插多行INSERT INTO t(id, name) VALUES (1, a), (2, b);5.2 MySQL 系MySQL 8.0 之后VALUES 语法变了必须带 ROW 关键字SELECT * FROM (VALUES ROW(1, a), ROW(2, b)) AS t(id, name);MySQL 8.0 之前只能用 UNION ALL 拼接。很多生产环境可能还在 5.7这块要注意。另外 MySQL 的派生表FROM 子句里的子查询必须有别名否则直接报错这是它的一个硬性要求。5.3 Oracle / 达梦系Oracle 比较传统不支持无 FROM 的 SELECT。单条记录要用 dual 虚表SELECT 1 AS id, 张三 AS name FROM dual;多条可以采用 UNION ALLSELECT 1 AS id, 张三 AS name FROM dual UNION ALL SELECT 2 AS id, 李四 AS name FROM dual;Oracle 还提供了一种高级写法SELECT ... FROM DUAL 结合 INSERT ALL 也能造多行但多少有点绕日常用 UNION ALL 就够。达梦数据库这块特别值得展开一下因为达梦是国产数据库里非常多见的而且它分了不同的兼容模式。在兼容 Oracle 的库上写法和 Oracle 一样需要 FROM DUAL在兼容 MySQL 的库上又可以不带 FROM。还有个容易踩的坑是达梦对 VALUES 行构造器的语法支持依赖它的版本和兼容模式不是所有版本都能直接用 SQL Server 那套。所以你在达梦上写这类 SQL 之前最好先确认它当前的兼容参数免得上线前被 DBA 教育一顿。5.4 PostgreSQL 系PostgreSQL 是最贴合 SQL 标准的VALUES 构造器原生支持SELECT * FROM (VALUES (1, a), (2, b)) AS t(id, name);列别名的写法跟 SQL Server 一致。PostgreSQL 还有一个特性REFERENCES 子句可以用 ROWS FROM 做函数组合不过这个跟常量记录关系不大就不展开了。为了让你一眼看明白给个汇总表数据库单条常量记录写法多条常量记录推荐写法注意事项SQL ServerSELECT 1 AS id, a AS nameVALUES 构造器无 FROM 可直接跑MySQL 8.0SELECT 1 AS id, a AS nameVALUES ROW(...) 构造器低版本用 UNION ALLMySQL 5.7无 FROM 直接跑UNION ALL 拼接派生表必须起别名OracleSELECT 1 FROM dualUNION ALL dual必须带 FROM dual达梦视兼容模式而定视版本用 UNION ALL / VALUES提前确认兼容参数PostgreSQLSELECT 1 AS id, a AS nameVALUES 构造器别名写在表别名后5.5 跨库兼容的“万金油”写法如果你的代码将来可能在不同数据库之间切换最保险的写法永远是 UNION ALL 串 SELECT。它有代价啰嗦但换来的兼容性是无敌的。我用过一个判断标准代码要进公共存储过程、可能被多个环境复用就用 UNION ALL只在某个特定数据库里跑、求简洁就用 VALUES 构造器。这样既照顾了兼容性又不牺牲可读性。6. 典型报错疑难杂症与排查技巧实录这块是真正的经验之谈。我先把见过的高频坑列出来再给排查手法。6.1 报错SELECT 失败因为下列 SET 选项的设置不正确SQL Server 里如果把常量记录跟表数据 UNION有时会遇到这个报错它的全称类似SELECT 失败因为下列 SET 选项的设置不正确: ANSI_NULLS, QUOTED_IDENTIFIER。这个 bug 让人抓狂的点在于语句本身看起来没毛病但一执行就报错。原因在于创建表或索引视图时会话的 SET 选项和当前会话不一致导致优化器拒绝执行。这里有个排查套路检查当前会话的 SET 选项用DBCC USEROPTIONS查看。强制在当前连接里执行一遍标准设置再跑查询SET ANSI_NULLS ON; SET QUOTED_IDENTIFIER ON; SET ANSI_PADDING ON; SET ANSI_WARNINGS ON; SET CONCAT_NULL_YIELDS_NULL ON;如果是在存储过程里确认存储过程创建时的 SET 选项并在过程开头显式 SET。这个报错特别典型的一个触发条件是你在 SSMS 某个查询窗口里改了 QUOTED_IDENTIFIER又重启了 SQL Server 服务当前连接设置被重置。所以解决它的关键是统一连接设置。6.2 报错派生表必须起别名MySQL 用户应该太熟了。你写SELECT * FROM (VALUES ROW(1, a), ROW(2, b)) t;紧跟着就报错说 Every derived table must have its own alias。解法很简单给派生表起别名。有时候列别名也要给全否则下游引用字段名会乱。6.3 报错列名无效或排序规则冲突常量记录和物理表 UNION 时如果两边字符串的排序规则不一致SQL Server 可能会报“无法解决排序规则冲突”。我当时排查这个问题查了半天才发现是常量字符串的排序规则继承了数据库默认排序规则而物理表列有自己的排序规则。解决办法是用 COLLATE 显式指定SELECT 华东 COLLATE Chinese_PRC_CI_AS AS region UNION ALL SELECT region FROM sales_data;6.4 陷阱NULL 值的类型推断构造常量记录时如果某列全是 NULL数据库可能推断不出类型。这在 UNION 时特别明显。SELECT NULL AS value UNION ALL SELECT 1;有些数据库会直接报错有些会推断为 INT。最好给 NULL 一个具体类型SELECT CAST(NULL AS VARCHAR(50)) AS value UNION ALL SELECT abc;6.5 陷阱ORDER BY 在派生表里失效把 VALUES 子句放在 FROM 里然后在外层 ORDER BY这没问题。但如果你在派生表内部就直接 ORDER BY外层再排序很多数据库里这种“内层排序”是不保证生效的因为优化器可能把它优化掉。解决办法是排序统一放在最外层不要依赖内层顺序。6.6 陷阱用常量记录做关联时索引全丢有些人会把大业务表跟一个很大的常量记录集 JOIN结果发现性能惨不忍睹。这很正常因为 VALUES 构造的内存数据集在数据库引擎眼里是个“未知大小的云”没有统计信息优化器不知道该怎么选 join 方式。一个可行的思路是如果常量记录集很小几十行以内直接 JOIN 问题不大如果行数多且反复使用建议把它插入临时表并加索引这样优化器有统计信息可用性能会稳定很多。6.7 陷阱在存储过程里使用临时表和常量记录的取舍很多存储过程一上来就用临时表。临时表的好处是能建索引、有统计信息、支持后续多次引用坏处是要写额外的建表和清理代码而且临时表在并发场景下可能有命名冲突和阻塞。常量记录则轻量很多但重复利用能力弱。个人建议只查一次、数据量小、不参与复杂计算用常量记录数据量大、要多次 join、要索引用临时表。这个判断准则我用了很久基本没出过事。7. 性能优化思路常量记录并不总是“零成本”很多人觉得常量记录是“纯内存运算”肯定很快。这话大多数时候对但不绝对。VALUES 构造的内容数据库可能会物化成内存中的行集。如果数据量特别大比如造几百条几千条不去重的记录它也有开销包括构造时的内存占用、后续排序时的内存消耗。我看到过最夸张的一个案例有人用 UNION ALL 手工拼了一万多行常量记录生成一份报告映射表结果 SQL 优化器直接把这份“常量集合”当成了一张大数据表来算整个查询跑了将近一分钟。其实本来业务数据只有几百行。这种场景的正确操作是别手写一万行用递归 CTE 或数字表生成。比如 SQL Server 的递归 CTEWITH nums AS ( SELECT 1 AS n UNION ALL SELECT n 1 FROM nums WHERE n 10000 ) SELECT n FROM nums OPTION (MAXRECURSION 0);MySQL 8.0 可以用递归 CTEWITH RECURSIVE nums AS ( SELECT 1 AS n UNION ALL SELECT n 1 FROM nums WHERE n 10000 ) SELECT n FROM nums;这样既满足“构造固定行集”的需求又不会写出一个巨型 SQL 文本。所以性能优化的核心思路是能用生成算法递归、数字表解决的就不要硬编码硬编码只适用于小规模、稳定的映射关系。8. 与 INSERT、变量、视图等配套的进阶玩法到这里常量记录就不只是 SELECT 的技巧了它还能跟很多周边语法配合产生 112 的效果。8.1 INSERT INTO ... SELECT 与常量记录你要快速往表里插入几条预设记录可以直接用INSERT INTO config_table (config_key, config_value) SELECT refresh_interval, 60 UNION ALL SELECT timeout, 30;这在初始化数据、预置配置的场景里很常见比逐条 INSERT 高效得多而且清晰。8.2 在视图里用常量记录做默认补全如果一张视图想给使用者展示“默认维度 实际数据”可以在视图定义里 UNION 常量记录CREATE VIEW v_daily_report AS SELECT report_date, region, sales_amount FROM daily_actual_data UNION ALL SELECT CAST(GETDATE() AS DATE), 未分配地区, 0;但这里要注意视图里的常量记录可能会影响查询优化器的去重和排序逻辑。如果业务上不希望出现重复行还是要谨慎处理。8.3 配合变量动态生成常量行有些场景下常量值不是写死的而是根据变量来的DECLARE region VARCHAR(20) 华东; SELECT region AS region, SUM(amount) AS total FROM sales_data WHERE region region;还可以用循环或递归生成一系列有规律的“准常量”WITH RECURSIVE date_range AS ( SELECT CAST(2025-01-01 AS DATE) AS d UNION ALL SELECT DATEADD(DAY, 1, d) FROM date_range WHERE d 2025-01-31 ) SELECT d FROM date_range;这在填充日期维度、连续月份等场景里简直是神器。8.4 与数组 / JSON 拆解的联动PostgreSQL 或 MySQL 里你可以直接用一个数组常量加 UNNEST / JSON_TABLE 展开成多行记录-- PostgreSQL SELECT unnest(ARRAY[华东, 华南, 华北]) AS region;这是“构造多条常量记录”的另一种形态用一个集合常量生成一个行集。MySQL 8.0 里可以用 JSON_TABLESELECT * FROM JSON_TABLE( [华东, 华南, 华北], $[*] COLUMNS(region VARCHAR(20) PATH $) ) AS jt;这种写法适合动态生成维度列表并且能跟参数化查询结合应用面很广。9. 实操心得这招用得好能帮你省下几张物理表从SQL 资源消耗的角度看构造常量记录而不是建物理表最大的好处是不产生磁盘 I/O不占用数据库连接的表对象锁不需要额外的权限不需要后续清理这就意味着你可以在一条查询里定义一个“逻辑配置表”而不用动数据库结构。对于快速验证、敏捷交付的项目来说这种轻量感很宝贵。但并不是所有时候都用常量记录就最优。如果你有这样的需求同一份常量集合会被多个查询反复引用常量集合行数超过几百行常量集合需要参与复杂关联且需要索引加速那我建议你老老实实建一张正式表或临时表。这就像便利贴和小本子的区别便利贴记录临时事项没问题但如果你是每天都要查的同义词表就应该整理成一份正式文档。开篇说的那个报表案例最后上线用的就是 UNION ALL 拼常量记录。直到半年后客户改需求加了几个新的地区汇总行我们还是一行常量、一行 SQL改起来特别快。最近一次带新人我给他布置了一个小任务用一条 SELECT 生成 1 到 100 的数字序列。他试了好几种写法最后看到递归 CTE 的解法时明显愣了一下。我觉得这种“原来还能这么写”的感觉正是这个知识点最有魅力的地方。如果你仔细看完这篇建议马上打开你手头最常用的数据库客户端把单条常量、多条常量、VALUES 构造器各跑一遍。不用刻意背语法跑几遍自然就记住了。以后再遇到“造几条数据”“拼个固定维度”“补个合计行”这类需求就能顺手拈来不用再临时建表了。
返回列表