<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>进阶 on 老张开工了</title>
    <link>/%E8%BF%9B%E9%98%B6/</link>
    <description>Recent content in 进阶 on 老张开工了</description>
    <generator>Hugo</generator>
    <language>zh-cn</language>
    <lastBuildDate>Thu, 14 May 2026 00:00:00 +0000</lastBuildDate>
    <atom:link href="/%E8%BF%9B%E9%98%B6/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>异常处理与调试</title>
      <link>/posts/2026/05/exceptions-debugging/</link>
      <pubDate>Thu, 14 May 2026 00:00:00 +0000</pubDate>
      <guid>/posts/2026/05/exceptions-debugging/</guid>
      <description>程序总会有出错的时候——用户输入了无效数据、网络连接中断、磁盘空间不足……优秀的程序员不是写出永不报错的代码（这是不可能的），而是能优雅地处理错误，让程序在异常情况下也能行为可预测、可排查。&#xA;本章将系统学习 Python 的异常处理机制——从 try/except 到自定义异常，再到使用 pdb 和 logging 进行调试，让你的代码从&amp;quot;动不动就崩溃&amp;quot;变成&amp;quot;稳如磐石&amp;quot;。&#xA;Python 的异常哲学 Python 推崇 EAFP 原则——&amp;ldquo;Easier to Ask for Forgiveness than Permission&amp;rdquo;（请求原谅比获得许可更容易）。也就是：先尝试执行，如果出错了再处理，而不是在动手之前做各种检查。&#xA;# LBYL 风格（Look Before You Leap）——非 Python 方式 def safe_divide_lbyl(a, b): if b == 0: return None if not isinstance(a, (int, float)): return None return a / b # EAFP 风格——Python 方式 def safe_divide_eafp(a, b): try: return a / b except (ZeroDivisionError, TypeError): return None try/except/else/finally Python 的异常处理结构由四个子句组成：</description>
    </item>
    <item>
      <title>文件读写与 IO 操作</title>
      <link>/posts/2026/05/file-io/</link>
      <pubDate>Wed, 13 May 2026 00:00:00 +0000</pubDate>
      <guid>/posts/2026/05/file-io/</guid>
      <description>文件操作是编程中最常见的任务之一——读取配置文件、写入日志、处理数据文件、导入导出……几乎每个程序都需要和文件打交道。&#xA;Python 的文件操作接口设计得既简洁又强大。本章将从基础的 open() 函数开始，逐步深入到上下文管理器、CSV 处理、JSON 读写、现代路径操作以及临时文件的处理。&#xA;open() 函数 open() 是 Python 文件操作的入口，返回一个文件对象（file object）：&#xA;# 打开文件读取 file = open(&amp;#34;example.txt&amp;#34;, &amp;#34;r&amp;#34;, encoding=&amp;#34;utf-8&amp;#34;) content = file.read() file.close() # 务必关闭！ print(content) 文件模式 open() 的第二个参数是模式（mode），决定了你要对文件做什么操作：&#xA;模式 含义 文件指针位置 &amp;quot;r&amp;quot; 读取（默认） 开头 &amp;quot;w&amp;quot; 写入（覆盖已有内容） 开头（清空文件） &amp;quot;a&amp;quot; 追加 末尾 &amp;quot;x&amp;quot; 排他创建（文件已存在则报错） 开头 &amp;quot;r+&amp;quot; 读写 开头 &amp;quot;w+&amp;quot; 读写（覆盖） 开头（清空文件） &amp;quot;a+&amp;quot; 读写（追加） 末尾 加上类型修饰符：&#xA;修饰符 含义 &amp;quot;b&amp;quot; 二进制模式（如 &amp;quot;rb&amp;quot;、&amp;quot;wb&amp;quot;） &amp;quot;t&amp;quot; 文本模式（默认，如 &amp;quot;rt&amp;quot; 等价于 &amp;quot;r&amp;quot;） # 各种模式的示例 # 读取文本文件 with open(&amp;#34;data.</description>
    </item>
    <item>
      <title>模块与包管理</title>
      <link>/posts/2026/05/modules-packages/</link>
      <pubDate>Tue, 12 May 2026 00:00:00 +0000</pubDate>
      <guid>/posts/2026/05/modules-packages/</guid>
      <description>到目前为止，我们所有的代码都写在了一个文件中。当程序变得庞大时，把所有代码塞在一个文件里会让维护变得痛苦不堪。&#xA;Python 通过**模块（Module）和包（Package）**来解决这个问题。模块就是 .py 文件，包就是包含模块的目录。借助这套机制，你可以把代码拆分成逻辑单元，复用第三方库，并组织自己的项目结构。&#xA;模块的概念 一个 .py 文件就是一个模块。模块名就是文件名（去掉 .py）。&#xA;# 文件：greeting.py &amp;#34;&amp;#34;&amp;#34;问候模块&amp;#34;&amp;#34;&amp;#34; def say_hello(name): return f&amp;#34;Hello, {name}!&amp;#34; def say_goodbye(name): return f&amp;#34;Goodbye, {name}!&amp;#34; PI = 3.14159 # 模块中也可以定义变量 print(f&amp;#34;模块 {__name__} 已加载&amp;#34;) # 每个模块都有 __name__ 在其他文件中导入它：&#xA;# 文件：main.py import greeting # 导入 greeting 模块 print(greeting.say_hello(&amp;#34;Alice&amp;#34;)) # Hello, Alice! print(greeting.PI) # 3.14159 import 的各种方式 基本导入 # 方式 1：导入整个模块 import math print(math.sqrt(16)) # 4.0 # 方式 2：导入特定对象 from math import sqrt, pi print(sqrt(25)) # 5.</description>
    </item>
    <item>
      <title>高阶函数与 Lambda 表达式</title>
      <link>/posts/2026/05/higher-order-functions/</link>
      <pubDate>Mon, 11 May 2026 00:00:00 +0000</pubDate>
      <guid>/posts/2026/05/higher-order-functions/</guid>
      <description>在上一章我们学到，Python 的函数是一等公民——可以作为参数传递、作为返回值返回。在这个基础上，**高阶函数（Higher-Order Function）**就是把函数当成普通值来操作的函数：要么接收函数作为参数，要么返回一个函数，或者两者兼备。&#xA;本章我们将学习 Python 中常见的高阶函数模式，包括 map、filter、reduce、sorted 的 key 函数、Lambda 表达式、偏函数，以及函数组合的技巧。&#xA;函数作为参数 这是最直观的高阶函数形式——一个函数接收另一个函数作为参数：&#xA;def apply_twice(func, value): &amp;#34;&amp;#34;&amp;#34;对 value 应用两次 func&amp;#34;&amp;#34;&amp;#34; return func(func(value)) def add_one(x): return x + 1 print(apply_twice(add_one, 5)) # 7（5→6→7） def square(x): return x ** 2 print(apply_twice(square, 2)) # 16（2→4→16） 这种模式在 Python 标准库中无处不在。比如 sorted() 的 key 参数：&#xA;words = [&amp;#34;python&amp;#34;, &amp;#34;Java&amp;#34;, &amp;#34;C&amp;#34;, &amp;#34;javascript&amp;#34;, &amp;#34;Go&amp;#34;] # 按照字符串长度排序 sorted_by_len = sorted(words, key=len) print(sorted_by_len) # [&amp;#39;C&amp;#39;, &amp;#39;Go&amp;#39;, &amp;#39;Java&amp;#39;, &amp;#39;python&amp;#39;, &amp;#39;javascript&amp;#39;] # 自定义排序逻辑 sorted_by_last_char = sorted(words, key=lambda s: s[-1]) print(sorted_by_last_char) # [&amp;#39;Java&amp;#39;, &amp;#39;C&amp;#39;, &amp;#39;python&amp;#39;, &amp;#39;Go&amp;#39;, &amp;#39;javascript&amp;#39;] Lambda 表达式 Lambda 表达式是创建匿名函数的快捷方式。当需要一个简单的、只使用一次的函数时，可以用 lambda 替代完整的 def 定义：</description>
    </item>
    <item>
      <title>作用域、闭包与命名空间</title>
      <link>/posts/2026/05/scope-closure/</link>
      <pubDate>Sun, 10 May 2026 00:00:00 +0000</pubDate>
      <guid>/posts/2026/05/scope-closure/</guid>
      <description>上一章我们学会了写函数。但你有没有想过：函数里面定义的变量为什么外面访问不到？为什么嵌套函数能&amp;quot;记住&amp;quot;外层函数的变量？这些问题的答案都藏在 Python 的作用域（Scope）和闭包（Closure）机制里。&#xA;理解作用域和闭包，是写出高质量 Python 代码的关键一步——它们决定了变量在哪里可见、函数的生命周期如何运作、以及如何优雅地实现状态保持。&#xA;命名空间（Namespace） 命名空间是&amp;quot;名字到对象&amp;quot;的映射。当你写 x = 1，Python 就把名字 x 和对象 1 关联起来，这个关联关系就存在于当前命名空间中。&#xA;Python 的命名空间分为几种：&#xA;# 内置命名空间 —— print, len, int 等内置函数 # 全局命名空间 —— 模块级别的名字 # 局部命名空间 —— 函数内的名字 不同命名空间是独立隔离的——同一个名字在不同空间中可以指向不同的对象。&#xA;LEGB 规则 Python 在查找变量时，按照 LEGB 顺序搜索四个作用域：&#xA;Local —— 当前函数内部的局部作用域 Enclosing —— 外层函数的局部作用域（嵌套函数中） Global —— 模块的全局作用域 Built-in —— Python 内置作用域（print、len 等） # Built-in 示例 —— 可以直接用内置函数 print(len(&amp;#34;hello&amp;#34;)) # len 在 Built-in 作用域 # Global 示例 —— 模块级别的变量 x = &amp;#34;全局变量&amp;#34; def outer(): # Enclosing —— 外围函数作用域 y = &amp;#34;外层变量&amp;#34; def inner(): # Local —— 局部作用域 z = &amp;#34;内层变量&amp;#34; # LEGB 查找顺序：先找 z（Local），再找 y（Enclosing），再找 x（Global），最后找内置 print(z) # Local print(y) # Enclosing print(x) # Global inner() outer() 图解 LEGB # 全局作用域（Global） g = 100 def outer_func(): # 外围作用域（Enclosing） e = 200 def inner_func(): # 局部作用域（Local） l = 300 # 查找顺序： print(l) # 1.</description>
    </item>
    <item>
      <title>函数定义与参数传递</title>
      <link>/posts/2026/05/functions/</link>
      <pubDate>Sat, 09 May 2026 00:00:00 +0000</pubDate>
      <guid>/posts/2026/05/functions/</guid>
      <description>函数是组织代码的基本单元。它让你把一段逻辑封装起来，给它一个名字，然后在需要的地方反复调用。Python 的函数设计灵活而强大——从最简单的位置参数到复杂的 *args、**kwargs，从类型注解到嵌套函数，掌握函数就等于掌握了代码复用的钥匙。&#xA;def 语句与函数定义 Python 使用 def 关键字定义函数，基本语法如下：&#xA;def greet(name): &amp;#34;&amp;#34;&amp;#34;向指定的人打招呼&amp;#34;&amp;#34;&amp;#34; return f&amp;#34;Hello, {name}!&amp;#34; print(greet(&amp;#34;Alice&amp;#34;)) # Hello, Alice! 函数的组成部分：&#xA;部分 说明 def 关键字 声明这是一个函数 函数名 greet 遵循变量命名规则，建议用蛇形命名法 参数列表 (name) 输入，可以为空 () 冒号 : 标志函数头结束 函数体（缩进块） 实际执行的代码 return 语句 返回值，没有 return 则返回 None 函数定义只是创建了一个可调用对象，函数体在调用之前不会执行：&#xA;def lazy(): print(&amp;#34;这行代码被推迟执行了！&amp;#34;) print(&amp;#34;函数已定义，但尚未执行&amp;#34;) # 先执行 lazy() # 现在才执行函数体 参数 vs 参数 初学者经常混淆这两个概念：&#xA;参数（Parameter）：函数定义时写在括号里的变量名，是&amp;quot;形参&amp;quot; 参数（Argument）：调用函数时传入的具体值，是&amp;quot;实参&amp;quot; def add(x, y): # x 和 y 是参数（Parameter） return x + y result = add(3, 5) # 3 和 5 是参数（Argument） 用中文记忆：&amp;ldquo;参数&amp;quot;是定义时的占位符，&amp;ldquo;参数&amp;quot;是调用时塞进去的值。</description>
    </item>
    <item>
      <title>视图与物化视图</title>
      <link>/posts/2025/02/views-and-materialized-views/</link>
      <pubDate>Wed, 19 Feb 2025 10:00:00 +0800</pubDate>
      <guid>/posts/2025/02/views-and-materialized-views/</guid>
      <description>视图（View）和物化视图（Materialized View）是 PostgreSQL 中封装复杂查询、提供数据和访问控制层的重要工具。&#xA;视图：虚拟表 视图本质上是一个命名的查询，不存储数据，每次查询时执行底层查询。&#xA;-- 准备数据 CREATE TABLE products ( id SERIAL PRIMARY KEY, name VARCHAR(100) NOT NULL, category VARCHAR(50), price NUMERIC(10,2), stock INTEGER, is_active BOOLEAN DEFAULT true ); INSERT INTO products (name, category, price, stock, is_active) VALUES (&amp;#39;笔记本电脑&amp;#39;, &amp;#39;电子产品&amp;#39;, 5999, 50, true), (&amp;#39;手机&amp;#39;, &amp;#39;电子产品&amp;#39;, 3999, 200, true), (&amp;#39;耳机&amp;#39;, &amp;#39;电子产品&amp;#39;, 299, 500, true), (&amp;#39;运动鞋&amp;#39;, &amp;#39;服饰&amp;#39;, 599, 300, true), (&amp;#39;T恤&amp;#39;, &amp;#39;服饰&amp;#39;, 99, 100, false), (&amp;#39;咖啡机&amp;#39;, &amp;#39;家居&amp;#39;, 1299, 50, true); 创建视图 -- 创建一个活跃产品视图 CREATE VIEW active_products AS SELECT id, name, category, price, stock FROM products WHERE is_active = true; -- 使用视图（就像使用表一样） SELECT * FROM active_products WHERE category = &amp;#39;电子产品&amp;#39; ORDER BY price DESC; 视图的作用 简化复杂查询 -- 创建常用分析视图 CREATE VIEW customer_order_summary AS SELECT c.</description>
    </item>
    <item>
      <title>CTE 与递归查询</title>
      <link>/posts/2025/02/cte-and-recursive/</link>
      <pubDate>Mon, 17 Feb 2025 10:00:00 +0800</pubDate>
      <guid>/posts/2025/02/cte-and-recursive/</guid>
      <description>WITH（CTE，Common Table Expression）可以让你在 SQL 中像写程序一样组织逻辑——分步骤、命名中间结果、复用。递归 CTE 更是处理层级数据的利器。&#xA;普通 CTE CTE 把复杂的查询拆成多个可读的步骤：&#xA;-- 统计每个城市的客户消费情况 WITH city_stats AS ( SELECT c.city, COUNT(DISTINCT o.customer_id) AS 客户数, SUM(o.total_amount) AS 总消费 FROM customers c LEFT JOIN orders o ON c.customer_id = o.customer_id AND o.status = &amp;#39;已完成&amp;#39; GROUP BY c.city ), avg_city AS ( SELECT AVG(总消费) AS 城市平均消费 FROM city_stats ) SELECT cs.city, cs.客户数, cs.总消费, ROUND(cs.总消费 / ac.城市平均消费 * 100, 2) AS 相对占比 FROM city_stats cs CROSS JOIN avg_city ac ORDER BY cs.</description>
    </item>
    <item>
      <title>事务与并发控制</title>
      <link>/posts/2025/02/transaction-and-concurrency/</link>
      <pubDate>Sat, 15 Feb 2025 10:00:00 +0800</pubDate>
      <guid>/posts/2025/02/transaction-and-concurrency/</guid>
      <description>数据库的并发控制决定了多个用户同时读写时，数据一致性和性能的平衡。PostgreSQL 使用 MVCC（多版本并发控制）来优雅地解决这个问题。&#xA;什么是事务？ 事务是一组 SQL 操作的逻辑单位，满足 ACID 特性：&#xA;原子性（A）：要么全部成功，要么全部回滚 一致性（C）：事务前后数据状态一致 隔离性（I）：并发事务互不干扰（程度可调） 持久性（D）：提交后数据不会丢失 基本事务控制 -- 准备测试数据 CREATE TABLE bank_accounts ( id SERIAL PRIMARY KEY, name VARCHAR(50) NOT NULL, balance NUMERIC(10,2) DEFAULT 0 CHECK (balance &amp;gt;= 0) ); INSERT INTO bank_accounts (name, balance) VALUES (&amp;#39;张三&amp;#39;, 10000), (&amp;#39;李四&amp;#39;, 5000), (&amp;#39;王五&amp;#39;, 20000); BEGIN / COMMIT / ROLLBACK -- 转账操作：张三转 1000 给李四 BEGIN; UPDATE bank_accounts SET balance = balance - 1000 WHERE name = &amp;#39;张三&amp;#39;; UPDATE bank_accounts SET balance = balance + 1000 WHERE name = &amp;#39;李四&amp;#39;; -- 检查余额是否正确（此时其他会话还看不到变化） SELECT * FROM bank_accounts ORDER BY id; -- 确认无误后提交 COMMIT; 回滚事务 BEGIN; UPDATE bank_accounts SET balance = balance - 50000 WHERE name = &amp;#39;张三&amp;#39;; -- ❌ 余额不足 -- 发现错误，回滚全部操作 ROLLBACK; -- 查看数据，发现数据变回事务开始前的状态 SELECT * FROM bank_accounts; SAVEPOINT 保存点 BEGIN; INSERT INTO bank_accounts (name, balance) VALUES (&amp;#39;赵六&amp;#39;, 3000); SAVEPOINT after_insert; UPDATE bank_accounts SET balance = balance + 5000 WHERE name = &amp;#39;张三&amp;#39;; -- 决定撤销这笔更新 ROLLBACK TO after_insert; -- 张三的余额不受影响，但赵六已插入 COMMIT; 隔离级别 SQL 标准定义了四个隔离级别，从低到高依次限制不同的并发问题：</description>
    </item>
    <item>
      <title>索引与性能优化</title>
      <link>/posts/2025/02/index-and-performance/</link>
      <pubDate>Thu, 13 Feb 2025 10:00:00 +0800</pubDate>
      <guid>/posts/2025/02/index-and-performance/</guid>
      <description>数据量达到百万级后，不合适的索引会让查询慢如蜗牛。本章将系统学习 PostgreSQL 的索引原理和性能优化方法。&#xA;为什么需要索引？ 没有索引的查询需要全表扫描——逐行检查数据。就像一本没有目录的书，要找一句话就得从头翻到尾。&#xA;索引的本质是一个排序过的数据结构（B-Tree），让数据库能在 O(log n) 时间内找到数据，而不是 O(n)。&#xA;创建练习数据 -- 创建一张大表来演示索引效果 CREATE TABLE big_orders ( id SERIAL PRIMARY KEY, user_id INTEGER NOT NULL, product_name VARCHAR(100), amount NUMERIC(10,2), status VARCHAR(10), order_date DATE, created_at TIMESTAMP DEFAULT NOW() ); -- 插入 100 万条数据 INSERT INTO big_orders (user_id, product_name, amount, status, order_date) SELECT (random() * 100000)::INT + 1, CASE (random() * 5)::INT WHEN 0 THEN &amp;#39;笔记本电脑&amp;#39; WHEN 1 THEN &amp;#39;手机&amp;#39; WHEN 2 THEN &amp;#39;耳机&amp;#39; WHEN 3 THEN &amp;#39;运动鞋&amp;#39; ELSE &amp;#39;T恤&amp;#39; END, (random() * 5000 + 100)::NUMERIC(10,2), CASE (random() * 4)::INT WHEN 0 THEN &amp;#39;已完成&amp;#39; WHEN 1 THEN &amp;#39;已取消&amp;#39; WHEN 2 THEN &amp;#39;退款中&amp;#39; ELSE &amp;#39;已完成&amp;#39; END, DATE &amp;#39;2024-01-01&amp;#39; + (random() * 730)::INT FROM generate_series(1, 1000000); -- 确认数据已插入 SELECT count(*) FROM big_orders; EXPLAIN 读懂查询计划 -- 查看查询计划 EXPLAIN SELECT * FROM big_orders WHERE user_id = 42; EXPLAIN ANALYZE SELECT * FROM big_orders WHERE user_id = 42; EXPLAIN 输出解读：</description>
    </item>
    <item>
      <title>窗口函数</title>
      <link>/posts/2025/02/window-functions/</link>
      <pubDate>Tue, 11 Feb 2025 10:00:00 +0800</pubDate>
      <guid>/posts/2025/02/window-functions/</guid>
      <description>窗口函数是 PostgreSQL 最强大的分析工具之一。它在不改变行数的前提下，为每一行计算一个&amp;quot;窗口&amp;quot;内的聚合值——让你同时看到&amp;quot;行级&amp;quot;和&amp;quot;聚合级&amp;quot;的数据。&#xA;窗口函数 vs 普通聚合 普通聚合用 GROUP BY 将多行压缩为一行：&#xA;SELECT product, SUM(amount) FROM sales GROUP BY product; -- 结果：每种商品一行 窗口函数保留原始行数，额外返回聚合值：&#xA;SELECT product, amount, SUM(amount) OVER () AS 总计 FROM sales; -- 结果：每行不变，多一列&amp;#34;总计&amp;#34; 创建练习数据集 -- 月度销售数据 CREATE TABLE monthly_sales ( id SERIAL PRIMARY KEY, product VARCHAR(50), category VARCHAR(50), month DATE NOT NULL, sales_amount NUMERIC(10,2), sales_qty INTEGER ); INSERT INTO monthly_sales (product, category, month, sales_amount, sales_qty) VALUES (&amp;#39;笔记本电脑&amp;#39;, &amp;#39;电子产品&amp;#39;, &amp;#39;2025-01-01&amp;#39;, 300000, 50), (&amp;#39;笔记本电脑&amp;#39;, &amp;#39;电子产品&amp;#39;, &amp;#39;2025-02-01&amp;#39;, 350000, 58), (&amp;#39;笔记本电脑&amp;#39;, &amp;#39;电子产品&amp;#39;, &amp;#39;2025-03-01&amp;#39;, 280000, 46), (&amp;#39;手机&amp;#39;, &amp;#39;电子产品&amp;#39;, &amp;#39;2025-01-01&amp;#39;, 450000, 112), (&amp;#39;手机&amp;#39;, &amp;#39;电子产品&amp;#39;, &amp;#39;2025-02-01&amp;#39;, 520000, 130), (&amp;#39;手机&amp;#39;, &amp;#39;电子产品&amp;#39;, &amp;#39;2025-03-01&amp;#39;, 480000, 120), (&amp;#39;耳机&amp;#39;, &amp;#39;电子产品&amp;#39;, &amp;#39;2025-01-01&amp;#39;, 60000, 200), (&amp;#39;耳机&amp;#39;, &amp;#39;电子产品&amp;#39;, &amp;#39;2025-02-01&amp;#39;, 55000, 180), (&amp;#39;耳机&amp;#39;, &amp;#39;电子产品&amp;#39;, &amp;#39;2025-03-01&amp;#39;, 70000, 240), (&amp;#39;运动鞋&amp;#39;, &amp;#39;服饰&amp;#39;, &amp;#39;2025-01-01&amp;#39;, 80000, 135), (&amp;#39;运动鞋&amp;#39;, &amp;#39;服饰&amp;#39;, &amp;#39;2025-02-01&amp;#39;, 75000, 125), (&amp;#39;运动鞋&amp;#39;, &amp;#39;服饰&amp;#39;, &amp;#39;2025-03-01&amp;#39;, 65000, 108), (&amp;#39;T恤&amp;#39;, &amp;#39;服饰&amp;#39;, &amp;#39;2025-01-01&amp;#39;, 30000, 300), (&amp;#39;T恤&amp;#39;, &amp;#39;服饰&amp;#39;, &amp;#39;2025-02-01&amp;#39;, 35000, 350), (&amp;#39;T恤&amp;#39;, &amp;#39;服饰&amp;#39;, &amp;#39;2025-03-01&amp;#39;, 28000, 280), (&amp;#39;咖啡机&amp;#39;, &amp;#39;家居&amp;#39;, &amp;#39;2025-01-01&amp;#39;, 65000, 50), (&amp;#39;咖啡机&amp;#39;, &amp;#39;家居&amp;#39;, &amp;#39;2025-02-01&amp;#39;, 55000, 42), (&amp;#39;咖啡机&amp;#39;, &amp;#39;家居&amp;#39;, &amp;#39;2025-03-01&amp;#39;, 40000, 30); 排名函数 -- ROW_NUMBER：连续的排名（不并列） SELECT product, month, sales_amount, ROW_NUMBER() OVER (ORDER BY sales_amount DESC) AS 总排名 FROM monthly_sales; -- RANK：并列排名（跳过后续序号） SELECT product, month, sales_amount, RANK() OVER (ORDER BY sales_amount DESC) AS 排名_可并列 FROM monthly_sales; -- DENSE_RANK：并列排名（不跳过后续序号） SELECT product, month, sales_amount, DENSE_RANK() OVER (ORDER BY sales_amount DESC) AS 排名_密集 FROM monthly_sales; 三者的区别：</description>
    </item>
    <item>
      <title>JOIN 与子查询</title>
      <link>/posts/2025/02/join-and-subquery/</link>
      <pubDate>Sun, 09 Feb 2025 10:00:00 +0800</pubDate>
      <guid>/posts/2025/02/join-and-subquery/</guid>
      <description>真实世界的数据很少只存在于一张表中。JOIN 和多表关联是 SQL 最核心的能力之一。&#xA;创建练习数据集 -- 客户表 CREATE TABLE customers ( customer_id SERIAL PRIMARY KEY, name VARCHAR(100) NOT NULL, city VARCHAR(50), member_level VARCHAR(10) DEFAULT &amp;#39;普通&amp;#39;, created_at TIMESTAMP DEFAULT NOW() ); -- 订单表 CREATE TABLE orders ( order_id SERIAL PRIMARY KEY, customer_id INTEGER REFERENCES customers(customer_id), order_date DATE NOT NULL, total_amount NUMERIC(10,2) NOT NULL, status VARCHAR(10) DEFAULT &amp;#39;已完成&amp;#39; CHECK (status IN (&amp;#39;已完成&amp;#39;, &amp;#39;已取消&amp;#39;, &amp;#39;退款中&amp;#39;)) ); -- 订单明细表 CREATE TABLE order_items ( item_id SERIAL PRIMARY KEY, order_id INTEGER NOT NULL REFERENCES orders(order_id), product_name VARCHAR(100) NOT NULL, price NUMERIC(10,2) NOT NULL, quantity INTEGER NOT NULL ); -- 插入客户数据 INSERT INTO customers (name, city, member_level) VALUES (&amp;#39;张三&amp;#39;, &amp;#39;北京&amp;#39;, &amp;#39;金卡&amp;#39;), (&amp;#39;李四&amp;#39;, &amp;#39;上海&amp;#39;, &amp;#39;银卡&amp;#39;), (&amp;#39;王五&amp;#39;, &amp;#39;广州&amp;#39;, &amp;#39;金卡&amp;#39;), (&amp;#39;赵六&amp;#39;, &amp;#39;深圳&amp;#39;, &amp;#39;普通&amp;#39;), (&amp;#39;陈七&amp;#39;, &amp;#39;杭州&amp;#39;, &amp;#39;银卡&amp;#39;), (&amp;#39;周八&amp;#39;, &amp;#39;成都&amp;#39;, &amp;#39;普通&amp;#39;); -- 从未下单的客户 -- 插入订单数据（陈七从未下单） INSERT INTO orders (customer_id, order_date, total_amount, status) VALUES (1, &amp;#39;2025-01-05&amp;#39;, 8997, &amp;#39;已完成&amp;#39;), (2, &amp;#39;2025-01-08&amp;#39;, 5295, &amp;#39;已完成&amp;#39;), (3, &amp;#39;2025-01-12&amp;#39;, 3500, &amp;#39;已完成&amp;#39;), (1, &amp;#39;2025-01-15&amp;#39;, 399, &amp;#39;已完成&amp;#39;), (4, &amp;#39;2025-01-18&amp;#39;, 1500, &amp;#39;已完成&amp;#39;), (5, &amp;#39;2025-01-20&amp;#39;, 2500, &amp;#39;已完成&amp;#39;), (2, &amp;#39;2025-01-22&amp;#39;, 880, &amp;#39;已取消&amp;#39;), (1, &amp;#39;2025-01-25&amp;#39;, 12000, &amp;#39;已完成&amp;#39;), (3, &amp;#39;2025-01-28&amp;#39;, 599, &amp;#39;退款中&amp;#39;), (5, &amp;#39;2025-02-01&amp;#39;, 1800, &amp;#39;已完成&amp;#39;); -- 插入订单明细 INSERT INTO order_items (order_id, product_name, price, quantity) VALUES (1, &amp;#39;笔记本电脑&amp;#39;, 5999, 1), (1, &amp;#39;手机&amp;#39;, 3999, 1), -- 订单1：5999+3999 = 9998-.</description>
    </item>
    <item>
      <title>聚合与分组</title>
      <link>/posts/2025/02/aggregation-and-grouping/</link>
      <pubDate>Fri, 07 Feb 2025 10:00:00 +0800</pubDate>
      <guid>/posts/2025/02/aggregation-and-grouping/</guid>
      <description>聚合函数 聚合函数将多行数据汇总为单个结果，是数据分析的核心工具。&#xA;-- 创建销售数据表 CREATE TABLE sales ( id SERIAL PRIMARY KEY, product VARCHAR(100), category VARCHAR(50), amount NUMERIC(10,2), quantity INTEGER, sale_date DATE, salesperson VARCHAR(50) ); INSERT INTO sales (product, category, amount, quantity, sale_date, salesperson) VALUES (&amp;#39;笔记本电脑&amp;#39;, &amp;#39;电子产品&amp;#39;, 5999, 2, &amp;#39;2025-01-01&amp;#39;, &amp;#39;小陈&amp;#39;), (&amp;#39;手机&amp;#39;, &amp;#39;电子产品&amp;#39;, 3999, 5, &amp;#39;2025-01-02&amp;#39;, &amp;#39;小陈&amp;#39;), (&amp;#39;耳机&amp;#39;, &amp;#39;电子产品&amp;#39;, 299, 10, &amp;#39;2025-01-03&amp;#39;, &amp;#39;小李&amp;#39;), (&amp;#39;运动鞋&amp;#39;, &amp;#39;服饰&amp;#39;, 599, 3, &amp;#39;2025-01-05&amp;#39;, &amp;#39;小王&amp;#39;), (&amp;#39;T恤&amp;#39;, &amp;#39;服饰&amp;#39;, 99, 20, &amp;#39;2025-01-06&amp;#39;, &amp;#39;小陈&amp;#39;), (&amp;#39;咖啡机&amp;#39;, &amp;#39;家居&amp;#39;, 1299, 2, &amp;#39;2025-01-07&amp;#39;, &amp;#39;小李&amp;#39;), (&amp;#39;台灯&amp;#39;, &amp;#39;家居&amp;#39;, 199, 8, &amp;#39;2025-01-08&amp;#39;, &amp;#39;小王&amp;#39;), (&amp;#39;手机&amp;#39;, &amp;#39;电子产品&amp;#39;, 3999, 3, &amp;#39;2025-01-10&amp;#39;, &amp;#39;小李&amp;#39;), (&amp;#39;笔记本电脑&amp;#39;, &amp;#39;电子产品&amp;#39;, 5999, 1, &amp;#39;2025-01-12&amp;#39;, &amp;#39;小王&amp;#39;), (&amp;#39;运动鞋&amp;#39;, &amp;#39;服饰&amp;#39;, 599, 5, &amp;#39;2025-01-15&amp;#39;, &amp;#39;小陈&amp;#39;), (&amp;#39;耳机&amp;#39;, &amp;#39;电子产品&amp;#39;, 299, 15, &amp;#39;2025-01-18&amp;#39;, &amp;#39;小李&amp;#39;), (&amp;#39;台灯&amp;#39;, &amp;#39;家居&amp;#39;, 199, 12, &amp;#39;2025-01-20&amp;#39;, &amp;#39;小陈&amp;#39;), (&amp;#39;咖啡机&amp;#39;, &amp;#39;家居&amp;#39;, 1299, 1, &amp;#39;2025-01-22&amp;#39;, &amp;#39;小王&amp;#39;), (&amp;#39;T恤&amp;#39;, &amp;#39;服饰&amp;#39;, 99, 30, &amp;#39;2025-01-25&amp;#39;, &amp;#39;小李&amp;#39;), (&amp;#39;手机&amp;#39;, &amp;#39;电子产品&amp;#39;, 3999, 4, &amp;#39;2025-01-28&amp;#39;, &amp;#39;小陈&amp;#39;); 五种基础聚合 SELECT COUNT(*) AS 总记录数, COUNT(DISTINCT product) AS 不同商品数, SUM(amount) AS 总金额, AVG(amount) AS 平均金额, MAX(amount) AS 最大金额, MIN(amount) AS 最小金额 FROM sales; COUNT 的细节差异 -- COUNT(*) 统计行数（包括 NULL） SELECT COUNT(*) FROM sales; -- COUNT(column) 统计非 NULL 值的数量 SELECT COUNT(product) FROM sales; -- COUNT(DISTINCT column) 统计不重复的非 NULL 值数量 SELECT COUNT(DISTINCT product) FROM sales; -- 三者的区别 SELECT COUNT(*) AS 总行数, COUNT(salesperson) AS 非空姓名数, COUNT(DISTINCT salesperson) AS 不同姓名数 FROM sales; GROUP BY 分组聚合 -- 按产品分组 SELECT product, COUNT(*) AS 订单数, SUM(amount) AS 总销售额, SUM(quantity) AS 总销量, ROUND(AVG(amount), 2) AS 平均金额 FROM sales GROUP BY product ORDER BY 总销售额 DESC; 多维度分组 -- 按类别和产品分组（SQL 中，SELECT 的非聚合列必须出现在 GROUP BY 中） SELECT category, product, SUM(amount) AS 销售额, COUNT(*) AS 订单数 FROM sales GROUP BY category, product ORDER BY category, 销售额 DESC; PostgreSQL 允许按 SELECT 中列的序号简化：</description>
    </item>
    <item>
      <title>时间序列分析</title>
      <link>/posts/2025/01/time-series-analysis/</link>
      <pubDate>Sun, 12 Jan 2025 00:00:00 +0000</pubDate>
      <guid>/posts/2025/01/time-series-analysis/</guid>
      <description>时间序列数据无处不在——股价、气温、网站流量、销售额、传感器读数……任何按时间顺序记录的数据都是时间序列。&#xA;时间序列分析和其他分析最大的不同在于：数据点之间不是独立的。今天的股价和昨天的股价有关，这个月的销量受上个月的影响。这种时序依赖性既是挑战也是机会——它让我们能做预测。&#xA;环境准备 import numpy as np import pandas as pd import matplotlib.pyplot as plt import seaborn as sns from statsmodels.tsa.stattools import adfuller, acf, pacf from statsmodels.tsa.seasonal import seasonal_decompose from statsmodels.tsa.holtwinters import ExponentialSmoothing, SimpleExpSmoothing from statsmodels.tsa.arima.model import ARIMA from statsmodels.graphics.tsaplots import plot_acf, plot_pacf import warnings warnings.filterwarnings(&amp;#39;ignore&amp;#39;) plt.rcParams[&amp;#39;font.sans-serif&amp;#39;] = [&amp;#39;SimHei&amp;#39;] plt.rcParams[&amp;#39;axes.unicode_minus&amp;#39;] = False sns.set_theme(style=&amp;#39;whitegrid&amp;#39;) 时间序列基础操作 创建和转换时间索引 # 从列创建日期时间 dates = pd.date_range(start=&amp;#39;2023-01-01&amp;#39;, periods=365, freq=&amp;#39;D&amp;#39;) ts = pd.Series(np.random.randn(365), index=dates) print(ts.head()) print(f&amp;#39;索引类型: {ts.index.dtype}&amp;#39;) # 字符串转 datetime df = pd.</description>
    </item>
    <item>
      <title>SQL 数据分析</title>
      <link>/posts/2025/01/sql-for-data-analysis/</link>
      <pubDate>Sat, 11 Jan 2025 00:00:00 +0000</pubDate>
      <guid>/posts/2025/01/sql-for-data-analysis/</guid>
      <description>Pandas 很强大，但现实世界中大部分数据都存放在关系型数据库里。无论你是从数据仓库取数、查询业务系统，还是做报表，SQL 都是数据分析师最核心的技能之一。&#xA;这篇文章不讲 DBA 那一套（建表、索引优化、事务管理），而是聚焦于数据分析师最常用的 SQL 操作，并用 SQLite 作为实践环境，配合 Python 打通数据链路。&#xA;环境准备 import sqlite3 import pandas as pd import numpy as np import matplotlib.pyplot as plt import seaborn as sns # 创建内存数据库 conn = sqlite3.connect(&amp;#39;:memory:&amp;#39;) cursor = conn.cursor() 我们创建三张模拟的业务表：&#xA;# 用户表 cursor.execute(&amp;#39;&amp;#39;&amp;#39; CREATE TABLE users ( user_id INTEGER PRIMARY KEY, name TEXT, age INTEGER, city TEXT, signup_date TEXT ) &amp;#39;&amp;#39;&amp;#39;) # 订单表 cursor.execute(&amp;#39;&amp;#39;&amp;#39; CREATE TABLE orders ( order_id INTEGER PRIMARY KEY, user_id INTEGER, product TEXT, amount REAL, quantity INTEGER, order_date TEXT, status TEXT ) &amp;#39;&amp;#39;&amp;#39;) # 产品表 cursor.</description>
    </item>
    <item>
      <title>统计学基础</title>
      <link>/posts/2025/01/statistics-basics/</link>
      <pubDate>Fri, 10 Jan 2025 00:00:00 +0000</pubDate>
      <guid>/posts/2025/01/statistics-basics/</guid>
      <description>数据分析的第三只眼睛是统计学。没有统计学，你看到的只是数字的堆砌；有了统计学，你才能判断这些差异是否显著、这些趋势是否可靠。&#xA;本文的目标不是让你成为统计学家，而是让你掌握数据分析中最常用的统计工具——能看懂统计指标，能做假设检验，能判断什么时候该用什么方法。&#xA;环境准备 import numpy as np import pandas as pd import matplotlib.pyplot as plt import seaborn as sns from scipy import stats from scipy.stats import (norm, t, chi2, f, ttest_ind, ttest_rel, chisquare, f_oneway, pearsonr, spearmanr) import statsmodels.api as sm import warnings warnings.filterwarnings(&amp;#39;ignore&amp;#39;) plt.rcParams[&amp;#39;font.sans-serif&amp;#39;] = [&amp;#39;SimHei&amp;#39;] plt.rcParams[&amp;#39;axes.unicode_minus&amp;#39;] = False sns.set_theme(style=&amp;#39;whitegrid&amp;#39;) 描述性统计回顾 在动手之前，先快速温习基础篇学过的描述性统计。&#xA;data = np.random.normal(loc=50, scale=15, size=1000) # 集中趋势 mean = np.mean(data) median = np.median(data) mode = stats.mode(data, keepdims=True).mode[0] # 离散程度 variance = np.</description>
    </item>
    <item>
      <title>数据可视化进阶</title>
      <link>/posts/2025/01/data-viz-advanced/</link>
      <pubDate>Thu, 09 Jan 2025 00:00:00 +0000</pubDate>
      <guid>/posts/2025/01/data-viz-advanced/</guid>
      <description>基础篇里我们学会了用 Matplotlib 画简单的折线图和柱状图。但到了进阶阶段，你需要更多——更丰富的图表类型、更美观的视觉风格、更直观的交互效果。&#xA;本文会深入讲解 Seaborn 的高级用法，再带你进入 Plotly 的交互式可视化世界，最后总结一套可视化设计的实用原则。&#xA;环境准备 import numpy as np import pandas as pd import matplotlib.pyplot as plt import seaborn as sns import plotly.express as px import plotly.graph_objects as go from plotly.subplots import make_subplots import warnings warnings.filterwarnings(&amp;#39;ignore&amp;#39;) plt.rcParams[&amp;#39;font.sans-serif&amp;#39;] = [&amp;#39;SimHei&amp;#39;] plt.rcParams[&amp;#39;axes.unicode_minus&amp;#39;] = False # Seaborn 主题 sns.set_theme(style=&amp;#39;whitegrid&amp;#39;, palette=&amp;#39;muted&amp;#39;, font=&amp;#39;SimHei&amp;#39;) 我们主要使用 Seaborn 内置数据集和 Plotly Express 示例数据来演示。&#xA;Seaborn 深度探索 基础篇我们用了 sns.histplot 和 sns.boxplot。Seaborn 的真正威力在于它的关系型绘图函数族。&#xA;relplot——关系图的统一入口 relplot 是一个 figure-level 函数，同时覆盖散点图和线图，并且天然支持分面：</description>
    </item>
    <item>
      <title>探索性数据分析（EDA）</title>
      <link>/posts/2025/01/exploratory-data-analysis/</link>
      <pubDate>Wed, 08 Jan 2025 00:00:00 +0000</pubDate>
      <guid>/posts/2025/01/exploratory-data-analysis/</guid>
      <description>拿到一份干净的数据后，你的第一反应是什么？很多人会立刻跑模型、调参数，但这是本末倒置。在建模之前，你需要先回答两个问题：这些数据长什么样？里面有什么规律？&#xA;探索性数据分析（EDA）就是回答这两个问题的过程。它不是一次性的步骤，而是一个反复提问、画图、计算、再提问的循环。好的 EDA 能帮你发现数据质量问题、验证假设、指导特征工程，甚至直接决定选什么模型。&#xA;EDA 的核心方法论 EDA 没有固定脚本，但有一个可循的工作流：&#xA;概览：数据有多大？每列是什么类型？有没有明显的缺失和异常？ 单变量分析：每个变量各自的分布如何？ 双变量分析：两个变量之间有什么关系？ 多变量分析：多个变量综合起来能发现什么？ 总结与假设：形成分析假设，进入建模阶段。 我们以 Kaggle 的 Titanic 数据集为例，一步步走完 EDA 流程。&#xA;环境与数据加载 import numpy as np import pandas as pd import matplotlib.pyplot as plt import seaborn as sns import warnings warnings.filterwarnings(&amp;#39;ignore&amp;#39;) # 设置中文字体 plt.rcParams[&amp;#39;font.sans-serif&amp;#39;] = [&amp;#39;SimHei&amp;#39;] plt.rcParams[&amp;#39;axes.unicode_minus&amp;#39;] = False sns.set_theme(style=&amp;#39;whitegrid&amp;#39;, palette=&amp;#39;muted&amp;#39;) 我们直接加载 Titanic 数据集：&#xA;# 从 seaborn 加载 df = sns.load_dataset(&amp;#39;titanic&amp;#39;) print(&amp;#39;数据集大小:&amp;#39;, df.shape) print(&amp;#39;\n前 5 行:&amp;#39;) print(df.head()) 第一步：快速概览 # 基本信息 print(df.info()) # 数值统计 print(df.</description>
    </item>
    <item>
      <title>数据清洗与预处理</title>
      <link>/posts/2025/01/data-cleaning/</link>
      <pubDate>Tue, 07 Jan 2025 00:00:00 +0000</pubDate>
      <guid>/posts/2025/01/data-cleaning/</guid>
      <description>数据分析圈有一句老话：数据清洗占了数据分析工作量的 80%。这不是夸张。在实际项目中，你拿到的原始数据几乎总是有问题的——缺失值、异常值、重复记录、格式不统一、噪声数据……如果不在分析之前把这些坑填平，后面的建模和可视化都会建立在沙地上。&#xA;本文是进阶篇的第一篇，我们会系统地讲解数据清洗与预处理的完整流程，每步都配有可运行的 Pandas 和 scikit-learn 代码。&#xA;数据质量维度 在动手清洗之前，先建立一套评估数据质量的框架。通常从四个维度衡量：&#xA;维度 含义 典型问题 准确性 数据是否真实反映客观事实 录入错误、单位混淆 完整性 数据是否有缺失 空值、Null、NaN 一致性 数据格式和单位是否统一 日期格式不一、大小写混用 时效性 数据是否在有效时间范围内 过期数据、时区偏差 理解这些维度能帮你快速定位问题类型，而不是盲目地套用清洗函数。&#xA;环境准备 本文所有代码基于以下依赖：&#xA;import numpy as np import pandas as pd import matplotlib.pyplot as plt import seaborn as sns from sklearn.preprocessing import LabelEncoder, OneHotEncoder, StandardScaler # 显示设置 pd.set_option(&amp;#39;display.max_columns&amp;#39;, None) pd.set_option(&amp;#39;display.max_rows&amp;#39;, 20) 我们用一个模拟的客户数据集来演示：&#xA;np.random.seed(42) n = 1000 df = pd.DataFrame({ &amp;#39;id&amp;#39;: range(n), &amp;#39;age&amp;#39;: np.random.randint(18, 70, n).astype(float), &amp;#39;income&amp;#39;: np.</description>
    </item>
  </channel>
</rss>
