<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>宽表 on OLAP 工作室 — 独立 DuckDB 中文技术社区与实验博客</title><link>https://duckdblab.org/zh/tags/%E5%AE%BD%E8%A1%A8/</link><description>Recent content in 宽表 on OLAP 工作室 — 独立 DuckDB 中文技术社区与实验博客</description><generator>Hugo -- gohugo.io</generator><language>zh-CN</language><lastBuildDate>Thu, 12 Nov 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://duckdblab.org/zh/tags/%E5%AE%BD%E8%A1%A8/index.xml" rel="self" type="application/rss+xml"/><item><title>DuckDB COLUMNS() 进阶：用正则捕获组批量重命名列，一行 SQL 替代 40 行 SELECT</title><link>https://duckdblab.org/zh/post/duckdb-columns-regex-capture-rename/</link><pubDate>Thu, 12 Nov 2026 00:00:00 +0000</pubDate><guid>https://duckdblab.org/zh/post/duckdb-columns-regex-capture-rename/</guid><description>&lt;h2 id="问题宽表改列名为什么这么痛"&gt;问题：宽表改列名，为什么这么痛
&lt;/h2&gt;&lt;p&gt;做数据工程的人几乎都遇过这个场景：上游 BI 系统导出的宽表列名带单位后缀——&lt;code&gt;web_spend&lt;/code&gt;、&lt;code&gt;app_spend&lt;/code&gt;、&lt;code&gt;store_spend&lt;/code&gt;；或者季度报表列名是 &lt;code&gt;q1_amount&lt;/code&gt;、&lt;code&gt;q2_amount&lt;/code&gt;、&lt;code&gt;q3_amount&lt;/code&gt;。下游消费方要求统一命名（比如全部改成 &lt;code&gt;*_usd&lt;/code&gt;），于是你写：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;web_spend&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;web_spend_usd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;app_spend&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;app_spend_usd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;store_spend&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;store_spend_usd&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;marketing&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;三列还好。但当宽表有 40 个渠道、12 个月份列时，这段 SELECT 会膨胀成几十行纯手抄代码。更糟的是：&lt;strong&gt;上游加了一个新渠道，SQL 不会报错，只会静默漏列&lt;/strong&gt;——这是最典型的&amp;quot;沉默型数据事故&amp;quot;。&lt;/p&gt;
&lt;p&gt;很多团队最后写 Python 脚本查 &lt;code&gt;information_schema.columns&lt;/code&gt; 再拼 SQL 字符串。但生成 SQL 的代码本身也要维护，还需要处理引号、大小写、保留字。有没有可能让 DuckDB 自己在编译期完成这件事？&lt;/p&gt;
&lt;p&gt;有。DuckDB 的 &lt;code&gt;COLUMNS()&lt;/code&gt; 表达式支持&lt;strong&gt;正则捕获组重命名&lt;/strong&gt;——一条 SQL 同时完成&amp;quot;按模式选列 + 按捕获组改列名&amp;quot;。&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;&lt;strong&gt;本文定位&lt;/strong&gt;：专注&lt;strong&gt;正则捕获组重命名&lt;/strong&gt;（&lt;code&gt;\1&lt;/code&gt;、&lt;code&gt;\0&lt;/code&gt;、&lt;code&gt;(?i)&lt;/code&gt; 回溯引用）。星号修饰符（EXCLUDE / REPLACE / RENAME）与 UNPIVOT 去重名请看 &lt;a class="link" href="https://duckdblab.org/zh/post/duckdb-columns-expression-star-modifiers/" &gt;上一篇基础指南&lt;/a&gt;。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;h2 id="一最小可运行示例"&gt;一、最小可运行示例
&lt;/h2&gt;&lt;p&gt;先把基础语法跑起来。下面这段可以在 DuckDB CLI 里直接粘贴执行：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;COLUMNS&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;(\w*)_spend&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;\1_spend_usd&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;web_spend&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;app_spend&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;store_spend&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;输出：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;┌───────────────┬───────────────┬─────────────────┐
│ web_spend_usd │ app_spend_usd │ store_spend_usd │
│ int32 │ int32 │ int32 │
├───────────────┼───────────────┼─────────────────┤
│ 10 │ 20 │ 30 │
└───────────────┴───────────────┴─────────────────┘
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;这条 SQL 里发生了三件事：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;'(\w*)_spend'&lt;/code&gt; 是正则&lt;/strong&gt;，不是 LIKE 通配符。&lt;code&gt;COLUMNS()&lt;/code&gt; 的参数位置如果放字符串常量，DuckDB 会把它当作&lt;strong&gt;正则表达式&lt;/strong&gt;去匹配列名（和 &lt;code&gt;* EXCLUDE&lt;/code&gt; 里的字符串匹配规则不同）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;(\w*)&lt;/code&gt; 是捕获组&lt;/strong&gt;，捕获了 &lt;code&gt;web&lt;/code&gt; / &lt;code&gt;app&lt;/code&gt; / &lt;code&gt;store&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;AS '\1_spend_usd'&lt;/code&gt; 里的 &lt;code&gt;\1&lt;/code&gt; 被替换成第 1 个捕获组的内容&lt;/strong&gt;——最终列名就是 &lt;code&gt;web_spend_usd&lt;/code&gt; 等。&lt;/li&gt;
&lt;/ol&gt;

 &lt;blockquote&gt;
 &lt;p&gt;注意：官方文档里写的是 &lt;code&gt;SELECT COLUMNS('(\w{3}).*') AS '\1'&lt;/code&gt;，&lt;code&gt;\0&lt;/code&gt; 代表整个匹配，&lt;code&gt;\1&lt;/code&gt;、&lt;code&gt;\2&lt;/code&gt; 依次对应第 1、2 个捕获组，和 sed / Python &lt;code&gt;re.sub&lt;/code&gt; 的回溯引用语义一致。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;为什么不用 &lt;code&gt;* REPLACE&lt;/code&gt;？&lt;/strong&gt; 因为 &lt;code&gt;REPLACE&lt;/code&gt; 要求你写 &lt;code&gt;表达式 AS 新列名&lt;/code&gt;，列名必须是常量。想批量改名只能一列一列写——&lt;code&gt;COLUMNS()&lt;/code&gt; + 捕获组是唯一能&amp;quot;按规则批量改名&amp;quot;的写法。&lt;/p&gt;
