未记录事实核验 · 状态:维护中

记录仅说明核验时间与维护状态,不保证适用于所有环境;使用前请留意正文中的版本、适用条件与风险提示。

warning 适用边界与维护说明

本节适合有SQL与查询优化基础的读者设计多表JOIN、子查询改写和执行计划对照实验,不是可直接连续执行的Presto脚本:当前多处建表仍含双冒号类型转换,需改为目标环境支持的CAST语法;算法图混合连接算法与分发方式,Merge Join、REPLICATED及重排序、动态过滤会话参数未注明对应实现与版本,不能据此认定同一Presto环境均支持。练习需预先配置memory.default,并按数组与内存限制缩减造数规模;customer_totals需自行提供。固定表大小阈值、“推荐”改写及SQL书写顺序不保证性能优势,应结合构建侧大小、统计信息、连接器和实际计划比较。广播预过滤示例的连接条件与前一示例不同,不能直接用于等价性能对照;Memory示例仅演示日期过滤,层次自连接仅展示最多两级上级,role_type仅为人数阈值标签。本次仅作全文结构审阅,未执行SQL或进行外部事实核验。

Presto JOIN优化与子查询 - 复杂查询性能调优实战

abstract 📋 本节概览

  • 🎯 学习目标:掌握Presto中JOIN操作和子查询的高级优化技术和最佳实践
  • 📚 前置知识:查询优化 和 SQL JOIN/子查询基础
  • ⏱️ 预计用时:120分钟

🔗 JOIN优化架构

📊 JOIN算法类型

%%{init: {"theme":"base","themeVariables":{"fontFamily":"PingFang SC, -apple-system, Source Han Serif SC, serif","fontSize":"16px","background":"#FDFBF5","primaryColor":"#FDFBF5","primaryTextColor":"#2D2A26","primaryBorderColor":"#6E6A60","secondaryColor":"#E8EFE8","secondaryBorderColor":"#8FA890","secondaryTextColor":"#2D2A26","tertiaryColor":"#FDFBF5","tertiaryBorderColor":"#9A9488","tertiaryTextColor":"#6E6A60","lineColor":"#6E6A60","textColor":"#2D2A26","mainBkg":"#FDFBF5","nodeBorder":"#2D5A3D","clusterBkg":"#F3EEDF","clusterBorder":"#9A9488","edgeLabelBackground":"#FDFBF5"},"flowchart":{"htmlLabels":true,"curve":"basis","padding":20,"nodeSpacing":60,"rankSpacing":70,"useMaxWidth":false},"themeCSS":".node rect, .node polygon, .node circle { stroke-width: 1.5px; } .edgePath .path { stroke-width: 1.5px; } .cluster-label .nodeLabel, .cluster-label foreignObject { color: #2D2A26 !important; } .edgeLabel, .edgeLabel foreignObject { color: #2D2A26 !important; background-color: #FDFBF5; } .edgeLabel rect { fill: #FDFBF5; }"}}%%
flowchart TB
    subgraph "Presto JOIN算法体系"
        A[JOIN查询<br/>🤝 多表关联]
        subgraph "JOIN算法类型"
            B[Hash Join<br/>🔑 哈希连接]
            C[Merge Join<br/>📊 归并连接]
            D[Nested Loop Join<br/>🔄 嵌套循环连接]
            E[Broadcast Join<br/>📡 广播连接]
        end
        subgraph "分发策略"
            F[BROADCAST<br/>📡 小表广播]
            G[PARTITIONED<br/>📊 分区重分布]
            H[REPLICATED<br/>🔄 数据复制]
            I[AUTOMATIC<br/>🎯 自动选择]
        end
        subgraph "优化技术"
            J[Dynamic Filtering<br/>⚡ 动态过滤]
            K[Join Reordering<br/>🔄 连接重排序]
            L[Predicate Pushdown<br/>⬇️ 谓词下推]
            M[Partition Pruning<br/>✂️ 分区裁剪]
        end
    A --> B
    A --> C
    A --> D
    A --> E
    B --> F
    C --> G
    D --> H
    E --> I
    F --> J
    G --> K
    H --> L
    I --> M
    style A fill:#e3f2fd
    style B fill:#f3e5f5
    style F fill:#e8f5e8
    style J fill:#fff3e0

    end

🎯 JOIN性能决策矩阵

