前置知识: SQL

PostgreSQL 窗口函数

1 min入门

PostgreSQL 窗口函数 的完整教学讲解。

排名函数

换行写法:ROW_NUMBER 行号 ROW_NUMBER() OVER (PARTITION BY <列> ORDER BY <列> [ASC|DESC])

-- 部门内按薪资生成行号
SELECT name, dept_id, salary,
  ROW_NUMBER() OVER (PARTITION BY dept_id ORDER BY salary DESC) AS row_num
FROM employees;

换行写法:RANK 排名(带跳跃) RANK() OVER (PARTITION BY <列> ORDER BY <列> [ASC|DESC])

-- 部门内薪资排名(同值跳号)
SELECT name, dept_id, salary,
  RANK() OVER (PARTITION BY dept_id ORDER BY salary DESC) AS rank
FROM employees;

换行写法:DENSE_RANK 密集排名 DENSE_RANK() OVER (PARTITION BY <列> ORDER BY <列> [ASC|DESC])

-- 部门内薪资密集排名(同值不跳号)
SELECT name, dept_id, salary,
  DENSE_RANK() OVER (PARTITION BY dept_id ORDER BY salary DESC) AS dense_rank
FROM employees;

换行写法:PERCENT_RANK 百分比排名 PERCENT_RANK() OVER (PARTITION BY <列> ORDER BY <列>)

-- 计算百分比排名(0 到 1)
SELECT name, salary,
  PERCENT_RANK() OVER (ORDER BY salary DESC) AS pct_rank
FROM employees;

换行写法:CUME_DIST 累积分布 CUME_DIST() OVER (PARTITION BY <列> ORDER BY <列>)

-- 计算累积分布比例
SELECT name, salary,
  CUME_DIST() OVER (ORDER BY salary ASC) AS cume_dist
FROM employees;

换行写法:NTILE 分桶 NTILE(<桶数>) OVER (PARTITION BY <列> ORDER BY <列>)

-- 将数据等分为 4 个桶
SELECT name, salary,
  NTILE(4) OVER (ORDER BY salary DESC) AS quartile
FROM employees;

偏移函数

换行写法:LAG 访问前一行 LAG(<列>[, <偏移量>[, <默认值>]]) OVER (ORDER BY <列>)

-- 计算环比变化
SELECT order_date, amount,
  amount - LAG(amount) OVER (ORDER BY order_date) AS day_over_day
FROM daily_sales;

换行写法:LEAD 访问后一行 LEAD(<列>[, <偏移量>[, <默认值>]]) OVER (ORDER BY <列>)

-- 访问下一行金额
SELECT order_date, amount,
  LEAD(amount) OVER (ORDER BY order_date) AS next_day_amount
FROM daily_sales;

换行写法:FIRST_VALUE 第一行值 FIRST_VALUE(<列>) OVER (PARTITION BY <列> ORDER BY <列>)

-- 获取每个部门最低薪资
SELECT name, dept_id, salary,
  FIRST_VALUE(salary) OVER (PARTITION BY dept_id ORDER BY salary ASC) AS min_salary
FROM employees;

换行写法:LAST_VALUE 末尾值 LAST_VALUE(<列>) OVER (PARTITION BY <列> ORDER BY <列> ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING)

-- 获取每个部门最高薪资
SELECT name, dept_id, salary,
  LAST_VALUE(salary) OVER (
    PARTITION BY dept_id ORDER BY salary
    ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
  ) AS max_salary
FROM employees;

换行写法:NTH_VALUE 第 N 行值 NTH_VALUE(<列>, <N>) OVER (PARTITION BY <列> ORDER BY <列>)

-- 获取部门内第 2 高薪资
SELECT name, dept_id, salary,
  NTH_VALUE(salary, 2) OVER (PARTITION BY dept_id ORDER BY salary DESC) AS second_salary
FROM employees;

聚合窗口函数

换行写法:累计求和 SUM(<列>) OVER (ORDER BY <列> ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)

-- 按日期累计求和
SELECT order_date, amount,
  SUM(amount) OVER (ORDER BY order_date) AS cumulative
FROM daily_sales;

换行写法:移动平均 AVG(<列>) OVER (ORDER BY <列> ROWS BETWEEN <N> PRECEDING AND CURRENT ROW)

