KES 数据仓库与OLAP应用实战:数据分析、聚合查询与性能优化

KES 数据仓库与OLAP应用实战:数据分析、聚合查询与性能优化

前言

数据仓库是企业数据分析的核心基础设施,为决策提供数据支撑。KES作为企业级关系型数据库,不仅擅长OLTP(联机事务处理),在OLAP(联机分析处理)方面也有出色表现。

本篇内容深入讲解KES在数据仓库和OLAP场景中的应用,详细讲解数据建模、聚合查询、分析函数以及性能优化技巧。全文以实际操作为主,结合大量真实案例。如果你需要构建数据分析系统,或者希望提升分析查询性能,相信这篇内容对你会有帮助。

一、数据仓库基础

理解数据仓库的基本概念是构建分析系统的前提。

数据仓库特点

-- OLTP vs OLAP对比
-- OLTP:面向事务,数据量小,查询简单,响应快
-- OLAP:面向分析,数据量大,查询复杂,响应慢
-- 创建数据仓库事实表
CREATE TABLE sales_fact (
    sale_id BIGSERIAL,
    date_key DATE NOT NULL,
    product_key BIGINT NOT NULL,
    customer_key BIGINT NOT NULL,
    store_key BIGINT NOT NULL,
    quantity INT NOT NULL,
    amount NUMERIC(12,2) NOT NULL,
    cost NUMERIC(12,2),
    profit NUMERIC(12,2),
    created_at TIMESTAMP DEFAULT now()
);
-- 创建维度表
CREATE TABLE date_dim (
    date_key DATE PRIMARY KEY,
    year INT,
    quarter INT,
    month INT,
    week INT,
    day_of_week INT,
    is_holiday BOOLEAN
);
CREATE TABLE product_dim (
    product_key BIGINT PRIMARY KEY,
    product_name VARCHAR(200),
    category VARCHAR(100),
    brand VARCHAR(100),
    price NUMERIC(10,2)
);
CREATE TABLE customer_dim (
    customer_key BIGINT PRIMARY KEY,
    customer_name VARCHAR(100),
    gender VARCHAR(10),
    age INT,
    city VARCHAR(100),
    vip_level INT
);

星型模型

-- 星型模型查询示例
SELECT 
    d.year,
    d.quarter,
    p.category,
    SUM(f.amount) AS total_sales,
    SUM(f.profit) AS total_profit
FROM sales_fact f
JOIN date_dim d ON f.date_key = d.date_key
JOIN product_dim p ON f.product_key = p.product_key
WHERE d.year = 2026
GROUP BY d.year, d.quarter, p.category
ORDER BY d.year, d.quarter, p.category;

雪花模型

-- 雪花模型:维度表进一步规范化
CREATE TABLE category_dim (
    category_key BIGINT PRIMARY KEY,
    category_name VARCHAR(100),
    parent_category BIGINT
);
CREATE TABLE product_dim_v2 (
    product_key BIGINT PRIMARY KEY,
    product_name VARCHAR(200),
    category_key BIGINT REFERENCES category_dim(category_key),
    brand VARCHAR(100)
);

二、聚合查询与分组分析

聚合查询是OLAP的核心功能,KES提供了丰富的聚合函数和分组方式。

基础聚合函数

-- 销售统计
SELECT 
    date_trunc('month', date_key) AS month,
    COUNT(*) AS order_count,
    SUM(quantity) AS total_quantity,
    SUM(amount) AS total_amount,
    AVG(amount) AS avg_amount,
    MIN(amount) AS min_amount,
    MAX(amount) AS max_amount
FROM sales_fact
WHERE date_key >= '2026-01-01'
GROUP BY date_trunc('month', date_key)
ORDER BY month;

高级聚合函数

-- 百分位数
SELECT 
    percentile_cont(0.5) WITHIN GROUP (ORDER BY amount) AS median_amount,
    percentile_cont(0.9) WITHIN GROUP (ORDER BY amount) AS p90_amount,
    percentile_cont(0.95) WITHIN GROUP (ORDER BY amount) AS p95_amount