JOIN类型 小表(<1GB) 中表(1-10GB) 大表(>10GB) 最佳策略
Inner Join Broadcast Partitioned Partitioned 基于表大小自动选择
Left Join Broadcast Partitioned Partitioned 保持左表分布
Right Join Partitioned Partitioned Partitioned 保持右表分布
Full Outer Join Partitioned Partitioned Partitioned 双表重分布
Cross Join 避免使用 谨慎使用 避免使用 添加适当JOIN条件

🔑 Hash Join优化

📊 Hash Join基础配置

-- 设置JOIN分发策略
SET SESSION join_distribution_type = 'AUTOMATIC';  -- BROADCAST, PARTITIONED, AUTOMATIC

-- 创建测试数据用于JOIN优化演示
CREATE TABLE memory.default.customers_large AS
SELECT 
    customer_id,
    'Customer_' || customer_id::VARCHAR as customer_name,
    CAST(random() * 100000 AS DECIMAL(10,2)) as lifetime_value,
    CASE WHEN random() < 0.1 THEN 'VIP' 
         WHEN random() < 0.3 THEN 'Premium' 
         ELSE 'Standard' END as customer_type
FROM (
    SELECT row_number() OVER () as customer_id
    FROM UNNEST(SEQUENCE(1, 1000000)) t(x)
);

CREATE TABLE memory.default.orders_large AS
SELECT 
    order_id,
    CAST(random() * 1000000 + 1 AS BIGINT) as customer_id,
    (random() * 1000 + 10)::DECIMAL(10,2) as order_amount,
    DATE '2024-01-01' + (random() * 90)::INTEGER * INTERVAL '1' DAY as order_date
FROM (
    SELECT row_number() OVER () as order_id
    FROM UNNEST(SEQUENCE(1, 5000000)) t(x)
);

CREATE TABLE memory.default.products_small AS
SELECT 
    product_id,
    'Product_' || product_id::VARCHAR as product_name,
    (random() * 100 + 10)::DECIMAL(8,2) as unit_price
FROM (
    SELECT row_number() OVER () as product_id
    FROM UNNEST(SEQUENCE(1, 1000)) t(x)
);

🎯 Broadcast Join优化

-- 小表JOIN大表:使用Broadcast策略
SET SESSION join_distribution_type = 'BROADCAST';

EXPLAIN (TYPE DISTRIBUTED)
SELECT 
    c.customer_name,
    c.customer_type,
    COUNT(o.order_id) as order_count,
    SUM(o.order_amount) as total_amount
FROM memory.default.customers_large c
JOIN memory.default.orders_large o ON c.customer_id = o.customer_id
WHERE c.customer_type = 'VIP'  -- 过滤条件减少广播数据量
GROUP BY c.customer_name, c.customer_type;

-- 查看实际执行效果
EXPLAIN ANALYZE
SELECT 
    p.product_name,
    COUNT(DISTINCT o.customer_id) as unique_customers,
    SUM(o.order_amount) as total_sales
FROM memory.default.products_small p  -- 小表
JOIN memory.default.orders_large o ON p.product_id % 1000 = o.customer_id % 1000
GROUP BY p.product_name
ORDER BY total_sales DESC
LIMIT 100;

-- 优化广播JOIN的技巧
-- 1. 预过滤小表数据
WITH filtered_products AS (
    SELECT product_id, product_name
    FROM memory.default.products_small
    WHERE unit_price > 50  -- 预先过滤减少广播数据
)
SELECT 
    p.product_name,
    COUNT(o.order_id) as order_count
FROM filtered_products p
JOIN memory.default.orders_large o ON p.product_id = o.customer_id % 1000
GROUP BY p.product_name;

📊 Partitioned Join优化

-- 大表JOIN大表:使用Partitioned策略
SET SESSION join_distribution_type = 'PARTITIONED';

EXPLAIN (TYPE DISTRIBUTED)
SELECT 
    c.customer_type,
    DATE_TRUNC('month', o.order_date) as order_month,
    COUNT(*) as order_count,
    AVG(o.order_amount) as avg_amount
FROM memory.default.customers_large c
JOIN memory.default.orders_large o ON c.customer_id = o.customer_id
WHERE o.order_date >= DATE '2024-02-01'
GROUP BY c.customer_type, DATE_TRUNC('month', o.order_date)
ORDER BY order_month, c.customer_type;