-- 7 日移动平均
SELECT order_date, amount,
  AVG(amount) OVER (ORDER BY order_date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS moving_avg
FROM daily_sales;

换行写法:分组累计求和 SUM(<列>) OVER (PARTITION BY <列> ORDER BY <列>)

-- 每个用户订单金额累计
SELECT user_id, order_date, amount,
  SUM(amount) OVER (PARTITION BY user_id ORDER BY order_date) AS cumulative
FROM orders;

换行写法:分组占比 SELECT <列>, <列> / SUM(<列>) OVER (PARTITION BY <列>) AS ratio

-- 计算每个用户订单金额占该用户总金额的比例
SELECT user_id, order_no, amount,
  amount / SUM(amount) OVER (PARTITION BY user_id) AS ratio
FROM orders;

换行写法:累计计数 COUNT(<列>) OVER (ORDER BY <列>)

-- 按日期累计计数
SELECT order_date,
  COUNT(*) OVER (ORDER BY order_date) AS cumulative_count
FROM orders;

窗口范围控制

换行写法:ROWS 范围 <函数>() OVER (ORDER BY <列> ROWS BETWEEN <起> AND <止>)

-- 指定行范围窗口
SELECT order_date, amount,
  AVG(amount) OVER (
    ORDER BY order_date
    ROWS BETWEEN 2 PRECEDING AND 2 FOLLOWING
  ) AS window_avg
FROM daily_sales;

换行写法:RANGE 范围 <函数>() OVER (ORDER BY <列> RANGE BETWEEN <起> AND <止>)

-- 按值范围窗口
SELECT order_date, amount,
  SUM(amount) OVER (
    ORDER BY order_date
    RANGE BETWEEN INTERVAL '7' DAY PRECEDING AND CURRENT ROW
  ) AS weekly_sum
FROM daily_sales;

换行写法:UNBOUNDED 无界限 <函数>() OVER (ORDER BY <列> ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING)

-- 整个分区作为窗口
SELECT name, salary,
  AVG(salary) OVER (
    ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
  ) AS overall_avg
FROM employees;

FILTER 条件聚合

换行写法:FILTER 条件聚合 <聚合函数>(<列>) FILTER (WHERE <条件>) OVER (...)

-- 条件聚合统计高收入人数
SELECT dept_id,
  COUNT(*) AS total,
  COUNT(*) FILTER (WHERE salary > 50000) AS high_earners,
  AVG(salary) FILTER (WHERE gender = 'M') AS male_avg
FROM employees
GROUP BY dept_id;

换行写法:FILTER 窗口函数组合 SUM(<列>) FILTER (WHERE <条件>) OVER (PARTITION BY <列>)

-- 每个部门高薪累计
SELECT name, dept_id, salary,
  SUM(salary) FILTER (WHERE salary > 50000) OVER (PARTITION BY dept_id) AS high_salary_sum
FROM employees;

命名窗口

换行写法:WINDOW 子句定义命名窗口 SELECT <列>, <函数>() OVER <窗口名> FROM <表> WINDOW <窗口名> AS (PARTITION BY <列> ORDER BY <列>)

-- 复用窗口定义
SELECT name, dept_id, salary,
  RANK() OVER w AS rank,
  DENSE_RANK() OVER w AS dense_rank,
  ROW_NUMBER() OVER w AS row_num
FROM employees
WINDOW w AS (PARTITION BY dept_id ORDER BY salary DESC);

常见应用场景

换行写法:取每组前 N 行 SELECT * FROM (SELECT *, ROW_NUMBER() OVER (PARTITION BY <列> ORDER BY <列>) AS rn FROM <表>) WHERE rn <= <N>

-- 取每个部门薪资前 3 的员工
SELECT * FROM (
  SELECT name, dept_id, salary,
    ROW_NUMBER() OVER (PARTITION BY dept_id ORDER BY salary DESC) AS rn
  FROM employees
) ranked
WHERE rn <= 3;

换行写法:去除重复行保留最新 SELECT * FROM (SELECT *, ROW_NUMBER() OVER (PARTITION BY <列> ORDER BY <时间> DESC) AS rn FROM <表>) WHERE rn = 1

-- 每个用户保留最新一条登录记录
SELECT * FROM (
  SELECT *, ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY login_time DESC) AS rn
  FROM user_logins
) latest
WHERE rn = 1;