FROM sales_fact
WHERE date_key >= '2026-01-01';
-- 标准差和方差
SELECT 
    STDDEV(amount) AS stddev_amount,
    VARIANCE(amount) AS variance_amount
FROM sales_fact;
-- 字符串聚合
SELECT 
    category,
    STRING_AGG(product_name, ', ') AS products
FROM product_dim
GROUP BY category;

GROUPING SETS

-- 多维度聚合
SELECT 
    year,
    quarter,
    category,
    SUM(amount) AS total_sales
FROM sales_fact s
JOIN date_dim d ON s.date_key = d.date_key
JOIN product_dim p ON s.product_key = p.product_key
WHERE year = 2026
GROUP BY GROUPING SETS (
    (year, quarter, category),  -- 年+季度+类别
    (year, quarter),             -- 年+季度
    (year, category),            -- 年+类别
    ()                           -- 总计
)
ORDER BY year, quarter, category;

ROLLUP和CUBE

-- ROLLUP:层次化聚合
SELECT 
    year,
    quarter,
    month,
    SUM(amount) AS total_sales
FROM sales_fact s
JOIN date_dim d ON s.date_key = d.date_key
WHERE year = 2026
GROUP BY ROLLUP (year, quarter, month)
ORDER BY year, quarter, month;
-- CUBE:全维度组合
SELECT 
    year,
    category,
    brand,
    SUM(amount) AS total_sales
FROM sales_fact s
JOIN date_dim d ON s.date_key = d.date_key
JOIN product_dim p ON s.product_key = p.product_key
WHERE year = 2026
GROUP BY CUBE (year, category, brand)
ORDER BY year, category, brand;

三、分析函数与窗口函数

分析函数是OLAP查询的利器,可以实现复杂的排名、累计、移动平均等计算。

排名函数

-- 销售排名
SELECT 
    product_name,
    category,
    total_sales,
    RANK() OVER (PARTITION BY category ORDER BY total_sales DESC) AS category_rank,
    DENSE_RANK() OVER (PARTITION BY category ORDER BY total_sales DESC) AS dense_rank,
    ROW_NUMBER() OVER (PARTITION BY category ORDER BY total_sales DESC) AS row_num
FROM (
    SELECT 
        p.product_name,
        p.category,
        SUM(f.amount) AS total_sales
    FROM sales_fact f
    JOIN product_dim p ON f.product_key = p.product_key
    GROUP BY p.product_name, p.category
) t;

累计与移动平均

-- 累计销售额
SELECT 
    date_trunc('month', date_key) AS month,
    SUM(amount) AS monthly_sales,
    SUM(SUM(amount)) OVER (ORDER BY date_trunc('month', date_key)) AS cumulative_sales
FROM sales_fact
WHERE date_key >= '2026-01-01'
GROUP BY date_trunc('month', date_key)
ORDER BY month;
-- 3个月移动平均
SELECT 
    date_trunc('month', date_key) AS month,
    SUM(amount) AS monthly_sales,
    AVG(SUM(amount)) OVER (
        ORDER BY date_trunc('month', date_key)
        ROWS BETWEEN 2 PRECEDING AND CURRENT ROW
    ) AS moving_avg_3m
FROM sales_fact
WHERE date_key >= '2026-01-01'
GROUP BY date_trunc('month', date_key)
ORDER BY month;

同比环比分析

-- 同比环比计算
WITH monthly_sales AS (
    SELECT 
        date_trunc('month', date_key) AS month,
        SUM(amount) AS sales
    FROM sales_fact
    GROUP BY date_trunc('month', date_key)
)
SELECT 
    month,
    sales,
    LAG(sales, 1) OVER (ORDER BY month) AS prev_month,
    LAG(sales, 12) OVER (ORDER BY month) AS prev_year,
    ROUND((sales - LAG(sales, 1) OVER (ORDER BY month)) * 100.0 / 
          NULLIF(LAG(sales, 1) OVER (ORDER BY month), 0), 2) AS mom_growth,
    ROUND((sales - LAG(sales, 12) OVER (ORDER BY month)) * 100.0 / 
          NULLIF(LAG(sales, 12) OVER (ORDER BY month), 0), 2) AS yoy_growth
