PostgreSQL笔记15: 高级查询全面解析——多表连接、窗口函数、CTE与序列生成

postgreSQL笔记-JOIN 文章目录概述提取交集:``INNER JOIN``其他几种提取方式对``LEFT/RIGHT JOIN``的理解 概述 JOIN语句用于从不同表中通过公共的列提取数据。 将该特殊的公共列称为公共键。 提取交集:INNER JOIN 表1 INNER JOIN 表2:代表提取表1、表2中公共键均有取值的所有个案。 语法: SELECT 表1.欲提取的列1,表1.欲提取的列2,... 表2.欲提取的列1,表2.欲提取的列2,... FROM 表1 INNER JOIN 表2 ON 表1.公共键名=表2.公共键名 阅读详情

纲要

  • 多表连接 (JOIN)
    • 连接类型:INNER JOIN、LEFT JOIN、RIGHT JOIN、FULL OUTER JOIN、CROSS JOIN
    • 连接算法:Nested Loop Join、Merge Join、Hash Join
    • 适用场景与性能考量
  • 横向子查询 (LATERAL)
  • 窗口函数 (Window Functions)
    • ROW_NUMBER()、RANK()、DENSE_RANK()
    • PARTITION BY 与 ORDER BY
    • Top-N 查询与累计统计
  • 公用表表达式 (CTE / WITH 查询)
  • 序列生成函数 (GENERATE_SERIES)
  • 条件聚合 (FILTER 子句)
  • 聚合与分组 (GROUP BY、HAVING)

多表连接

在实际业务中,绝大多数查询并非仅涉及单张表,而是需要通过 JOIN 将多张表的数据组合起来。PostgreSQL 遵循 SQL 标准,提供了多种连接类型。

连接类型

以下示例基于 student 表和 class 表进行演示:

-- 学生表
CREATE TABLE student (
    student_id SERIAL PRIMARY KEY,
    name VARCHAR(50),
    class_id INT,
    score INT
);

-- 班级表
CREATE TABLE class (
    class_id SERIAL PRIMARY KEY,
    class_name VARCHAR(50)
);

-- 示例数据
INSERT INTO class (class_id, class_name) VALUES (1, '一班'), (2, '二班'), (3, '三班');
INSERT INTO student (name, class_id, score) VALUES
    ('Alice', 1, 95), ('Bob', 1, 82), ('Charlie', 1, 76), ('David', 1, 75),
    ('Eve', 2, 88), ('Frank', 2, 79), ('Grace', 2, 91),
    ('Henry', 3, 85), ('Ivy', 3, 90), ('Jack', 3, 78);
INNER JOIN

INNER JOIN 仅返回两个表中满足连接条件的匹配行,是最常用的连接类型。

SELECT s.name, s.score, c.class_name
FROM student s
INNER JOIN class c ON s.class_id = c.class_id;
LEFT JOIN / RIGHT JOIN

LEFT JOIN 返回左表的全部行,右表匹配不到的行以 NULL 填充。RIGHT JOIN 与之相反。

-- 返回所有班级及其学生信息,无学生的班级右表部分为 NULL
SELECT c.class_name, s.name, s.score
FROM class c
LEFT JOIN student s ON c.class_id = s.class_id;
FULL OUTER JOIN

FULL OUTER JOIN 返回左表和右表的全部行,匹配不到的以 NULL 填充。

SELECT c.class_name, s.name, s.score
FROM class c
FULL OUTER JOIN student s ON c.class_id = s.class_id;
CROSS JOIN

CROSS JOIN 返回两表的笛卡尔积,即所有行的组合。需谨慎使用,数据量较大时会产生海量结果。

SELECT c.class_name, s.name
FROM class c
CROSS JOIN student s;

连接算法

PostgreSQL 实现 JOIN 时采用三种底层算法,优化器会根据统计信息和查询条件自动选择。

算法适用场景特点
Nested Loop Join数据量较小,或内表有索引外表每返回一行,便扫描一次内表
Hash Join数据量较大,等值连接先构建小表的哈希表,再扫描大表匹配
Merge Join数据已排序,或可利用索引两边排序后合并,仅支持等值连接

横向子查询 (LATERAL)

LATERAL 允许 FROM 子句中的子查询引用同一 FROM 子句中位于它之前的表或子查询的列。这使得子查询可以针对左表的每一行独立计算。

典型场景:为每位用户获取其最新的一笔订单。

SELECT u.name, o.order_date, o.amount
FROM users u
LEFT JOIN LATERAL (
    SELECT order_date, amount
    FROM orders
    WHERE orders.user_id = u.id
    ORDER BY order_date DESC
    LIMIT 1
) o ON true;

普通 JOIN 无法在子查询中引用外表并配合 LIMIT 使用,而 LATERAL 完美解决了这一问题。

窗口函数

窗口函数在不折叠行的情况下,对与当前行相关的一组行执行计算。其核心语法为 OVER 子句。

基本语法

<窗口函数> OVER (
    [PARTITION BY <分组列>]
    [ORDER BY <排序列>]
)
  • PARTITION BY:将数据分组,窗口函数在各组内独立计算
  • ORDER BY:定义组内的排序顺序

排名函数