&lt;h2 id="二实战场景-1季度宽表批量重命名"&gt;二、实战场景 1：季度宽表批量重命名
&lt;/h2&gt;&lt;p&gt;真实报表常见的形状是这样：一行一个区域/渠道，后面跟三个季度列。我们要把 &lt;code&gt;q1_amount&lt;/code&gt; → &lt;code&gt;amount_q1&lt;/code&gt;（前后缀对调）。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;OR&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;REPLACE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;TABLE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sales&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;VALUES&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;north&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;web&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;150&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;120&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;00&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;north&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;app&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;140&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;130&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;110&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;90&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;00&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;south&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;web&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;180&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;240&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;120&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;00&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;region&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;q1_amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;q2_amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;q3_amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cost&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;region&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;COLUMNS&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;q([0-9])_amount&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;amount_q\1&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sales&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;输出：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;┌─────────┬─────────┬──────────────┬──────────────┬──────────────┐
│ region │ channel │ amount_q1 │ amount_q2 │ amount_q3 │
│ varchar │ varchar │ decimal(5,2) │ decimal(5,2) │ decimal(5,2) │
├─────────┼─────────┼──────────────┼──────────────┼──────────────┤
│ north │ web │ 100.00 │ 150.00 │ 120.00 │
│ north │ app │ 140.00 │ 130.00 │ 110.00 │
│ south │ web │ 200.00 │ 180.00 │ 240.00 │
└─────────┴─────────┴──────────────┴──────────────┴──────────────┘
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;cost&lt;/code&gt; 列被自动剔除，因为正则只匹配 &lt;code&gt;q[0-9]_amount&lt;/code&gt;。这就是 &lt;code&gt;COLUMNS()&lt;/code&gt; 相比 &lt;code&gt;SELECT *&lt;/code&gt; 的第二个价值：&lt;strong&gt;选列和改名是同一次操作&lt;/strong&gt;，不需要先 SELECT 再 EXCLUDE。&lt;/p&gt;
&lt;p&gt;用 &lt;code&gt;DESCRIBE&lt;/code&gt; 可以确认列名确实改了（而不是只在输出头显示）：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;DESCRIBE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;COLUMNS&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;q([0-9])_amount&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;amount_q\1&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sales&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;pre tabindex="0"&gt;&lt;code&gt;┌───────────────────────┐
│ sales │
│ │
│ amount_q1 decimal │
│ amount_q2 decimal │
│ amount_q3 decimal │
└───────────────────────┘
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="三实战场景-2多币种报表统一加后缀"&gt;三、实战场景 2：多币种报表统一加后缀
&lt;/h2&gt;&lt;p&gt;换一个更贴近财务的场景：原始列名是 &lt;code&gt;usd_sales&lt;/code&gt;、&lt;code&gt;eur_sales&lt;/code&gt;、&lt;code&gt;gbp_sales&lt;/code&gt;，下游要求改成 &lt;code&gt;usd_sales_usd&lt;/code&gt; 这种&amp;quot;币种_指标_单位&amp;quot;三段式。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;COLUMNS&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;^(\w{3})_sales$&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;\1_sales_usd&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;usd_sales&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;eur_sales&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;gbp_sales&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这里两个正则细节值得单独说：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;^&lt;/code&gt; 和 &lt;code&gt;$&lt;/code&gt; 锚定&lt;/strong&gt;：不加锚点，&lt;code&gt;\w{3}_sales&lt;/code&gt; 也能匹配 &lt;code&gt;total_usd_sales&lt;/code&gt; 里的 &lt;code&gt;usd_sales&lt;/code&gt; 子串——但捕获组会捕获错位。宽表列名一多，锚点是必须的保险。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;\w&lt;/code&gt; 包含下划线和数字&lt;/strong&gt;，所以 &lt;code&gt;\w{3}&lt;/code&gt; 能精确匹配 &lt;code&gt;usd&lt;/code&gt; 但也会匹配 &lt;code&gt;a_1&lt;/code&gt;。如果想严格限定字母，用 &lt;code&gt;[a-z]{3}&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;一个常被忽略的点：&lt;strong&gt;捕获组编号从 &lt;code&gt;\1&lt;/code&gt; 开始&lt;/strong&gt;，&lt;code&gt;\0&lt;/code&gt; 是整个匹配。想给所有列加统一前缀，最省事的写法是：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;COLUMNS&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;.*_amount&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;amt_\0&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sales&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;输出列名变成 &lt;code&gt;amt_q1_amount&lt;/code&gt;、&lt;code&gt;amt_q2_amount&lt;/code&gt;、&lt;code&gt;amt_q3_amount&lt;/code&gt;——&lt;code&gt;\0&lt;/code&gt; 保留了原列名的完整匹配内容。&lt;/p&gt;
&lt;h2 id="四实战场景-3大小写混写列名的选列与规范化"&gt;四、实战场景 3：大小写混写列名的选列与规范化
&lt;/h2&gt;&lt;p&gt;上游系统导出的列名经常是 &lt;code&gt;Amount&lt;/code&gt;、&lt;code&gt;AmountUSD&lt;/code&gt; 这种驼峰。&lt;code&gt;COLUMNS()&lt;/code&gt; 的正则默认&lt;strong&gt;区分大小写&lt;/strong&gt;，需要不区分时加内联标志 &lt;code&gt;(?i)&lt;/code&gt;：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;-- 区分大小写：a.* 只匹配小写开头的 apple
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;COLUMNS&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;a.*&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;apple&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;-- 结果一列：apple
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;-- 不区分大小写：(?i)a.* 两列都匹配
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;COLUMNS&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;(?i)a.*&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;apple&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;-- 结果两列：Amount、apple
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;(?i)&lt;/code&gt; 是内联标志语法，DuckDB 的正则引擎支持，实测确认 &lt;code&gt;(?i)&lt;/code&gt; 下 &lt;code&gt;a.*&lt;/code&gt; 能同时匹配大写 &lt;code&gt;Amount&lt;/code&gt; 与小写 &lt;code&gt;apple&lt;/code&gt;。&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;&lt;strong&gt;踩坑实测&lt;/strong&gt;：锚点 &lt;code&gt;\A&lt;/code&gt;（PCRE 里表示&amp;quot;匹配串首&amp;quot;）在该引擎里对列名匹配未按 PCRE 语义生效——&lt;code&gt;COLUMNS('\A.*')&lt;/code&gt; 与 &lt;code&gt;COLUMNS('(?i)a.*')&lt;/code&gt; 实测返回完全相同的两列。别用 &lt;code&gt;\A&lt;/code&gt; 想&amp;quot;只锚大写 A 开头&amp;quot;，用 &lt;code&gt;(?i)&lt;/code&gt; + 显式大小写是可靠做法。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;lambda 选列 + &lt;code&gt;\0&lt;/code&gt; 重命名可跑通&lt;/strong&gt;（容易被忽略的组合）：lambda 侧做&lt;strong&gt;选列&lt;/strong&gt;，AS 侧做&lt;strong&gt;改名&lt;/strong&gt;，分工清晰：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;-- lambda 负责选列（不区分大小写选 a 开头），AS 用 \0 加前后缀
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;COLUMNS&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;ILIKE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;a%&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;x\0y&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;apple&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;实测输出列名：&lt;code&gt;xAmounty&lt;/code&gt;、&lt;code&gt;xappley&lt;/code&gt;——lambda 选列、&lt;code&gt;\0&lt;/code&gt; 改名在同一行 SQL 里完成。&lt;/p&gt;
&lt;p&gt;但注意：&lt;strong&gt;lambda 选列时，AS 侧不能用 &lt;code&gt;\1&lt;/code&gt;（没有捕获组可引用）&lt;/strong&gt;：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;-- 报错：Binder Error: Only the backslash escape code \0 can be used when no regex is supplied to COLUMNS(*)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;COLUMNS&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;ILIKE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;a%&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;\1&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;apple&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;lambda 内部不能再写 &lt;code&gt;AS&lt;/code&gt; 做二次改名&lt;/strong&gt;——下面这种写法是语法错误，别被某些教程误导：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;-- Parser Error: syntax error at or near &amp;#34;AS&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;COLUMNS&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;ILIKE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;a%&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;...;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;想把 &lt;code&gt;Amount&lt;/code&gt; 变成 &lt;code&gt;amount&lt;/code&gt;（真正改变大小写）&lt;/strong&gt;，捕获组和 &lt;code&gt;\0&lt;/code&gt; 拼接都做不到——它们只能&amp;quot;保留原样再前后加料&amp;quot;。可行路径是：先用 lambda 选列 → 用 &lt;code&gt;\0&lt;/code&gt; 拿到原名 → 在&lt;strong&gt;外层&lt;/strong&gt;包一层显式别名，或者干脆 &lt;code&gt;ALTER TABLE ... RENAME COLUMN&lt;/code&gt; 逐列处理。&lt;/p&gt;
&lt;p&gt;结论：&lt;strong&gt;结构化批量改名用捕获组 &lt;code&gt;'\1_suffix'&lt;/code&gt;；不区分大小写选列用 &lt;code&gt;(?i)&lt;/code&gt; 或 lambda &lt;code&gt;ILIKE&lt;/code&gt;；大小写规范化只能在 SQL 外层或 DDL 里做，&lt;code&gt;COLUMNS()&lt;/code&gt; 内没有变换能力&lt;/strong&gt;。三者边界分清，才不会写出&amp;quot;以为改了大小写实测没改&amp;quot;的 bug。&lt;/p&gt;
&lt;h2 id="五实战场景-4批量聚合--自动带列名的结果"&gt;五、实战场景 4：批量聚合 + 自动带列名的结果
&lt;/h2&gt;&lt;p&gt;&lt;code&gt;COLUMNS()&lt;/code&gt; 最容易被忽略的能力：它不只能在 &lt;code&gt;SELECT&lt;/code&gt; 列表里做重命名，还能&lt;strong&gt;作为聚合函数的参数&lt;/strong&gt;，并且捕获组命名同样生效。&lt;/p&gt;
&lt;p&gt;下面统计每个区域三个季度的合计，并让结果列名自带语义：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;COLUMNS&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;q([0-9])_amount&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;lo_q\1&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;COLUMNS&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;q([0-9])_amount&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;hi_q\1&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sales&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;输出（&lt;strong&gt;手动写的 min/max 各展开 3 个季度列，共 6 列，列名不重复&lt;/strong&gt;）：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;┌──────────────┬──────────────┬──────────────┬──────────────┬──────────────┬──────────────┐
│ lo_q1 │ lo_q2 │ lo_q3 │ hi_q1 │ hi_q2 │ hi_q3 │
│ decimal(5,2) │ decimal(5,2) │ decimal(5,2) │ decimal(5,2) │ decimal(5,2) │ decimal(5,2) │
├──────────────┼──────────────┼──────────────┼──────────────┼──────────────┼──────────────┤
│ 100.00 │ 130.00 │ 110.00 │ 200.00 │ 180.00 │ 240.00 │
└──────────────┴──────────────┴──────────────┴──────────────┴──────────────┴──────────────┘
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;这是本文最实用的一个组合。没有捕获组时，&lt;code&gt;AS lo&lt;/code&gt; 会展开成 3 个同名 &lt;code&gt;lo&lt;/code&gt; 列——程序按位置读还行，肉眼完全没法对应。加了 &lt;code&gt;\1&lt;/code&gt; 后，列名自带季度信息，导成 CSV 交给下游也不会错位。&lt;/p&gt;
&lt;h2 id="六边界什么情况下捕获组方案不成立"&gt;六、边界：什么情况下捕获组方案不成立
&lt;/h2&gt;&lt;p&gt;实测下来有四条硬边界，写之前必须知道：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. 同一个表达式里不能出现两个不同的 &lt;code&gt;COLUMNS()&lt;/code&gt;。&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;-- 报错：Binder Error: Multiple different STAR/COLUMNS in the same expression are not supported
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;COLUMNS&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;.*_sales&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;COLUMNS&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;.*_cost&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;metrics&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;想算&amp;quot;收入减成本&amp;quot;的宽表列对列差，只能先 UNPIVOT 成长表，或者显式列出每一对。这是 &lt;code&gt;COLUMNS()&lt;/code&gt; 目前最关键的设计限制。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. 正则必须匹配到至少一列，否则直接报错。&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;-- Binder Error: Star expression &amp;#34;COLUMNS(...)&amp;#34; resulted in an empty set of columns
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;COLUMNS&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;typo_prefix_.*&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sales&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这和 &lt;code&gt;SELECT *&lt;/code&gt; 的宽容行为不同。好处是——&lt;strong&gt;上游改名会立刻炸，而不是静默漏列&lt;/strong&gt;，这正是第一节那个&amp;quot;沉默型数据事故&amp;quot;的解药。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3. 别名冲突。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;SELECT COLUMNS('.*_amount') AS 'x' FROM sales&lt;/code&gt; 会生成 3 个都叫 &lt;code&gt;x&lt;/code&gt; 的列。DuckDB 允许这样，但下游 &lt;code&gt;df['x']&lt;/code&gt; 只会拿到第一列。要么用 &lt;code&gt;\0&lt;/code&gt; / &lt;code&gt;\1&lt;/code&gt; 保证唯一，要么后续 UNPIVOT。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;4. &lt;code&gt;COLUMNS()&lt;/code&gt; 不能用在 &lt;code&gt;* REPLACE&lt;/code&gt; / &lt;code&gt;* RENAME&lt;/code&gt; 内部。&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;-- 报错：STAR expression is not supported here
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;REPLACE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;COLUMNS&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;.*_amount&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;q1_amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sales&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;REPLACE&lt;/code&gt; 的右边必须是具体表达式，不能是另一个星号展开。&lt;/p&gt;
&lt;h2 id="七零开销验证捕获组不影响执行计划"&gt;七、零开销验证：捕获组不影响执行计划
&lt;/h2&gt;&lt;p&gt;改列名会不会引入额外的 Projection 或性能损失？用 200 万行表对比 &lt;code&gt;SUM(COLUMNS('m[0-9]'))&lt;/code&gt; 与手写 &lt;code&gt;SUM(m1) + SUM(m2)&lt;/code&gt;，两者都在 10 ms 量级（本机实测 9 ms / 7 ms，差异在噪声范围内）。&lt;code&gt;EXPLAIN&lt;/code&gt; 输出&lt;strong&gt;物理计划逐字节相同&lt;/strong&gt;（&lt;code&gt;md5sum&lt;/code&gt; 比对过）——捕获组在 binder 阶段就完成列名解析，进优化器时已是普通列引用。&lt;/p&gt;
&lt;p&gt;也就是说：&lt;strong&gt;用 &lt;code&gt;COLUMNS()&lt;/code&gt; + 捕获组改列名，成本是一次性的 SQL 解析，不是每行的运行时开销。&lt;/strong&gt;（上一篇 &lt;a class="link" href="https://duckdblab.org/zh/post/duckdb-columns-expression-star-modifiers/" &gt;基础指南&lt;/a&gt; 第 5 节用 20 万行 × 50 列做了同样的零开销验证，呼应此处。）&lt;/p&gt;
&lt;h2 id="八什么时候该用--不该用"&gt;八、什么时候该用 / 不该用
&lt;/h2&gt;&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;场景&lt;/th&gt;
 &lt;th&gt;推荐写法&lt;/th&gt;
 &lt;th&gt;原因&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;宽表按模式批量改名（同构列）&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;COLUMNS('regex') AS '\1_suffix'&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;一行替代几十行手抄&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;宽表批量聚合且要可读列名&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;min(COLUMNS('regex')) AS 'lo_\1'&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;避免重名列&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;需要算&amp;quot;两两列之间&amp;quot;的运算&lt;/td&gt;
 &lt;td&gt;先 UNPIVOT 再 JOIN&lt;/td&gt;
 &lt;td&gt;多 &lt;code&gt;COLUMNS()&lt;/code&gt; 不被支持&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;列数固定（&amp;lt; 5 列）&lt;/td&gt;
 &lt;td&gt;直接手写&lt;/td&gt;
 &lt;td&gt;正则可读性反而更差&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;列名需要改变大小写&lt;/td&gt;
 &lt;td&gt;lambda 选列 + 下游处理&lt;/td&gt;
 &lt;td&gt;捕获组只做拼接，不做变换&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;需要&amp;quot;列缺失也静默通过&amp;quot;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;SELECT *&lt;/code&gt; 或 lambda&lt;/td&gt;
 &lt;td&gt;COLUMNS 匹配失败会报错&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id="继续深入"&gt;继续深入
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://duckdblab.org/zh/post/duckdb-columns-expression-star-modifiers/" &gt;DuckDB COLUMNS() 与星号修饰符完全指南&lt;/a&gt; — EXCLUDE / REPLACE / RENAME / UNPIVOT 的基础盘&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://duckdblab.org/zh/post/duckdb-unpivot-grouping-sets/" &gt;DuckDB UNPIVOT 与 GROUPING SETS&lt;/a&gt; — 宽表转长表的正解&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://duckdblab.org/zh/post/duckdb-complete-guide/" &gt;DuckDB 完整使用指南&lt;/a&gt; — 从安装到进阶的路线图&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://duckdblab.org/zh/post/duckdb-list-functions-complete-guide/" &gt;DuckDB 列表函数完全指南&lt;/a&gt; — 配合捕获组处理嵌套结构&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="可复现验证"&gt;可复现验证
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;版本&lt;/strong&gt;：DuckDB 1.5.2 (Variegata)，Linux x86_64，CLI 直接执行。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;注意&lt;/strong&gt;：&lt;code&gt;\1&lt;/code&gt; 回溯引用需要真实文件里的单反斜杠。通过 &lt;code&gt;duckdb -c &amp;quot;...\1...&amp;quot;&lt;/code&gt; 传参时 shell 会吞掉反斜杠，请把 SQL 写进 &lt;code&gt;.sql&lt;/code&gt; 文件后 &lt;code&gt;duckdb &amp;lt; file.sql&lt;/code&gt; 执行——本文所有示例均以此方式验证。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;官方参考&lt;/strong&gt;：&lt;a class="link" href="https://duckdb.org/docs/2.0/sql/expressions/star.html" target="_blank" rel="noopener"
 &gt;Star Expression（含 COLUMNS 正则与捕获组）&lt;/a&gt;、&lt;a class="link" href="https://duckdb.org/docs/stable/sql/functions/lambda" target="_blank" rel="noopener"
 &gt;Lambda Functions&lt;/a&gt;、&lt;a class="link" href="https://duckdb.org/docs/stable/sql/query_syntax/groupby" target="_blank" rel="noopener"
 &gt;GROUP BY ALL&lt;/a&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;本文信息&lt;/strong&gt;：最后验证 2026-11-12。如发现问题，欢迎通过 &lt;a class="link" href="https://github.com/pengzz9527/duckdb-blog/issues" target="_blank" rel="noopener"
 &gt;GitHub Issue&lt;/a&gt; 反馈。&lt;/li&gt;
&lt;/ul&gt;</description></item></channel></rss>