You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

PostgreSQL:大数据量下产品当前与历史价格的查询优化

问题描述

现有products表与product_prices_history表,需求为每个产品生成单行记录,包含当前价格、当前价格创建时间,以及存在且与当前价格不同的上一次历史价格及其创建时间。当前采用LATERAL JOIN的查询语句在100万产品、4000万价格历史记录的场景下运行极慢,寻求替代解决方案。

当前查询语句
SELECT 
    products.id, 
    price.price as price, 
    price.created_at as price_created_at,
    prev_price.price as prev_price,
    prev_price.created_at as prev_price_created_at 
FROM products
LEFT JOIN LATERAL (
    SELECT price, created_at
    FROM product_prices_history
    WHERE product_prices_history.product_id = products.id
    ORDER BY created_at DESC
    LIMIT 1
) price ON true
LEFT JOIN LATERAL (
    SELECT price, created_at
    FROM product_prices_history as prev_price 
    WHERE prev_price.product_id = products.id AND prev_price.price <> price.price
    ORDER BY created_at DESC
    LIMIT 1
) prev_price ON true;
数据示例

products表

id
1
2
3

product_prices_history表

idpricecreated_at
1100.0'2023-01-01'
210.0'2023-01-01'
310.0'2023-01-01'
1100.0'2023-01-02'
210.0'2023-01-02'
310.0'2023-01-02'
1100.0'2023-01-03'
22.0'2023-01-03'
1100.0'2023-01-04'
22.0'2023-01-04'
1100.0'2023-01-05'
22.0'2023-01-05'
预期结果
idpriceprice_created_atprev_priceprev_price_created_at
1100.02023-01-05T00:00:00.000Z
22.02023-01-05T00:00:00.000Z10.02023-01-02T00:00:00.000Z
310.02023-01-02T00:00:00.000Z
优化方案

方案一:窗口函数批量筛选记录

通过窗口函数为每个产品的价格记录排序并标记价格变化节点,一次性提取当前价格和上一次不同的价格,避免多次LATERAL JOIN的嵌套查询:

WITH price_with_markers AS (
    SELECT
        product_id,
        price,
        created_at,
        -- 按创建时间倒序排名,取第一条为当前价格
        ROW_NUMBER() OVER (PARTITION BY product_id ORDER BY created_at DESC) AS rn,
        -- 标记与上一条(更早的)价格是否不同
        CASE WHEN price != LAG(price) OVER (PARTITION BY product_id ORDER BY created_at DESC)
             THEN 1 ELSE 0 END AS is_diff_from_current
    FROM product_prices_history
),
current_prices AS (
    SELECT product_id, price, created_at FROM price_with_markers WHERE rn = 1
),
prev_prices AS (
    SELECT
        product_id,
        price,
        created_at
    FROM (
        SELECT
            product_id,
            price,
            created_at,
            ROW_NUMBER() OVER (PARTITION BY product_id ORDER BY created_at DESC) AS diff_rn
        FROM price_with_markers
        WHERE rn > 1 AND is_diff_from_current = 1
    ) t
    WHERE diff_rn = 1
)
SELECT
    p.id,
    cp.price,
    cp.created_at AS price_created_at,
    pp.price AS prev_price,
    pp.created_at AS prev_price_created_at
FROM products p
LEFT JOIN current_prices cp ON p.id = cp.product_id
LEFT JOIN prev_prices pp ON p.id = pp.product_id;

方案二:先去重连续相同价格再关联

如果历史表中有大量连续相同的价格记录,先去重仅保留价格变化的节点,大幅减少后续处理的数据量:

WITH price_changes_only AS (
    SELECT
        product_id,
        price,
        created_at
    FROM (
        SELECT
            product_id,
            price,
            created_at,
            -- 只保留价格发生变化的记录(含第一条)
            CASE WHEN price = LAG(price) OVER (PARTITION BY product_id ORDER BY created_at)
                 THEN 0 ELSE 1 END AS keep_record
        FROM product_prices_history
    ) t
    WHERE keep_record = 1
),
ranked_changes AS (
    SELECT
        product_id,
        price,
        created_at,
        ROW_NUMBER() OVER (PARTITION BY product_id ORDER BY created_at DESC) AS rn
    FROM price_changes_only
)
SELECT
    p.id,
    rc1.price AS price,
    rc1.created_at AS price_created_at,
    rc2.price AS prev_price,
    rc2.created_at AS prev_price_created_at
FROM products p
LEFT JOIN ranked_changes rc1 ON p.id = rc1.product_id AND rc1.rn = 1
LEFT JOIN ranked_changes rc2 ON p.id = rc2.product_id AND rc2.rn = 2;

关键索引优化

无论采用哪种方案,必须创建复合索引提升查询效率:

CREATE INDEX idx_product_price_history ON product_prices_history (product_id, created_at DESC, price);

该索引可让窗口函数的排序、筛选直接走索引,避免全表扫描。

内容的提问来源于stack exchange,提问作者Andrew

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.28 13:18:01