-- 优化分区JOIN的技巧
-- 1. 使用相同的分区键
-- 2. 预聚合减少JOIN数据量
WITH customer_summary AS (
    SELECT 
        customer_id,
        customer_type,
        lifetime_value
    FROM memory.default.customers_large
    WHERE customer_type IN ('VIP', 'Premium')  -- 预过滤
),
order_summary AS (
    SELECT 
        customer_id,
        COUNT(*) as order_count,
        SUM(order_amount) as total_spent,
        MAX(order_date) as last_order_date
    FROM memory.default.orders_large
    WHERE order_date >= DATE '2024-01-01'
    GROUP BY customer_id  -- 预聚合
)
SELECT 
    cs.customer_type,
    COUNT(*) as active_customers,
    AVG(os.order_count) as avg_orders_per_customer,
    AVG(os.total_spent) as avg_spent_per_customer
FROM customer_summary cs
JOIN order_summary os ON cs.customer_id = os.customer_id
GROUP BY cs.customer_type;

🔄 JOIN重排序优化

📊 多表JOIN顺序优化

-- 创建不同大小的表用于JOIN顺序测试
CREATE TABLE memory.default.table_small AS
SELECT 
    id,
    'Small_' || id::VARCHAR as name
FROM (SELECT row_number() OVER () as id FROM UNNEST(SEQUENCE(1, 1000)) t(x));

CREATE TABLE memory.default.table_medium AS
SELECT 
    id,
    'Medium_' || id::VARCHAR as name
FROM (SELECT row_number() OVER () as id FROM UNNEST(SEQUENCE(1, 100000)) t(x));

CREATE TABLE memory.default.table_large AS
SELECT 
    id,
    'Large_' || id::VARCHAR as name
FROM (SELECT row_number() OVER () as id FROM UNNEST(SEQUENCE(1, 1000000)) t(x));

-- 多表JOIN:让优化器自动选择最佳顺序
SET SESSION optimizer.join_reordering_strategy = 'COST_BASED';

EXPLAIN (TYPE DISTRIBUTED)
SELECT 
    s.name as small_name,
    m.name as medium_name,
    l.name as large_name
FROM memory.default.table_large l      -- 大表
JOIN memory.default.table_medium m ON l.id = m.id * 10  -- 中表
JOIN memory.default.table_small s ON m.id = s.id * 100  -- 小表
WHERE s.id <= 100;  -- 选择性高的过滤条件

-- 手动优化JOIN顺序:从小表开始
SELECT 
    s.name as small_name,
    m.name as medium_name,
    l.name as large_name
FROM memory.default.table_small s      -- 从最小的表开始
JOIN memory.default.table_medium m ON s.id * 100 = m.id
JOIN memory.default.table_large l ON m.id * 10 = l.id
WHERE s.id <= 100;

🎯 星型模式JOIN优化

-- 典型的星型模式:事实表JOIN多个维度表
-- 创建星型模式测试数据
CREATE TABLE memory.default.fact_sales AS
SELECT 
    sale_id,
    (random() * 10000 + 1)::BIGINT as customer_id,
    (random() * 1000 + 1)::BIGINT as product_id,
    CAST(random() * 100 + 1 AS BIGINT) as store_id,
    (random() * 1000 + 10)::DECIMAL(10,2) as amount,
    DATE '2024-01-01' + (random() * 90)::INTEGER * INTERVAL '1' DAY as sale_date
FROM (
    SELECT row_number() OVER () as sale_id
    FROM UNNEST(SEQUENCE(1, 2000000)) t(x)
);

CREATE TABLE memory.default.dim_customers AS
SELECT 
    customer_id,
    'Customer_' || customer_id::VARCHAR as customer_name,
    CASE WHEN random() < 0.2 THEN 'Premium' ELSE 'Standard' END as tier
FROM (
    SELECT row_number() OVER () as customer_id
    FROM UNNEST(SEQUENCE(1, 10000)) t(x)
);

CREATE TABLE memory.default.dim_products AS
SELECT 
    product_id,
    'Product_' || product_id::VARCHAR as product_name,
    'Category_' || CAST(product_id % 10 AS VARCHAR) as category
FROM (
    SELECT row_number() OVER () as product_id
    FROM UNNEST(SEQUENCE(1, 1000)) t(x)
);

CREATE TABLE memory.default.dim_stores AS
SELECT 
    store_id,
    'Store_' || CAST(store_id AS VARCHAR) as store_name,
    'Region_' || CAST(store_id % 5 AS VARCHAR) as region
FROM (
    SELECT row_number() OVER () as store_id
    FROM UNNEST(SEQUENCE(1, 100)) t(x)
);