FROM monthly_sales
ORDER BY month;

四、数据仓库性能优化

大规模数据分析查询需要针对性的性能优化。

物化视图

-- 创建物化视图
CREATE MATERIALIZED VIEW mv_monthly_sales AS
SELECT 
    date_trunc('month', date_key) AS month,
    category,
    brand,
    SUM(quantity) AS total_quantity,
    SUM(amount) AS total_amount,
    SUM(profit) AS total_profit
FROM sales_fact s
JOIN product_dim p ON s.product_key = p.product_key
GROUP BY date_trunc('month', date_key), category, brand;
-- 创建索引
CREATE INDEX idx_mv_monthly_sales_month ON mv_monthly_sales (month);
CREATE INDEX idx_mv_monthly_sales_category ON mv_monthly_sales (category);
-- 刷新物化视图
REFRESH MATERIALIZED VIEW mv_monthly_sales;
-- 并发刷新(不阻塞查询)
REFRESH MATERIALIZED VIEW CONCURRENTLY mv_monthly_sales;

分区表优化

-- 按月分区销售事实表
CREATE TABLE sales_fact_partitioned (
    sale_id BIGSERIAL,
    date_key DATE NOT NULL,
    product_key BIGINT NOT NULL,
    customer_key BIGINT NOT NULL,
    amount NUMERIC(12,2) NOT NULL
) PARTITION BY RANGE (date_key);
-- 创建月度分区
CREATE TABLE sales_2026_01 PARTITION OF sales_fact_partitioned
    FOR VALUES FROM ('2026-01-01') TO ('2026-02-01');
CREATE TABLE sales_2026_02 PARTITION OF sales_fact_partitioned
    FOR VALUES FROM ('2026-02-01') TO ('2026-03-01');
-- 分区裁剪:查询自动只扫描相关分区
SELECT * FROM sales_fact_partitioned
WHERE date_key >= '2026-01-01' AND date_key < '2026-02-01';

列存储优化

-- 创建列存储表(需要扩展支持)
-- KES支持列存储引擎,适合OLAP场景
-- 压缩策略
-- 对于历史数据,可以使用更高压缩比
ALTER TABLE sales_fact SET (
    toast_tuple_target = 128,
    toast.autovacuum_enabled = true
);

五、实战案例解析

场景一:销售数据分析仪表盘

构建销售数据分析仪表盘。

-- 销售概览
CREATE OR REPLACE FUNCTION get_sales_dashboard(p_date DATE)
RETURNS TABLE(
    metric_name VARCHAR,
    metric_value NUMERIC,
    prev_value NUMERIC,
    change_rate NUMERIC
) AS $$
BEGIN
    RETURN QUERY
    SELECT * FROM (
        VALUES
        ('今日销售额', 
         (SELECT SUM(amount) FROM sales_fact WHERE date_key = p_date),
         (SELECT SUM(amount) FROM sales_fact WHERE date_key = p_date - INTERVAL '1 day'),
         (SELECT ROUND((SUM(amount) - (SELECT SUM(amount) FROM sales_fact WHERE date_key = p_date - INTERVAL '1 day')) * 100.0 / 
          NULLIF((SELECT SUM(amount) FROM sales_fact WHERE date_key = p_date - INTERVAL '1 day'), 0), 2)
         FROM sales_fact WHERE date_key = p_date)),
        ('本月销售额',
         (SELECT SUM(amount) FROM sales_fact WHERE date_trunc('month', date_key) = date_trunc('month', p_date)),
         (SELECT SUM(amount) FROM sales_fact WHERE date_trunc('month', date_key) = date_trunc('month', p_date - INTERVAL '1 month')),
         (SELECT ROUND((SUM(amount) - (SELECT SUM(amount) FROM sales_fact WHERE date_trunc('month', date_key) = date_trunc('month', p_date - INTERVAL '1 month'))) * 100.0 / 
          NULLIF((SELECT SUM(amount) FROM sales_fact WHERE date_trunc('month', date_key) = date_trunc('month', p_date - INTERVAL '1 month')), 0), 2)
         FROM sales_fact WHERE date_trunc('month', date_key) = date_trunc('month', p_date)))
    ) t;