函数行为适用场景
ROW_NUMBER()为每一行分配唯一序号,即使值相同也不会并列需要唯一行号
RANK()相同值排名相同,后续排名跳过(有间隙)竞赛排名,有并列时跳过
DENSE_RANK()相同值排名相同,后续排名不跳过(无间隙)竞赛排名,有并列时不跳过
-- 按班级内分数排名
SELECT
    name,
    class_id,
    score,
    ROW_NUMBER() OVER (PARTITION BY class_id ORDER BY score DESC) AS row_num,
    RANK() OVER (PARTITION BY class_id ORDER BY score DESC) AS rank,
    DENSE_RANK() OVER (PARTITION BY class_id ORDER BY score DESC) AS dense_rank
FROM student;

Top-N 查询

利用窗口函数可轻松实现每组内取前 N 条记录:

WITH ranked AS (
    SELECT
        name,
        class_id,
        score,
        ROW_NUMBER() OVER (PARTITION BY class_id ORDER BY score DESC) AS rn
    FROM student
)
SELECT name, class_id, score
FROM ranked
WHERE rn <= 2;  -- 每班前两名

累计统计

窗口函数还可用于计算累计平均值等:

SELECT
    name,
    class_id,
    score,
    AVG(score) OVER (PARTITION BY class_id ORDER BY score DESC) AS running_avg
FROM student;

公用表表达式 (CTE)

CTE(Common Table Expression,公用表表达式)通过 WITH 子句定义临时命名的结果集,供后续查询引用。其核心价值在于:

  • 将复杂查询拆解为清晰的步骤,提升可读性
  • 同一中间结果可被多次复用
WITH class_avg AS (
    SELECT class_id, AVG(score) AS avg_score
    FROM student
    GROUP BY class_id
)
SELECT s.name, s.score, ca.avg_score
FROM student s
JOIN class_avg ca ON s.class_id = ca.class_id
WHERE s.score > ca.avg_score;  -- 查询成绩高于班级平均分的学生

PostgreSQL 还支持 WITH RECURSIVE 递归 CTE,用于处理树形或层级结构数据。

WITH RECURSIVE t(n) AS (
    VALUES (1)
    UNION ALL
    SELECT n + 1 FROM t WHERE n < 100
)
SELECT sum(n) FROM t;  -- 计算 1 到 100 的和

序列生成函数 (GENERATE_SERIES)

GENERATE_SERIES 是 PostgreSQL 的集合返回函数,可生成连续的数字或时间序列。

-- 生成 1 到 10 的整数序列
SELECT * FROM generate_series(1, 10);

-- 步长为 2
SELECT * FROM generate_series(1, 10, 2);

-- 生成日期序列
SELECT * FROM generate_series(
    '2023-10-01'::date,
    '2025-10-01'::date,
    '1 day'::interval
);

该函数常用于数据补齐、生成基准行或快速构造测试数据。

条件聚合 (FILTER 子句)

FILTER 子句允许在聚合函数中附加 WHERE 条件,仅对满足条件的行进行聚合。

SELECT
    class_id,
    COUNT(*) AS total,
    COUNT(*) FILTER (WHERE score >= 80) AS pass_count,
    AVG(score) FILTER (WHERE score >= 80) AS pass_avg
FROM student
GROUP BY class_id;

相较于传统的 CASE WHEN 写法,FILTER 语义更清晰、可读性更强。

聚合与分组 (GROUP BY + HAVING)

GROUP BY 将数据按指定列分组,配合聚合函数进行统计。HAVING 用于在分组后进一步过滤。

-- 按班级统计平均分
SELECT class_id, AVG(score) AS avg_score
FROM student
GROUP BY class_id;

-- 仅显示平均分大于 80 的班级
SELECT class_id, AVG(score) AS avg_score
FROM student
GROUP BY class_id
HAVING AVG(score) > 80;

API 速览

JOIN 语法

所属模块:PostgreSQL SQL 语法(SELECT 语句)

方法签名:

SELECT <列列表>
FROM <左表> [INNER | LEFT | RIGHT | FULL] JOIN <右表> ON <连接条件>
-- 或
SELECT <列列表> FROM <左表> CROSS JOIN <右表>

参数说明:

  • INNER JOIN:仅返回匹配行
  • LEFT JOIN:返回左表全部行
  • RIGHT JOIN:返回右表全部行
  • FULL JOIN:返回两表全部行
  • CROSS JOIN:返回笛卡尔积

返回值:结果集

LATERAL 子查询

所属模块:PostgreSQL FROM 子句

方法签名:

SELECT <列列表>
FROM <左表>
[LEFT | CROSS] JOIN LATERAL (<子查询>) AS <别名> ON <连接条件>

参数说明:

  • 子查询可引用位于其前的表或子查询的列
  • 通常与 LEFT JOIN 或 CROSS JOIN 配合使用

返回值:子查询对左表每一行独立计算后的结果集

窗口函数

所属模块:PostgreSQL 窗口函数(OVER 子句)

核心函数签名:

ROW_NUMBER() OVER ([PARTITION BY <列>] [ORDER BY <列>]) → bigint
RANK() OVER ([PARTITION BY <列>] [ORDER BY <列>]) → bigint
DENSE_RANK() OVER ([PARTITION BY <列>] [ORDER BY <列>]) → bigint
AVG(<列>) OVER ([PARTITION BY <列>] [ORDER BY <列>]) → numeric

参数说明:

  • PARTITION BY:可选,定义分组
  • ORDER BY:可选,定义组内排序
  • 聚合函数加 OVER 子句后即变为窗口函数

返回值:对每一行返回一个计算值,行数不变

WITH (CTE)

所属模块:PostgreSQL WITH 查询

方法签名:

WITH <cte名称> AS (
    <查询语句>
)
SELECT ... FROM <cte名称> ...;