-- 星型模式查询优化
SET SESSION join_distribution_type = 'AUTOMATIC';
SET SESSION enable_dynamic_filtering = true;

EXPLAIN ANALYZE
SELECT 
    dc.tier,
    dp.category,
    ds.region,
    COUNT(*) as sale_count,
    SUM(fs.amount) as total_amount,
    AVG(fs.amount) as avg_amount
FROM memory.default.fact_sales fs
JOIN memory.default.dim_customers dc ON fs.customer_id = dc.customer_id
JOIN memory.default.dim_products dp ON fs.product_id = dp.product_id  
JOIN memory.default.dim_stores ds ON fs.store_id = ds.store_id
WHERE dc.tier = 'Premium'
  AND dp.category IN ('Category_1', 'Category_2')
  AND fs.sale_date >= DATE '2024-02-01'
GROUP BY dc.tier, dp.category, ds.region
ORDER BY total_amount DESC;

🔍 子查询优化

📊 相关子查询优化

-- 创建用于子查询优化的测试数据
CREATE TABLE memory.default.employees AS
SELECT 
    emp_id,
    'Employee_' || emp_id::VARCHAR as emp_name,
    'Dept_' || CAST(emp_id % 10 AS VARCHAR) as department,
    CAST(random() * 50000 + 30000 AS DECIMAL(8,2)) as salary,
    DATE '2020-01-01' + CAST(random() * 1460 AS INTEGER) * INTERVAL '1' DAY as hire_date
FROM (
    SELECT row_number() OVER () as emp_id
    FROM UNNEST(SEQUENCE(1, 100000)) t(x)
);

-- 相关子查询示例:查找高于部门平均薪资的员工
-- 对照版本:相关子查询;性能取决于优化器改写及实际执行计划
SELECT 
    emp_name,
    department,
    salary,
    (SELECT AVG(salary) FROM memory.default.employees e2 
     WHERE e2.department = e1.department) as dept_avg_salary
FROM memory.default.employees e1
WHERE salary > (
    SELECT AVG(salary) 
    FROM memory.default.employees e2 
    WHERE e2.department = e1.department
)
ORDER BY department, salary DESC;

-- 优化版本:使用窗口函数替代相关子查询
SELECT 
    emp_name,
    department,
    salary,
    dept_avg_salary
FROM (
    SELECT 
        emp_name,
        department,
        salary,
        AVG(salary) OVER (PARTITION BY department) as dept_avg_salary
    FROM memory.default.employees
) t
WHERE salary > dept_avg_salary
ORDER BY department, salary DESC;

-- 进一步优化:使用JOIN替代相关子查询
WITH dept_avg AS (
    SELECT 
        department,
        AVG(salary) as avg_salary
    FROM memory.default.employees
    GROUP BY department
)
SELECT 
    e.emp_name,
    e.department,
    e.salary,
    da.avg_salary as dept_avg_salary
FROM memory.default.employees e
JOIN dept_avg da ON e.department = da.department
WHERE e.salary > da.avg_salary
ORDER BY e.department, e.salary DESC;

🎯 EXISTS/IN子查询优化

-- 创建订单和订单项目数据
CREATE TABLE memory.default.orders AS
SELECT 
    order_id,
    (random() * 100000 + 1)::BIGINT as customer_id,
    DATE '2024-01-01' + (random() * 90)::INTEGER * INTERVAL '1' DAY as order_date,
    CASE WHEN random() < 0.1 THEN 'CANCELLED' ELSE 'COMPLETED' END as status
FROM (
    SELECT row_number() OVER () as order_id
    FROM UNNEST(SEQUENCE(1, 500000)) t(x)
);

CREATE TABLE memory.default.order_items AS
SELECT 
    item_id,
    CAST(random() * 500000 + 1 AS BIGINT) as order_id,
    (random() * 1000 + 1)::BIGINT as product_id,
    CAST(random() * 5 + 1 AS INTEGER) as quantity,
    (random() * 100 + 10)::DECIMAL(8,2) as unit_price
FROM (
    SELECT row_number() OVER () as item_id
    FROM UNNEST(SEQUENCE(1, 2000000)) t(x)
);

-- EXISTS子查询优化
-- 查找有订单项目的订单
-- 使用EXISTS(推荐)
EXPLAIN ANALYZE
SELECT 
    order_id,
    customer_id,
    order_date
FROM memory.default.orders o
WHERE EXISTS (
    SELECT 1 
    FROM memory.default.order_items oi 
    WHERE oi.order_id = o.order_id
)
  AND status = 'COMPLETED';

