<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Bugengxin</title><description>Stay hungry, stay foolish.</description><link>https://bugengxin.github.io/</link><templateTheme>Firefly</templateTheme><templateThemeVersion>6.13.9</templateThemeVersion><templateThemeUrl>https://github.com/Bugengxin</templateThemeUrl><lastBuildDate>2026年7月16日 00:41:36</lastBuildDate><item><title>从 CSV 到 Parquet：用 DuckDB 搭建轻量分析工作流</title><link>https://bugengxin.github.io/posts/duckdb-parquet-local-analysis/</link><guid isPermaLink="true">https://bugengxin.github.io/posts/duckdb-parquet-local-analysis/</guid><description>用 DuckDB 和 Parquet 处理本地数据集，建立可复用、无需常驻数据库的分析流程。</description><pubDate>Tue, 14 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;很多本地数据分析的起点并不复杂：运营或业务同事导出一份 CSV，分析者需要尽快确认规模、口径和趋势。问题通常不在于能否读到文件，而在于文件会不断追加、字段会有空值或类型漂移，临时脚本也容易把清洗逻辑散落在各处。DuckDB 适合放在这段流程的中间：它直接读取文件，用 SQL 完成探索和校验；当规则稳定后，再把结果写成适合后续复用的 Parquet。&lt;/p&gt;
&lt;section&gt;&lt;h2&gt;接收原始 CSV&lt;a href=&quot;#接收原始-csv&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;先把原始文件按批次保存在 &lt;code&gt;data/&lt;/code&gt;，不要在原文件上手工改列或覆盖历史版本。文件名、导出日期和来源应能对应起来，方便发现某天数据突然变少或列名变化。首次读取时，可以抽样查看订单号是否重复、金额是否为非负数、时间列能否解析，以及状态值有哪些取值。把这些检查写进查询，比把结论留在聊天记录里更可靠。&lt;/p&gt;&lt;p&gt;CSV 仍然是入口格式，而不是长期分析层。它可读、可交换，却缺少稳定的类型信息，扫描大文件时也会反复付出解析成本。因此，原始层保持不变，清洗规则在 DuckDB 中显式表达，避免某次临时修改悄悄改变指标。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;用 DuckDB 查询&lt;a href=&quot;#用-duckdb-查询&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;DuckDB 不需要启动常驻服务，打开本地数据库文件或内存连接即可对 CSV 执行 SQL。&lt;code&gt;read_csv_auto&lt;/code&gt; 会根据内容推断列类型，适合先探索；对已知且易变的字段，则应在后续查询中补充转换和空值处理。下面的查询按支付成功订单汇总每日收入：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;SELECT&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;date_trunc(&lt;/span&gt;&lt;span&gt;&apos;day&apos;&lt;/span&gt;&lt;span&gt;, order_time) &lt;/span&gt;&lt;span&gt;AS&lt;/span&gt;&lt;span&gt; order_day,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;sum&lt;/span&gt;&lt;span&gt;(amount) &lt;/span&gt;&lt;span&gt;AS&lt;/span&gt;&lt;span&gt; revenue&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;FROM&lt;/span&gt;&lt;span&gt; read_csv_auto(&lt;/span&gt;&lt;span&gt;&apos;data/orders.csv&apos;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;WHERE&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;status&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&apos;paid&apos;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;GROUP BY&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;ORDER BY&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;这一步的价值不只是得到一张日报。可以把筛选条件、去重规则和金额口径逐项审视，再与业务的预期总额对照。确认口径后，将查询保存为脚本或视图定义；下次换一批 CSV 时，重复运行同一套规则就能定位数据差异，而不必重新拼接数据框操作。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;输出 Parquet&lt;a href=&quot;#输出-parquet&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;当字段命名、时间格式、状态映射和去重逻辑已经稳定，就把清洗后的结果写入 &lt;code&gt;warehouse/&lt;/code&gt;。Parquet 按列存储并保存类型信息，后续只读取少数分析字段时更省扫描，也比重新猜测 CSV 类型更可控。Python 只需调用 DuckDB，就能把查询结果固化为一个本地数据集：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; pathlib &lt;/span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; Path&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; duckdb&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;Path&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&apos;warehouse&apos;&lt;/span&gt;&lt;span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span&gt;mkdir&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;exist_ok&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;True&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;con &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; duckdb.&lt;/span&gt;&lt;span&gt;connect&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&apos;warehouse/local.duckdb&apos;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;con.&lt;/span&gt;&lt;span&gt;execute&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;&quot;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;COPY (&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;SELECT order_id, order_time, amount, status&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;FROM read_csv_auto(&apos;data/orders.csv&apos;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;WHERE status = &apos;paid&apos;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;12&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;) TO &apos;warehouse/orders.parquet&apos; (FORMAT PARQUET, COMPRESSION ZSTD);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;13&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&quot;&quot;&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;输出前应明确分区策略和覆盖方式。按日期分批的数据可以保留批次字段，避免新文件与旧文件混淆；需要重跑时，应先在临时路径验证行数、唯一键和汇总金额，再替换正式文件。Parquet 是清洗层的交付物，原始 CSV 仍应保留，以便规则修订后重新生成。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;适用边界&lt;a href=&quot;#适用边界&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;这套工作流适合个人、小团队或单机任务：文件在本地，分析以读取和批量转换为主，结果需要被 notebook、脚本或其他本地工具重复使用。它减少了为一次分析部署数据库服务的成本，也让规则和产物都落在可追踪的文件中。&lt;/p&gt;&lt;p&gt;但它不是高并发写入的替代方案，也不适合处理复杂的共享数仓权限。多个系统持续写入、多人同时修改同一数据集、细粒度授权与审计都需要专门的服务端数据平台。在这些场景中，DuckDB 和 Parquet 仍可作为导出、验证或离线分析环节，而不应承担中心事务库或共享仓库的全部职责。&lt;/p&gt;&lt;/section&gt;</content:encoded></item><item><title>让 LLM 参与数据分析前，先补齐这三道护栏</title><link>https://bugengxin.github.io/posts/llm-data-analysis-guardrails/</link><guid isPermaLink="true">https://bugengxin.github.io/posts/llm-data-analysis-guardrails/</guid><description>在把大模型接入数据问答前，先建立可信数据、权限边界和结果校验三道基础护栏。</description><pubDate>Tue, 14 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;让 LLM 回答“上周华东收入为什么下降”这类问题，真正困难的并不是把自然语言翻译成 SQL。模型可以帮助理解问题、补全查询和解释结果，但它不知道指标口径是否更新，也不应天然拥有触碰全部数据的权力。把它放进数据分析流程前，先把数据、执行和结论三个环节约束清楚，回答才有被复查的基础。&lt;/p&gt;
&lt;section&gt;&lt;h2&gt;可信的数据上下文&lt;a href=&quot;#可信的数据上下文&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;模型看到的上下文应来自受维护的语义层，而不是临时拼接的表名清单。每个可用指标至少说明定义、时间字段、所属数据源、更新频率和常见过滤条件；维度的枚举值、主键关系与脱敏规则也要能被检索。这样，“收入”才能被解析成约定的已支付金额，而不会把退款前的订单金额当作同一件事。&lt;/p&gt;&lt;p&gt;上下文还应有版本和责任人。指标定义改动后，应同步更新可供模型读取的说明，并保留变更记录。模型可以引用这些定义，却不应自行发明口径；遇到名称相近、字段含义不明或数据新鲜度不足时，应要求澄清，或把不确定性写在回答里。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;模型提议，执行权限归系统&lt;a href=&quot;#模型提议执行权限归系统&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;LLM 的职责是提出候选 SQL 和解释计划，不是获得数据库账号。服务端应解析候选语句，只允许受控数据集、已批准的表列和只读语法；禁止多语句、写入、导出、权限变更和绕过租户条件。执行器用独立的只读身份运行，并在网络、超时、扫描量和返回行数上设置上限。用户身份和数据域授权仍由系统验证，不能让提示词代替权限判断。&lt;/p&gt;&lt;p&gt;一个紧凑的流程可以写成：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;用户问题 -&amp;gt; LLM 候选 SQL -&amp;gt; 解析 / 表列白名单 -&amp;gt; 用户与数据域授权策略门&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;-&amp;gt; 只读且限行、强制执行授权的执行器&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;-&amp;gt; 指标校验 -&amp;gt; LLM 解释&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;其中，解析阶段应把 SQL 变成结构化语法树，再检查表、列、函数和条件，不能只靠关键词拦截。白名单只界定可查询的结构；执行器必须在每次请求中按用户身份和数据域授权策略强制判定，不能由 LLM 或白名单代替。执行前还可自动补上时间范围、租户过滤和 &lt;code&gt;LIMIT&lt;/code&gt;。审计日志应记录问题、经过审批的查询、执行身份、耗时和结果摘要，便于定位错误，但日志本身也要避免保存不必要的敏感明细。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;结果必须可校验&lt;a href=&quot;#结果必须可校验&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;自然语言回答不能遮住查询条件。每次输出至少以符合访问策略的脱敏形式暴露时间范围、数据来源、返回行数和实际过滤条件；若用到了聚合，也要说明指标口径。这样读者可以判断“下降”是按自然周、滚动七天还是某个业务周期计算，也能知道结果来自正式宽表还是尚未完成刷新的明细表。&lt;/p&gt;&lt;p&gt;数值必须来自已执行的查询结果，模型不能补全缺失的小数、增长率或排名。对关键指标，系统可比较历史区间、总分项是否相加一致、空值比例和异常阈值；必要时再跑一条独立的对照查询。空结果、明显异常或执行超时都不是“没有变化”的证据，回答应明确说明无法得出结论，并给出可复查的状态，而不是把失败包装成业务解释。&lt;/p&gt;&lt;p&gt;同样要区分事实与推测。查询能说明某时间段、某筛选条件下的数值变化；“促销结束导致下降”属于待验证的假设，需要结合活动、价格、渠道或实验数据进一步检验。LLM 可以整理假设和下一步检查，但不应把相关性写成因果结论。&lt;/p&gt;&lt;p&gt;更稳妥的落地方式，是先选少量口径稳定、敏感度可控且已有人工分析流程的指标，限定在只读问答和结果复核场景中试运行。等到数据目录、授权审计和校验规则经得起日常使用，再逐步扩展范围；不应一开始就把所有数据资产交给一个会生成 SQL 的对话框。&lt;/p&gt;&lt;/section&gt;</content:encoded></item></channel></rss>