参数说明:

  • 可定义多个 CTE,用逗号分隔
  • 支持 RECURSIVE 实现递归查询

返回值:临时命名的结果集,仅存在于当前查询中

GENERATE_SERIES

所属模块:PostgreSQL 集合返回函数

方法签名:

-- 数字序列
generate_series(start integer, stop integer [, step integer]) → setof integer
-- 时间序列
generate_series(start timestamp, stop timestamp, step interval) → setof timestamp

参数说明:

  • start:起始值
  • stop:结束值
  • step:步长,默认为 1

返回值:一组连续的值

FILTER 子句

所属模块:PostgreSQL 聚合函数

方法签名:

<聚合函数>(<列>) FILTER (WHERE <条件>)

参数说明:

  • 仅将满足 WHERE 条件的行喂给聚合函数
  • 可用于 SUM、COUNT、AVG、MIN、MAX 等聚合函数

返回值:聚合结果

Demo 简单示例

以下是一个完整的 Node.js 示例,使用 pg 库连接 PostgreSQL,演示了多表连接、窗口函数、CTE 和 GENERATE_SERIES 的用法。

运行说明

  1. 确保本地 PostgreSQL 服务运行中(版本 ≥ 11)
  2. 创建测试数据库
  3. 安装依赖:npm install pg
  4. 运行脚本:node demo.js

代码

const { Client } = require('pg');

const client = new Client({
    host: 'localhost',
    port: 5432,
    database: 'testdb',
    user: 'postgres',
    password: 'your_password',
});

async function run() {
    await client.connect();

    // 1. 建表
    await client.query(`
        DROP TABLE IF EXISTS student CASCADE;
        DROP TABLE IF EXISTS class CASCADE;

        CREATE TABLE class (
            class_id SERIAL PRIMARY KEY,
            class_name VARCHAR(50)
        );

        CREATE TABLE student (
            student_id SERIAL PRIMARY KEY,
            name VARCHAR(50),
            class_id INT REFERENCES class(class_id),
            score INT
        );
    `);

    // 2. 插入测试数据
    await client.query(`
        INSERT INTO class (class_id, class_name) VALUES
            (1, '一班'), (2, '二班'), (3, '三班');

        INSERT INTO student (name, class_id, score) VALUES
            ('Alice', 1, 95), ('Bob', 1, 82), ('Charlie', 1, 76), ('David', 1, 75),
            ('Eve', 2, 88), ('Frank', 2, 79), ('Grace', 2, 91),
            ('Henry', 3, 85), ('Ivy', 3, 90), ('Jack', 3, 78);
    `);

    console.log('=== 1. INNER JOIN ===');
    const res1 = await client.query(`
        SELECT s.name, s.score, c.class_name
        FROM student s
        INNER JOIN class c ON s.class_id = c.class_id;
    `);
    console.table(res1.rows);

    console.log('=== 2. 窗口函数:按班级排名 ===');
    const res2 = await client.query(`
        SELECT
            name,
            class_id,
            score,
            ROW_NUMBER() OVER (PARTITION BY class_id ORDER BY score DESC) AS rank
        FROM student;
    `);
    console.table(res2.rows);

    console.log('=== 3. CTE:班级平均分 ===');
    const res3 = await client.query(`
        WITH class_avg AS (
            SELECT class_id, AVG(score) AS avg_score
            FROM student
            GROUP BY class_id
        )
        SELECT s.name, s.score, ca.avg_score
        FROM student s
        JOIN class_avg ca ON s.class_id = ca.class_id
        WHERE s.score > ca.avg_score;
    `);
    console.table(res3.rows);

    console.log('=== 4. GENERATE_SERIES ===');
    const res4 = await client.query(`
        SELECT * FROM generate_series(1, 5);
    `);
    console.table(res4.rows);

    console.log('=== 5. FILTER 条件聚合 ===');
    const res5 = await client.query(`
        SELECT
            class_id,
            COUNT(*) AS total,
            COUNT(*) FILTER (WHERE score >= 80) AS pass_count
        FROM student
        GROUP BY class_id;
    `);
    console.table(res5.rows);

    await client.end();
}

run().catch(console.error);

代码说明

  • 使用 pg 库的 Client 连接 PostgreSQL
  • 依次执行建表、插入数据、各类查询
  • 窗口函数展示了 ROW_NUMBER() 在班级分组内的排名效果
  • CTE 将班级平均分计算抽离为独立步骤,主查询引用该中间结果
  • GENERATE_SERIES 快速生成测试序列
  • FILTER 子句在同一 GROUP BY 中同时统计总数和及格数

技术点总结

  • 多表连接的语法与语义
  • 窗口函数的分组排序能力
  • CTE 对复杂查询的拆解与复用
  • 序列生成函数的实用价值
  • 条件聚合的简洁写法

多语言 Demo 示例补充

以下分别提供 Go、Python、Java 三种语言的完整 Demo,功能与前述 Node.js 示例完全一致,均演示了多表连接、窗口函数、CTE 和 GENERATE_SERIES 的使用。

Go 示例

运行说明
  1. 确保本地 PostgreSQL 服务运行中(版本 ≥ 11)
  2. 创建测试数据库
  3. 安装驱动:go get github.com/lib/pq
  4. 运行脚本:go run demo.go
代码
package main

import (
    "database/sql"
    "fmt"
    "log"

    _ "github.com/lib/pq"
)