-- 使用IN子查询(注意性能)
EXPLAIN ANALYZE
SELECT 
    order_id,
    customer_id,
    order_date
FROM memory.default.orders o
WHERE order_id IN (
    SELECT order_id 
    FROM memory.default.order_items
)
  AND status = 'COMPLETED';

-- 使用JOIN加DISTINCT替代EXISTS作为对照方案;是否更高效需比较实际执行计划与资源消耗
EXPLAIN ANALYZE
SELECT DISTINCT
    o.order_id,
    o.customer_id,
    o.order_date
FROM memory.default.orders o
JOIN memory.default.order_items oi ON o.order_id = oi.order_id
WHERE o.status = 'COMPLETED';

-- NOT EXISTS優化
-- 查找没有订单项目的订单
SELECT 
    order_id,
    customer_id,
    order_date
FROM memory.default.orders o
WHERE NOT EXISTS (
    SELECT 1 
    FROM memory.default.order_items oi 
    WHERE oi.order_id = o.order_id
)
  AND status = 'COMPLETED';

-- 使用LEFT JOIN替代NOT EXISTS
SELECT 
    o.order_id,
    o.customer_id,
    o.order_date
FROM memory.default.orders o
LEFT JOIN memory.default.order_items oi ON o.order_id = oi.order_id
WHERE oi.order_id IS NULL
  AND o.status = 'COMPLETED';

📊 标量子查询优化

-- 标量子查询优化示例
-- 未优化:多次执行标量子查询
SELECT 
    emp_name,
    department,
    salary,
    (SELECT AVG(salary) FROM memory.default.employees) as company_avg,
    (SELECT MAX(salary) FROM memory.default.employees WHERE department = e.department) as dept_max,
    (SELECT COUNT(*) FROM memory.default.employees WHERE department = e.department) as dept_count
FROM memory.default.employees e
WHERE hire_date >= DATE '2023-01-01';

-- 优化:使用CTE预计算
WITH company_stats AS (
    SELECT AVG(salary) as company_avg
    FROM memory.default.employees
),
dept_stats AS (
    SELECT 
        department,
        MAX(salary) as dept_max,
        COUNT(*) as dept_count
    FROM memory.default.employees
    GROUP BY department
)
SELECT 
    e.emp_name,
    e.department,
    e.salary,
    cs.company_avg,
    ds.dept_max,
    ds.dept_count
FROM memory.default.employees e
CROSS JOIN company_stats cs
JOIN dept_stats ds ON e.department = ds.department
WHERE e.hire_date >= DATE '2023-01-01';

🎯 复杂查询优化案例

📊 窗口函数与JOIN结合优化

-- 复杂分析查询:客户排名与订单分析
-- 创建更真实的业务数据
CREATE TABLE memory.default.customer_orders AS
SELECT 
    order_id,
    customer_id,
    product_category,
    order_amount,
    order_date,
    -- 添加一些业务逻辑字段
    CASE WHEN order_amount > 500 THEN 'High' 
         WHEN order_amount > 100 THEN 'Medium' 
         ELSE 'Low' END as order_value_tier
FROM (
    SELECT 
        row_number() OVER () as order_id,
        CAST(random() * 50000 + 1 AS BIGINT) as customer_id,
        'Category_' || CAST(CAST(random() * 10 + 1 AS INTEGER) AS VARCHAR) as product_category,
        CAST(random() * 1000 + 10 AS DECIMAL(8,2)) as order_amount,
        DATE '2024-01-01' + (random() * 90)::INTEGER * INTERVAL '1' DAY as order_date
    FROM UNNEST(SEQUENCE(1, 1000000)) t(x)
);