END;
$$ LANGUAGE plpgsql;
-- 使用仪表盘函数
SELECT * FROM get_sales_dashboard(CURRENT_DATE);

场景二:客户行为分析

分析客户购买行为。

-- 客户分层分析
WITH customer_stats AS (
    SELECT 
        c.customer_key,
        c.customer_name,
        COUNT(DISTINCT f.sale_id) AS order_count,
        SUM(f.amount) AS total_amount,
        AVG(f.amount) AS avg_amount,
        MAX(f.date_key) AS last_purchase_date
    FROM customer_dim c
    LEFT JOIN sales_fact f ON c.customer_key = f.customer_key
    GROUP BY c.customer_key, c.customer_name
)
SELECT 
    CASE 
        WHEN total_amount >= 100000 THEN 'VIP客户'
        WHEN total_amount >= 50000 THEN '重要客户'
        WHEN total_amount >= 10000 THEN '普通客户'
        ELSE '低价值客户'
    END AS customer_level,
    COUNT(*) AS customer_count,
    SUM(order_count) AS total_orders,
    SUM(total_amount) AS total_sales,
    ROUND(AVG(total_amount), 2) AS avg_sales_per_customer
FROM customer_stats
GROUP BY 
    CASE 
        WHEN total_amount >= 100000 THEN 'VIP客户'
        WHEN total_amount >= 50000 THEN '重要客户'
        WHEN total_amount >= 10000 THEN '普通客户'
        ELSE '低价值客户'
    END
ORDER BY total_sales DESC;

场景三:商品销售趋势分析

分析商品销售趋势。

-- 商品销售趋势
WITH product_trend AS (
    SELECT 
        p.product_name,
        p.category,
        date_trunc('month', f.date_key) AS month,
        SUM(f.amount) AS monthly_sales
    FROM sales_fact f
    JOIN product_dim p ON f.product_key = p.product_key
    WHERE f.date_key >= CURRENT_DATE - INTERVAL '12 months'
    GROUP BY p.product_name, p.category, date_trunc('month', f.date_key)
)
SELECT 
    product_name,
    category,
    month,
    monthly_sales,
    LAG(monthly_sales, 1) OVER (PARTITION BY product_name ORDER BY month) AS prev_month_sales,
    ROUND((monthly_sales - LAG(monthly_sales, 1) OVER (PARTITION BY product_name ORDER BY month)) * 100.0 / 
          NULLIF(LAG(monthly_sales, 1) OVER (PARTITION BY product_name ORDER BY month), 0), 2) AS mom_growth,
    AVG(monthly_sales) OVER (
        PARTITION BY product_name 
        ORDER BY month 
        ROWS BETWEEN 2 PRECEDING AND CURRENT ROW
    ) AS moving_avg_3m
FROM product_trend
ORDER BY product_name, month;

总结与展望

数据仓库与OLAP是KES的重要应用场景。通过合理的数据建模、高效的聚合查询和性能优化,可以构建强大的数据分析系统。

核心原则:

  1. 根据业务需求选择合适的数据建模方式
  2. 充分利用聚合函数和分析函数
  3. 使用物化视图优化重复查询
  4. 合理设计分区策略,提升查询性能
  5. 定期分析查询性能,持续优化

KES在OLAP场景表现出色,支持丰富的分析函数和聚合方式。在实际应用中,建议根据数据量和查询特点,选择合适的优化策略,构建高效的分析系统。

期望本篇内容能够帮助你掌握KES数据仓库和OLAP应用的核心技术,为构建数据分析平台提供技术支撑。

© 版权声明

相关文章