func main() {
    connStr := "postgresql://postgres:your_password@localhost/testdb?sslmode=disable"
    db, err := sql.Open("postgres", connStr)
    if err != nil {
        log.Fatal(err)
    }
    defer db.Close()

    // 1. 建表
    _, err = db.Exec(`
        DROP TABLE IF EXISTS student CASCADE;
        DROP TABLE IF EXISTS class CASCADE;

        CREATE TABLE class (
            class_id SERIAL PRIMARY KEY,
            class_name VARCHAR(50)
        );

        CREATE TABLE student (
            student_id SERIAL PRIMARY KEY,
            name VARCHAR(50),
            class_id INT REFERENCES class(class_id),
            score INT
        );
    `)
    if err != nil {
        log.Fatal(err)
    }

    // 2. 插入测试数据
    _, err = db.Exec(`
        INSERT INTO class (class_id, class_name) VALUES
            (1, '一班'), (2, '二班'), (3, '三班');

        INSERT INTO student (name, class_id, score) VALUES
            ('Alice', 1, 95), ('Bob', 1, 82), ('Charlie', 1, 76), ('David', 1, 75),
            ('Eve', 2, 88), ('Frank', 2, 79), ('Grace', 2, 91),
            ('Henry', 3, 85), ('Ivy', 3, 90), ('Jack', 3, 78);
    `)
    if err != nil {
        log.Fatal(err)
    }

    fmt.Println("=== 1. INNER JOIN ===")
    rows, err := db.Query(`
        SELECT s.name, s.score, c.class_name
        FROM student s
        INNER JOIN class c ON s.class_id = c.class_id;
    `)
    if err != nil {
        log.Fatal(err)
    }
    for rows.Next() {
        var name string
        var score int
        var className string
        rows.Scan(&name, &score, &className)
        fmt.Printf("%s | %d | %s\n", name, score, className)
    }
    rows.Close()

    fmt.Println("=== 2. 窗口函数:按班级排名 ===")
    rows, err = db.Query(`
        SELECT
            name,
            class_id,
            score,
            ROW_NUMBER() OVER (PARTITION BY class_id ORDER BY score DESC) AS rank
        FROM student;
    `)
    if err != nil {
        log.Fatal(err)
    }
    for rows.Next() {
        var name string
        var classID int
        var score int
        var rank int
        rows.Scan(&name, &classID, &score, &rank)
        fmt.Printf("%s | class %d | score %d | rank %d\n", name, classID, score, rank)
    }
    rows.Close()

    fmt.Println("=== 3. CTE:班级平均分 ===")
    rows, err = db.Query(`
        WITH class_avg AS (
            SELECT class_id, AVG(score) AS avg_score
            FROM student
            GROUP BY class_id
        )
        SELECT s.name, s.score, ca.avg_score
        FROM student s
        JOIN class_avg ca ON s.class_id = ca.class_id
        WHERE s.score > ca.avg_score;
    `)
    if err != nil {
        log.Fatal(err)
    }
    for rows.Next() {
        var name string
        var score int
        var avgScore float64
        rows.Scan(&name, &score, &avgScore)
        fmt.Printf("%s | score %d | class avg %.2f\n", name, score, avgScore)
    }
    rows.Close()

    fmt.Println("=== 4. GENERATE_SERIES ===")
    rows, err = db.Query(`SELECT * FROM generate_series(1, 5);`)
    if err != nil {
        log.Fatal(err)
    }
    for rows.Next() {
        var n int
        rows.Scan(&n)
        fmt.Println(n)
    }
    rows.Close()

    fmt.Println("=== 5. FILTER 条件聚合 ===")
    rows, err = db.Query(`
        SELECT
            class_id,
            COUNT(*) AS total,
            COUNT(*) FILTER (WHERE score >= 80) AS pass_count
        FROM student
        GROUP BY class_id;
    `)
    if err != nil {
        log.Fatal(err)
    }
    for rows.Next() {
        var classID int
        var total int
        var passCount int
        rows.Scan(&classID, &total, &passCount)
        fmt.Printf("class %d | total %d | pass %d\n", classID, total, passCount)
    }
    rows.Close()
}

Python 示例

运行说明
  1. 确保本地 PostgreSQL 服务运行中(版本 ≥ 11)
  2. 创建测试数据库
  3. 安装依赖:pip install psycopg2-binary
  4. 运行脚本:python demo.py
代码
import psycopg2

conn = psycopg2.connect(
    host='localhost',
    port=5432,
    database='testdb',
    user='postgres',
    password='your_password'
)
cur = conn.cursor()

# 1. 建表
cur.execute(```
    DROP TABLE IF EXISTS student CASCADE;
    DROP TABLE IF EXISTS class CASCADE;

    CREATE TABLE class (
        class_id SERIAL PRIMARY KEY,
        class_name VARCHAR(50)
    );

    CREATE TABLE student (
        student_id SERIAL PRIMARY KEY,
        name VARCHAR(50),
        class_id INT REFERENCES class(class_id),
        score INT
    );
```)

# 2. 插入测试数据
cur.execute(```
    INSERT INTO class (class_id, class_name) VALUES
        (1, '一班'), (2, '二班'), (3, '三班');

    INSERT INTO student (name, class_id, score) VALUES
        ('Alice', 1, 95), ('Bob', 1, 82), ('Charlie', 1, 76), ('David', 1, 75),
        ('Eve', 2, 88), ('Frank', 2, 79), ('Grace', 2, 91),
        ('Henry', 3, 85), ('Ivy', 3, 90), ('Jack', 3, 78);
```)
conn.commit()