-- 复杂查询:结合窗口函数、JOIN和聚合
-- 目标:分析每个客户的购买模式和排名
WITH customer_metrics AS (
    -- 第一步:计算客户级别指标
    SELECT 
        customer_id,
        COUNT(*) as total_orders,
        SUM(order_amount) as total_spent,
        AVG(order_amount) as avg_order_value,
        COUNT(DISTINCT product_category) as category_diversity,
        MIN(order_date) as first_order_date,
        MAX(order_date) as last_order_date,
        -- 计算每个客户的消费排名
        RANK() OVER (ORDER BY SUM(order_amount) DESC) as spending_rank,
        NTILE(5) OVER (ORDER BY SUM(order_amount)) as spending_quintile
    FROM memory.default.customer_orders
    WHERE order_date >= DATE '2024-02-01'
    GROUP BY customer_id
),
category_preferences AS (
    -- 第二步:分析客户的品类偏好
    SELECT 
        customer_id,
        product_category,
        COUNT(*) as category_orders,
        SUM(order_amount) as category_spent,
        ROW_NUMBER() OVER (PARTITION BY customer_id ORDER BY COUNT(*) DESC) as category_rank
    FROM memory.default.customer_orders
    WHERE order_date >= DATE '2024-02-01'
    GROUP BY customer_id, product_category
),
top_category_per_customer AS (
    -- 第三步:获取每个客户的主要品类
    SELECT 
        customer_id,
        product_category as primary_category,
        category_orders,
        category_spent
    FROM category_preferences
    WHERE category_rank = 1
)
-- 最终查询:综合分析
SELECT 
    cm.customer_id,
    cm.total_orders,
    cm.total_spent,
    cm.avg_order_value,
    cm.category_diversity,
    cm.spending_rank,
    cm.spending_quintile,
    tcp.primary_category,
    tcp.category_spent as primary_category_spent,
    -- 计算主要品类占比
    ROUND(100.0 * tcp.category_spent / cm.total_spent, 2) as primary_category_pct,
    -- 客户生命周期天数
    DATE_DIFF('day', cm.first_order_date, cm.last_order_date) as customer_lifecycle_days,
    -- 客户价值评分
    CASE 
        WHEN cm.spending_quintile = 5 AND cm.category_diversity >= 5 THEN 'High Value Multi-Category'
        WHEN cm.spending_quintile = 5 THEN 'High Value'
        WHEN cm.category_diversity >= 7 THEN 'Multi-Category Explorer'
        ELSE 'Standard'
    END as customer_segment
FROM customer_metrics cm
JOIN top_category_per_customer tcp ON cm.customer_id = tcp.customer_id
WHERE cm.total_orders >= 5  -- 只关注活跃客户
ORDER BY cm.spending_rank
LIMIT 1000;

🔄 递归式查询优化

-- 层次数据查询优化(模拟组织架构)
CREATE TABLE memory.default.employee_hierarchy AS
SELECT 
    emp_id,
    'Employee_' || emp_id::VARCHAR as emp_name,
    CASE WHEN emp_id <= 10 THEN NULL 
         ELSE (emp_id - 1) / 10 + 1 END as manager_id,
    'Level_' || CASE WHEN emp_id <= 10 THEN '1'
                     WHEN emp_id <= 100 THEN '2'
                     ELSE '3' END as level
FROM (
    SELECT row_number() OVER () as emp_id
    FROM UNNEST(SEQUENCE(1, 1000)) t(x)
);

-- 使用自连接替代递归查询
-- 查找每个员工最多两级上级;不适用于任意深度的完整管理链路
SELECT 
    e.emp_id,
    e.emp_name,
    e.level,
    m1.emp_name as direct_manager,
    m2.emp_name as senior_manager,
    CASE 
        WHEN m2.emp_id IS NOT NULL THEN 3
        WHEN m1.emp_id IS NOT NULL THEN 2
        ELSE 1
    END as hierarchy_depth
FROM memory.default.employee_hierarchy e
LEFT JOIN memory.default.employee_hierarchy m1 ON e.manager_id = m1.emp_id
LEFT JOIN memory.default.employee_hierarchy m2 ON m1.manager_id = m2.emp_id
ORDER BY e.emp_id;

-- 使用窗口函数进行层次分析
WITH hierarchy_analysis AS (
    SELECT 
        emp_id,
        emp_name,
        manager_id,
        level,
        -- 计算每个级别的员工数量
        COUNT(*) OVER (PARTITION BY level) as level_employee_count,
        -- 计算直接下属数量
        (SELECT COUNT(*) FROM memory.default.employee_hierarchy r WHERE r.manager_id = employee_hierarchy.emp_id) as direct_reports,
        -- 级别内排名
        ROW_NUMBER() OVER (PARTITION BY level ORDER BY emp_id) as level_rank
    FROM memory.default.employee_hierarchy
)
SELECT 
    emp_id,
    emp_name,
    level,
    level_employee_count,
    direct_reports,
    level_rank,
    CASE 
        WHEN direct_reports >= 10 THEN 'Senior Manager'
        WHEN direct_reports >= 5 THEN 'Manager'
        WHEN direct_reports > 0 THEN 'Team Lead'
        ELSE 'Individual Contributor'
    END as role_type
FROM hierarchy_analysis
ORDER BY level, level_rank;

🚀 高级优化技术

⚡ 动态过滤在JOIN中的应用

-- 动态过滤优化大事实表JOIN
SET SESSION enable_dynamic_filtering = true;
SET SESSION dynamic_filtering_max_per_driver_row_count = 5000;

-- 创建大事实表
CREATE TABLE memory.default.fact_transactions AS
SELECT 
    txn_id,
    (random() * 100000 + 1)::BIGINT as customer_id,
    (random() * 10000 + 1)::BIGINT as merchant_id,
    (random() * 1000 + 10)::DECIMAL(10,2) as amount,
    TIMESTAMP '2024-01-01 00:00:00' + 
        CAST(random() * 7776000 AS INTEGER) * INTERVAL '1' SECOND as txn_timestamp
FROM (
    SELECT row_number() OVER () as txn_id
    FROM UNNEST(SEQUENCE(1, 10000000)) t(x)  -- 1000万记录
);

-- 创建小维度表
CREATE TABLE memory.default.high_value_customers AS
SELECT 
    customer_id,
    'HighValue_' || customer_id::VARCHAR as customer_name
FROM (
    SELECT (random() * 100000 + 1)::BIGINT as customer_id
    FROM UNNEST(SEQUENCE(1, 1000)) t(x)  -- 只有1000个高价值客户
);

-- 动态过滤可能减少探测侧读取或处理的数据量;效果取决于过滤选择性、连接器支持及执行计划
EXPLAIN ANALYZE
SELECT 
    DATE_TRUNC('day', f.txn_timestamp) as txn_date,
    COUNT(*) as transaction_count,
    SUM(f.amount) as total_amount,
    COUNT(DISTINCT f.customer_id) as unique_customers
FROM memory.default.fact_transactions f
JOIN memory.default.high_value_customers h ON f.customer_id = h.customer_id
WHERE f.txn_timestamp >= TIMESTAMP '2024-03-01 00:00:00'
GROUP BY DATE_TRUNC('day', f.txn_timestamp)
ORDER BY txn_date;

📊 分区感知JOIN优化

-- 分区表JOIN优化
-- 假设在Hive中有按日期分区的表
-- 这里用Memory连接器演示日期过滤与JOIN,不模拟真实存储分区裁剪

-- 创建模拟的分区数据
CREATE TABLE memory.default.sales_march AS
SELECT 
    sale_id,
    customer_id,
    amount,
    sale_date
FROM (
    SELECT 
        row_number() OVER () as sale_id,
        (random() * 10000 + 1)::BIGINT as customer_id,
        CAST(random() * 500 + 10 AS DECIMAL(8,2)) as amount,
        DATE '2024-03-01' + CAST(random() * 30 AS INTEGER) * INTERVAL '1' DAY as sale_date
    FROM UNNEST(SEQUENCE(1, 500000)) t(x)
);

CREATE TABLE memory.default.customer_segments AS
SELECT 
    customer_id,
    segment,
    segment_score
FROM (
    SELECT 
        row_number() OVER () as customer_id,
        CASE WHEN random() < 0.2 THEN 'Premium'
             WHEN random() < 0.5 THEN 'Standard'
             ELSE 'Basic' END as segment,
        CAST(random() * 100 AS INTEGER) as segment_score
    FROM UNNEST(SEQUENCE(1, 10000)) t(x)
);

-- 日期过滤与JOIN对照查询
-- 此Memory表示例不展示真实存储分区裁剪
SELECT 
    cs.segment,
    COUNT(*) as sale_count,
    SUM(s.amount) as total_amount,
    AVG(s.amount) as avg_amount
FROM (
    SELECT customer_id, amount, sale_date
    FROM memory.default.sales_march
    WHERE sale_date BETWEEN DATE '2024-03-15' AND DATE '2024-03-31'  -- 日期过滤;此Memory表示例不证明分区裁剪
) s
JOIN (
    SELECT customer_id, segment
    FROM memory.default.customer_segments
    WHERE segment IN ('Premium', 'Standard')  -- 预过滤
) cs ON s.customer_id = cs.customer_id
GROUP BY cs.segment
ORDER BY total_amount DESC;

💡 性能调优最佳实践

📋 JOIN性能检查清单

-- 1. 检查JOIN键的数据分布
SELECT 
    customer_id,
    COUNT(*) as frequency,
    COUNT(*) * 100.0 / SUM(COUNT(*)) OVER () as percentage