print("=== 1. INNER JOIN ===")
cur.execute(```
    SELECT s.name, s.score, c.class_name
    FROM student s
    INNER JOIN class c ON s.class_id = c.class_id;
```)
for row in cur.fetchall():
    print(f"{row[0]} | {row[1]} | {row[2]}")

print("=== 2. 窗口函数:按班级排名 ===")
cur.execute(```
    SELECT
        name,
        class_id,
        score,
        ROW_NUMBER() OVER (PARTITION BY class_id ORDER BY score DESC) AS rank
    FROM student;
```)
for row in cur.fetchall():
    print(f"{row[0]} | class {row[1]} | score {row[2]} | rank {row[3]}")

print("=== 3. CTE:班级平均分 ===")
cur.execute(```
    WITH class_avg AS (
        SELECT class_id, AVG(score) AS avg_score
        FROM student
        GROUP BY class_id
    )
    SELECT s.name, s.score, ca.avg_score
    FROM student s
    JOIN class_avg ca ON s.class_id = ca.class_id
    WHERE s.score > ca.avg_score;
```)
for row in cur.fetchall():
    print(f"{row[0]} | score {row[1]} | class avg {row[2]:.2f}")

print("=== 4. GENERATE_SERIES ===")
cur.execute('SELECT * FROM generate_series(1, 5);')
for row in cur.fetchall():
    print(row[0])

print("=== 5. FILTER 条件聚合 ===")
cur.execute(```
    SELECT
        class_id,
        COUNT(*) AS total,
        COUNT(*) FILTER (WHERE score >= 80) AS pass_count
    FROM student
    GROUP BY class_id;
```)
for row in cur.fetchall():
    print(f"class {row[0]} | total {row[1]} | pass {row[2]}")

cur.close()
conn.close()

Java 示例

运行说明
  1. 确保本地 PostgreSQL 服务运行中(版本 ≥ 11)
  2. 创建测试数据库
  3. 使用 Maven 或手动添加 JDBC 驱动(org.postgresql:postgresql:42.6.0)
  4. 编译并运行:javac Demo.java && java Demo
代码
import java.sql.*;

public class Demo {
    public static void main(String[] args) {
        String url = "jdbc:postgresql://localhost:5432/testdb";
        String user = "postgres";
        String password = "your_password";

        try (Connection conn = DriverManager.getConnection(url, user, password)) {
            Statement stmt = conn.createStatement();

            // 1. 建表
            stmt.execute("DROP TABLE IF EXISTS student CASCADE;");
            stmt.execute("DROP TABLE IF EXISTS class CASCADE;");
            stmt.execute("CREATE TABLE class (class_id SERIAL PRIMARY KEY, class_name VARCHAR(50));");
            stmt.execute("CREATE TABLE student (student_id SERIAL PRIMARY KEY, name VARCHAR(50), class_id INT REFERENCES class(class_id), score INT);");

            // 2. 插入测试数据
            stmt.execute("INSERT INTO class (class_id, class_name) VALUES (1, '一班'), (2, '二班'), (3, '三班');");
            stmt.execute("INSERT INTO student (name, class_id, score) VALUES ('Alice', 1, 95), ('Bob', 1, 82), ('Charlie', 1, 76), ('David', 1, 75), ('Eve', 2, 88), ('Frank', 2, 79), ('Grace', 2, 91), ('Henry', 3, 85), ('Ivy', 3, 90), ('Jack', 3, 78);");

            System.out.println("=== 1. INNER JOIN ===");
            ResultSet rs = stmt.executeQuery("SELECT s.name, s.score, c.class_name FROM student s INNER JOIN class c ON s.class_id = c.class_id;");
            while (rs.next()) {
                System.out.printf("%s | %d | %s%n", rs.getString(1), rs.getInt(2), rs.getString(3));
            }

            System.out.println("=== 2. 窗口函数:按班级排名 ===");
            rs = stmt.executeQuery("SELECT name, class_id, score, ROW_NUMBER() OVER (PARTITION BY class_id ORDER BY score DESC) AS rank FROM student;");
            while (rs.next()) {
                System.out.printf("%s | class %d | score %d | rank %d%n", rs.getString(1), rs.getInt(2), rs.getInt(3), rs.getInt(4));
            }

            System.out.println("=== 3. CTE:班级平均分 ===");
            rs = stmt.executeQuery("WITH class_avg AS (SELECT class_id, AVG(score) AS avg_score FROM student GROUP BY class_id) SELECT s.name, s.score, ca.avg_score FROM student s JOIN class_avg ca ON s.class_id = ca.class_id WHERE s.score > ca.avg_score;");
            while (rs.next()) {
                System.out.printf("%s | score %d | class avg %.2f%n", rs.getString(1), rs.getInt(2), rs.getDouble(3));
            }

            System.out.println("=== 4. GENERATE_SERIES ===");
            rs = stmt.executeQuery("SELECT * FROM generate_series(1, 5);");
            while (rs.next()) {
                System.out.println(rs.getInt(1));
            }

            System.out.println("=== 5. FILTER 条件聚合 ===");
            rs = stmt.executeQuery("SELECT class_id, COUNT(*) AS total, COUNT(*) FILTER (WHERE score >= 80) AS pass_count FROM student GROUP BY class_id;");
            while (rs.next()) {
                System.out.printf("class %d | total %d | pass %d%n", rs.getInt(1), rs.getInt(2), rs.getInt(3));
            }

        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

技术点总结

  • Go 使用 database/sql + lib/pq,Python 使用 psycopg2,Java 使用 JDBC,均实现了相同的 PostgreSQL 高级查询功能
  • 所有示例均涵盖:多表连接、窗口函数、CTE、序列生成、条件聚合
  • 各语言代码风格遵循各自生态最佳实践,错误处理完整
  • 可直接复制运行,需替换数据库连接参数

项目难点与解决方案

核心难点

  • 连接算法选择:优化器如何在海量数据场景下正确选择 Nested Loop、Hash Join 或 Merge Join,直接影响查询性能
  • 窗口函数的理解成本:PARTITION BY 与 ORDER BY 的语义、排名函数的差异(RANK vs DENSE_RANK)容易混淆
  • LATERAL 的适用边界:何时使用 LATERAL 而非普通 JOIN 或子查询,需要清晰的场景判断

解决方案

  • 通过 EXPLAIN ANALYZE 分析执行计划,观察优化器选择的连接算法,针对性调整索引或统计信息
  • 使用具体数据示例对比 ROW_NUMBER()、RANK()、DENSE_RANK() 的输出差异,强化理解
  • 将 LATERAL 与普通 JOIN 的写法并列对比,突出其在“逐行依赖子查询 + LIMIT”场景下的不可替代性

广度

本文覆盖了 PostgreSQL 高级查询的六大核心模块:多表连接(含三种算法)、横向子查询、窗口函数、CTE、序列生成、条件聚合,基本涵盖了日常复杂查询的主要技术栈。

深度

对每种技术不仅给出语法,还深入解析了:

  • 连接算法的适用场景与性能差异
  • 窗口函数排名函数的细微区别
  • CTE 的递归能力与执行机制
  • FILTER 与传统 CASE WHEN 的写法对比

复杂度

各模块之间层层递进:

  • 从基础的 JOIN 类型 → 底层算法 → LATERAL 高级关联
  • 从普通聚合 → 窗口函数的不折叠分析
  • 从单层查询 → CTE 多层拆解
  • 从静态数据 → GENERATE_SERIES 动态生成

官方文档

官方文档

参考链接

总结

本文系统梳理了 PostgreSQL 高级查询的六大核心模块:多表连接(涵盖 INNER、LEFT、RIGHT、FULL、CROSS JOIN 及三种底层算法 Nested Loop、Hash Join、Merge Join)、横向子查询 LATERAL、窗口函数(ROW_NUMBER、RANK、DENSE_RANK 及 PARTITION BY/ORDER BY 语义)、公用表表达式 CTE(含递归 WITH RECURSIVE)、序列生成函数 GENERATE_SERIES 以及条件聚合 FILTER 子句。

通过对比表格、Mermaid 图表和完整的 Node.js Demo,展示了各技术点的语法、适用场景与性能考量,为编写复杂分析查询提供了系统性的技术参考。

PostgreSQL列举某区间内所有数据 一、简介 PostgreSQL 中有一个很有用处的内置函数generate_series,可以按不同的规则产生一系列的填充数据。 二、语法 函数 参数类型 返回类型 描述 generate_series(start, stop) int 或 bigint setof int 或 setof bigint(与参数类型相同) 生成一个数值序列,从start... 阅读详情

相关推荐

PostgreSQL高级特性解析:窗口函数与CTE

窗口函数是SQL标准中的一种特殊函数,它可以在不合并行的情况下对每行进行计算,同时能够访问同一查询结果集中其他行的数据。与传统的聚合函数不同,窗口函数不会将多行合并为一行,而是为每一行返回一个值。 窗口函数的核心优势在于它能够在保持原始行数的同时,对数据进行复杂的分析计算。这使得我们可以在单个查询中同时获取详细数据和聚合信息。

九天的专栏 987

cross join lateral 表连接

在 PostgreSQL 中,表连接是查询构建的核心。通过连接,可以将多张表的数据组合在一起,以生成所需的结果。是 PostgreSQL 中一个非常强大的功能,它允许对左表的每一行执行右侧的子查询或表达式。不同于普通的JOIN操作,允许右侧的子查询直接引用左表中的列,从而在每一行的上下文中执行子查询。和LEFT JOIN各有其独特的应用场景和功能。在选择使用哪种连接方式时,需要根据实际需求进行权衡。提供了更灵活的查询方式,适合复杂的每行计算和数据处理;而LEFT JOIN则更适合数据合并和关联操作。

m0_72560900的博客 2361

PostgreSQL 18 高级查询优化:5个常用窗口函数与CTE实战解析

本文深入解析PostgreSQL 18中5个常用窗口函数(ROW_NUMBER、RANK、LAG等)与CTE的高级应用,结合实战案例展示如何优化复杂查询性能。文章详细介绍了PostgreSQL语法优化原理,包括分区并行处理、内存使用优化等新特性,帮助开发者提升数据分析效率。

weixin_42662293的博客 275

oracle数据库

加油 希望将来的你更好

qq_62553456的博客 1152

PostgreSQL 高级特性:窗口函数与分区表的应用

窗口函数,也称为分析函数,是 PostgreSQL 中用于在查询结果集的 “窗口” 范围内进行计算的函数。与普通聚合函数(如 SUM、AVG 等)不同,窗口函数不会将多行数据聚合为一行,而是在保留每一行数据的基础上,基于定义的窗口范围进行计算。每个窗口都可以包含一组行,窗口函数会针对每一行数据,在其对应的窗口范围内进行计算,从而生成新的结果列。窗口函数的语法结构通常如下:<窗口函数> OVER (PARTITION BY <列名> ORDER BY <列名> <窗口框架>)

数字魔方操控师的博客 1347

如何优化 PostgreSQL 中对于自关联表的查询?

在 PostgreSQL 中,当处理自关联表(即一个表与自身进行关联)的查询时,优化查询性能可能会具有一定的挑战性,但通过合理的策略和技巧,可以显著提高查询的效率。

技术笔记 1643

PostgreSQL WITH 子句(CTE)详解

PostgreSQL 的WITH子句(又称公用表表达式,Common Table Expression,CTE)是编写复杂SQL查询的强大工具。它允许创建临时命名结果集,可以在主查询中多次引用,显著提高复杂查询的可读性和可维护性。WITH),提高可读性:将复杂查询分解为逻辑模块代码重用:避免重复子查询递归能力:轻松处理层级数据数据修改:在单一语句中实现复杂操作优化潜力:为查询优化器提供更多信息。

✨ 欢迎来到【Seal ^_^ 的优快云博客】!✨ 9446

PostgreSQL里的子查询和CTE居然在性能上“掐架”?到底该站哪边?

PostgreSQL中子查询与CTE的性能差异主要体现在执行机制和适用场景上。子查询通过优化器融合可转换为JOIN操作,利用索引提升性能,适合简单逻辑和小结果集过滤。而CTE默认物化为临时表,避免重复计算但增加I/O开销,适合多次引用或递归查询(如层级遍历)。PostgreSQL 12+支持NOT MATERIALIZED选项可减少CTE物化开销。实际选择需权衡计算次数、数据量及索引利用,递归查询是CTE的独占场景。

https://blog.cmdragon.cn/ 1177

pgrust 高级 SQL 功能指南:游标、CTE 与窗口函数实战

如果你正在寻找一款既能兼容 PostgreSQL、又能尝鲜 Rust 高性能数据库的 SQL 高级功能指南,那么 pgrust 值得关注。pgrust 是用 Rust 重新实现的 PostgreSQL,目标兼容 Postgres 18.3,已通过超过 46,000 条回归查询测试,并能直接挂载现有 Postgres 18.3 数据目录启动。本文聚焦三大高频高级 SQL 特性——游标(Cursor)

gitblog_00145的博客 1063

【PostgreSQL教程】PostgreSQL 高级篇之连接(JOIN)

PostgreSQL JOIN 子句用于把来自两个或多个表的行结合起来,基于这些表之间的共同字段。 在 PostgreSQL 中,**JOIN** 有五种连接类型: - CROSS JOIN :交叉连接 - INNER JOIN:内连接 - LEFT OUTER JOIN:左外连接 - RIGHT OUTER JOIN:右外连接 - FULL OUTER JOIN:全外连接

No8g攻城狮的博客 1096

PostgreSQL:高级SQL特性

在数据驱动的时代,SQL早已突破"增删改查"的简单定位,成为数据处理领域的瑞士军刀。作为全球最先进的开源关系型数据库,PostgreSQL 15(2023年最新版本)将SQL的表达能力推向了新的高度——其内置的窗口函数可进行复杂数据分析,递归CTE能轻松处理树形结构,LATERAL JOIN颠覆传统子查询模式,而键集分页技术更是将海量数据访问效率提升十倍级。

在技术的广袤天地里,本博客如精准罗盘。剖析前沿科技,深掘代码奥秘,以精炼笔触,带您穿越复杂技术迷宫,速达知识彼岸。 3280

介绍PostgreSQL CTE(common table expressions)

介绍PostgreSQL CTE(common table expressions) 本文我们学习如何使用PostgreSQL CTE(common table expressions)简化复杂查询。 介绍 common table expressions PostgreSQL CTE(common table expressions) 是临时结果,可以在其他SQL中引用,如SELECT, INS...

neweastsun的专栏 6929

【postgresql 基础入门】CTE(common table expression)让你轻松写出复杂多变的嵌套SQL,同时SQL也可以递归树的遍历查询

在一些分析型业务中,通常会遇到一些非常复杂的SQL语句,多个子查询的联合,此时可以使用 common table expression (CTE) 方式的SQL写法,来让SQL更加简化和易于理解,本文就来分享一下postgresql中的CTE语言的写法,以及使用案例。

一个追逐梦想的码农 3502

基于 PostgreSQL Exercises 数据集的 Peewee 查询实战:从基础 SQL 到窗口函数与递归 CTE 的完整对照指南

本文是一份以 Peewee ORM 为主体的查询实战指南,全部练习取自 PostgreSQL Exercises 网站的 club 示例数据集(members / facilities / bookings 三张表)。读者将掌握 Peewee 中 select/where 条件构造、字符串与日期过滤、JOIN 与自连接、子查询、insert/update/delete 各类 DML、GROUP B

gitblog_01052的博客 458

Win11笔记本BIOS设置避坑大全:Secure Boot、RST、UEFI引导,一次讲清

本文详细解析了Win11笔记本BIOS设置中的关键问题,包括Secure Boot、Intel RST和UEFI引导的配置方法,帮助用户避开双系统安装中的常见陷阱。特别针对不同品牌笔记本的BIOS界面差异,提供了实用的操作指南和优化建议,确保系统兼容性和启动效率。

weixin_29251131的博客 690

Claude Code 完整使用教程(2026最新版)

《Claude Code 完整使用教程(2026最新版)》摘要: Claude Code 是 Anthropic 公司推出的智能编程工具,通过自然语言对话实现代码编写、调试和重构。本教程基于 v2.1.101 版本,涵盖安装配置、核心功能及最新特性。主要内容包括: 多平台安装方式(macOS/Linux/Windows) 桌面应用专属功能如并行会话、定时任务和语音模式 模型选择指南(Sonnet/Opus/Haiku) 快速入门示例:5分钟创建Node.js项目 核心功能:代码读写、命令执行、Git集成等

程序猿_三产 2万+

PostgreSQL 高级查询:子查询、CTE、窗口函数、聚合函数实战

子查询、CTE、窗口函数和高级聚合函数是 PostgreSQL 高级查询的核心工具。它们能够极大地提升你处理复杂数据查询的能力,让你的 SQL 代码更加简洁、高效和易于维护。

wuzuhuzhanlang的专栏 262

OK3588-C开发板Forlinx Desktop系统烧录与多媒体功能实战

本文详细介绍了OK3588-C开发板Forlinx Desktop系统的烧录步骤与多媒体功能实战。从硬件准备到软件工具配置,逐步解析RKDevTool烧录技巧,并深入测试摄像头、视频编解码及音频功能,帮助开发者快速掌握RK3588芯片的强大性能与Ubuntu系统的优化应用。

weixin_29190169的博客 419

MySQL 8.0 与 PostgreSQL 15 对比:5 类复杂 SQL 查询的语法差异与性能实测

本文深度对比了MySQL 8.0与PostgreSQL 15在5类复杂SQL查询中的语法差异与性能表现,包括全称量词查询、窗口函数、递归查询、JSON数据处理及多表连接优化。通过实测数据揭示两者在高级查询功能上的优劣,为数据库选型提供实用参考。特别针对SQL性能优化进行了详细分析,帮助开发者提升查询效率。

weixin_34214500的博客 427

直流电源EMI滤波器设计实战:从器件选型到PCB布局的完整指南

本文提供了一份直流电源EMI滤波器的完整实战设计指南。针对EMI噪声超标问题,文章深入剖析了差模与共模噪声的抑制原理,详细讲解了从电路拓扑、核心器件(如X/Y电容、共模电感)的选型计算,到决定性能成败的PCB布局艺术。重点强调了低阻抗路径、干净接地及输入输出隔离等关键布局原则,并分享了常见的调试坑点与排查方法,旨在帮助硬件工程师设计出真正有效的滤波器,确保电源系统通过EMC测试。

q9r0s的博客 644

智驾芯片新天团!NVIDIA Thor vs 高通8797 vs 蔚来神玑NX9031,2025-2026谁能称王?

应用场景推荐芯片理由高阶NOA + L4 Robotaxi千TOPS以上,满足BEV+TransformerL2+/L3级别NOAThor-U / 高通8797算力适中,功耗控制佳,生态成熟经济型城市NOAThor-Z / S性价比均衡,适合前装普及自研平台一体化NX9031华为之外最强自研整合方案📌 TOPS不再是天花板,GPU、带宽、功耗、上市时间和车企绑定都是决胜关键📌 Thor系列仍然统治高端,但国产神经元开始觉醒📌 智驾芯片的选择,从“强”走向“适合场景”

weixin_43199439的博客 2657

PostgreSQL15——通用表表达式(CTE)

通用表表达式(Common Table Expression、CTE)是一个临时的查询结果或者临时表,可以在其他 SELECT、INSERT、UPDATE 以及 DELETE 语句中使用。通用表表达式只在当前语句中有效,类似于子查询。

吴声子夜歌的博客 1292

GameFramework框架详解之 Network网络框架

NetworkManager只负责维护NetworkChannel,实际的有种我们简历网络连接的也是NetworkChannel作为一个网路频道,这样管理起来比较方便,比如:一个游戏可能有多个网络连接,那么我们建立多个网络频道即可,或者当我们一个网络频道断线后,我们可以直接再新建一个,防止数据混乱。/// /// 网络管理器。/// /// 网络频道。/// 用户自定义网络错误事件。/// 检查是否存在网络频道。/// 获取网络频道。/// 获取所有网络频道。

TxNet 3369

【PostgreSQL】提高篇——公用表表达式(CTE)和窗口函数

公用表表达式(Common Table Expression,CTE)是一种在 SQL 查询中定义临时结果集的方式,可以在主查询中多次引用。CTE 可以提高查询的可读性和结构性,尤其是在处理复杂的查询时。使用 CTE,可以避免使用嵌套查询,从而使 SQL 代码更清晰。窗口函数是一种在结果集的每一行上执行计算的函数,它允许我们在不分组的情况下进行聚合计算。窗口函数通常用于计算排名、移动平均、累计和等。与普通的聚合函数不同,窗口函数不会减少结果集的行数。

thinking_chou的专栏 1907

华为OD机试真题 2026-9-2【查找幸运数】

华为OD机试真题 新系统【查找幸运数】,提供C语言、Java、C++、Python、Go、JavaScript源码实现,并提供完整思路讲解,以及在线OJ模拟做题。华为OD机试真题 【查找幸运数】多语言题解。

qq_45776114的博客 506

Visual Studio调试技巧与实用方法总结(实战经验分享)

本文根据多年的项目实战经验,详细介绍一些Visual Studio常用的高效调试手段与技巧。

dvlinker的技术专栏 3万+
上一篇: PostgreSQL笔记14:子查询与嵌套查询的完整分类、执行模型与性能优化
下一篇: PostgreSQL笔记16: 索引基础——概念、类型、创建与最佳实践
Wang's Blog
博客等级 码龄12年 7438粉丝 2422原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Wang's Blog

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值