FROM memory.default.customer_orders
GROUP BY customer_id
ORDER BY frequency DESC
LIMIT 20;

-- 2. 检查NULL值对JOIN的影响
SELECT 
    'customer_orders' as table_name,
    COUNT(*) as total_rows,
    COUNT(customer_id) as non_null_customer_ids,
    COUNT(*) - COUNT(customer_id) as null_customer_ids
FROM memory.default.customer_orders
UNION ALL
SELECT 
    'customers_large' as table_name,
    COUNT(*) as total_rows,
    COUNT(customer_id) as non_null_customer_ids,
    COUNT(*) - COUNT(customer_id) as null_customer_ids
FROM memory.default.customers_large;

-- 3. 分析JOIN选择性
WITH join_stats AS (
    SELECT 
        COUNT(DISTINCT co.customer_id) as left_distinct,
        COUNT(DISTINCT cl.customer_id) as right_distinct,
        COUNT(*) as join_result_count
    FROM memory.default.customer_orders co
    JOIN memory.default.customers_large cl ON co.customer_id = cl.customer_id
)
SELECT 
    left_distinct,
    right_distinct,
    join_result_count,
    join_result_count * 1.0 / left_distinct as avg_matches_per_left,
    join_result_count * 1.0 / right_distinct as avg_matches_per_right
FROM join_stats;

🎯 子查询性能优化建议

-- 子查询优化决策树示例

-- 场景1:相关子查询 → 窗口函数
-- 不推荐
SELECT emp_name, salary
FROM memory.default.employees e1
WHERE salary > (
    SELECT AVG(salary) 
    FROM memory.default.employees e2 
    WHERE e2.department = e1.department
);

-- 推荐
SELECT emp_name, salary
FROM (
    SELECT 
        emp_name, 
        salary,
        AVG(salary) OVER (PARTITION BY department) as dept_avg
    FROM memory.default.employees
) t
WHERE salary > dept_avg;

-- 场景2:EXISTS与JOIN加DISTINCT对照;不预设性能优劣
-- EXISTS方案
SELECT o.order_id
FROM memory.default.orders o
WHERE EXISTS (
    SELECT 1 FROM memory.default.order_items oi 
    WHERE oi.order_id = o.order_id AND oi.quantity > 5
);

-- JOIN加DISTINCT对照方案;需比较实际执行计划与资源消耗
SELECT DISTINCT o.order_id
FROM memory.default.orders o
JOIN memory.default.order_items oi ON o.order_id = oi.order_id
WHERE oi.quantity > 5;

-- 场景3:标量子查询 → CTE
-- 以下假设已有customer_totals表,包含customer_id和total_spent;本节未创建该表
-- 不推荐
SELECT 
    customer_id,
    total_spent,
    total_spent / (SELECT SUM(total_spent) FROM customer_totals) as pct_of_total
FROM customer_totals;

-- 推荐
WITH total_sum AS (
    SELECT SUM(total_spent) as grand_total FROM customer_totals
)
SELECT 
    customer_id,
    total_spent,
    total_spent / ts.grand_total as pct_of_total
FROM customer_totals ct
CROSS JOIN total_sum ts;

🔗 学习连接

📚 本节核心技能

  • JOIN算法理解:Hash Join、Merge Join、Nested Loop Join的适用场景
  • 分发策略掌握:BROADCAST、PARTITIONED策略的选择原则
  • JOIN重排序:多表JOIN的最优执行顺序
  • 子查询优化:相关子查询、EXISTS/IN子查询的性能优化
  • 复杂查询设计:窗口函数与JOIN结合的高级查询模式
  • 动态过滤应用:大表JOIN的性能提升技术

🎯 下一步学习

高级优化检查:

  • ✅ 能够分析和优化复杂的多表JOIN查询?
  • ✅ 掌握子查询到JOIN的转换技巧?
  • ✅ 理解动态过滤等高级优化技术?

继续学习路径:


tip 💡 JOIN与子查询优化精髓

🔗 JOIN优化核心原则:

  • 📊 小表驱动 - 小表广播,大表分区重分布
  • 🎯 过滤前置 - 在JOIN之前尽可能过滤数据
  • 📈 统计驱动 - 依赖准确的表统计信息选择最优策略
  • ⚡ 动态过滤 - 利用运行时信息减少大表扫描

🚀 系统优化:JOIN优化掌握后,进入 内存配置与调优 学习系统级性能调优!