未记录事实核验 · 状态:维护中
记录仅说明核验时间与维护状态,不保证适用于所有环境;使用前请留意正文中的版本、适用条件与风险提示。
warning 适用范围与待维护事项
本文适用于已具备SQL基础、能管理测试集群的读者,须在隔离的Greenplum练习环境中运行;包含建库、建表、造数、建索引及实际执行查询的操作,不宜直接用于生产。原稿未注明适配版本,也未提供运行验证记录,系统视图、统计数组和执行计划示意须按目标版本核对。销售数据为教学样例,随机数据每次不同;计时部分未设置单节点对照,不能证明MPP加速。部分“优化”改变筛选、排序或返回行数,不能视为等价性能对比;按已有数据统计Segment还会漏掉零行节点。
warning 关联材料边界
以下原引用目标缺失或无法唯一确认,已取消跳转并保留提及:02-Greenplum架构与核心概念。相关素材/延伸说明暂不可用,不能据此声称已经提供配套材料。
第一个Greenplum查询 - MPP分布式查询入门
info 前置知识
- Greenplum环境搭建 — 确保集群已正常启动
- Greenplum架构(原引用“02-Greenplum架构与核心概念”未能确定有效目标,暂不可跳转) — 了解 Master/Segment 架构
- SQL 基础(SELECT、GROUP BY、JOIN)
🎯 查询前的准备工作
🔧 环境检查
📋 连接准备清单
#!/bin/bash
# Greenplum连接准备检查脚本
echo "🔍 开始Greenplum环境检查..."
# 1. 检查服务状态
check_service_status() {
echo "📊 检查Greenplum服务状态..."
su - gpadmin -c "
source /opt/greenplum-db/greenplum_path.sh
gpstate -s
"
if [ $? -eq 0 ]; then
echo "✅ Greenplum服务正常运行"
else
echo "❌ Greenplum服务异常,请检查"
exit 1
fi
}
# 2. 检查网络连接
check_network() {
echo "🌐 检查网络连接..."
# 检查端口是否开放
netstat -ln | grep :5432
if [ $? -eq 0 ]; then
echo "✅ 数据库端口5432正常监听"
else
echo "❌ 端口5432未开放"
fi
}
# 3. 检查用户权限
check_permissions() {
echo "👤 检查用户权限..."
su - gpadmin -c "
source /opt/greenplum-db/greenplum_path.sh
psql -c 'SELECT version();' -q
"
if [ $? -eq 0 ]; then
echo "✅ 用户权限正常"
else
echo "❌ 用户权限异常"
fi
}
# 执行检查
check_service_status
check_network
check_permissions
echo "🔍 检查流程结束;请确认上述各项均通过,并成功连接目标数据库后再开始查询。"
方式2:使用 psql 连接字符串
# URI 格式连接字符串(适合在脚本中使用)
psql "postgresql://gpadmin@localhost:5432/postgres"
# 带密码的 URI 连接(不推荐在命令行明文写密码,建议用 .pgpass 文件)
psql "postgresql://gpadmin:your_password@localhost:5432/postgres"
# 通过环境变量传递密码(避免出现在命令行参数中,但仍可能被有权限的进程读取;优先使用权限受限的 .pgpass)
export PGPASSWORD='your_password'
psql -h localhost -p 5432 -U gpadmin -d postgres
unset PGPASSWORD # 用完后清除
note 分布键选择原则
Greenplum 最关键的建表决策是选择 DISTRIBUTED BY 的分布键,它决定数据如何分散到各个 Segment。
好的分布键应满足:
- 高基数:字段值尽量多,如 user_id、order_id,避免 gender(只有两个值,会导致数据严重倾斜)
- 均匀分布:各值出现频率相近,避免某一个值占据 90% 的数据
- 常用于 JOIN:如果两张大表经常做等值 JOIN,可考虑将连接字段作为分布键;当完整分布键、数据类型和分布策略兼容且连接条件覆盖分布键时,有机会避免重分布,实际效果应以执行计划和测试结果为准
不确定如何选择时,可以考虑 DISTRIBUTED RANDOMLY(随机分布),但它不保证数据完全均匀,且可能增加 JOIN 的数据移动成本;应结合数据分布和查询负载验证。
方式3:图形化客户端连接
| 工具名称 | 连接配置 | 推荐程度 |
|---|---|---|
| DBeaver | Host: localhost Port: 5432 Database: postgres User: gpadmin |
⭐⭐⭐⭐⭐ |
| pgAdmin | 同上配置 | ⭐⭐⭐⭐ |
| DataGrip | 同上配置 | ⭐⭐⭐⭐ |
| Navicat | 同上配置 | ⭐⭐⭐ |
📊 初始数据准备
🏗️ 创建示例数据
-- 连接数据库后执行
-- 1. 创建示例数据库
CREATE DATABASE sales_demo;
-- 2. 切换到示例数据库
\c sales_demo
-- 3. 创建销售数据表
CREATE TABLE sales_data (
sale_id SERIAL PRIMARY KEY,
product_name VARCHAR(100),
category VARCHAR(50),
sale_date DATE,
quantity INTEGER,
unit_price DECIMAL(10,2),
total_amount DECIMAL(12,2),
customer_region VARCHAR(50),
salesperson VARCHAR(50)
) DISTRIBUTED BY (sale_id);
-- 4. 插入示例数据
INSERT INTO sales_data (product_name, category, sale_date, quantity, unit_price, total_amount, customer_region, salesperson) VALUES
('iPhone 14', '手机', '2023-01-15', 2, 6999.00, 13998.00, '北京', '张三'),
('MacBook Pro', '电脑', '2023-01-16', 1, 12999.00, 12999.00, '上海', '李四'),
('iPad Air', '平板', '2023-01-17', 3, 4599.00, 13797.00, '深圳', '王五'),
('Apple Watch', '手表', '2023-01-18', 1, 2999.00, 2999.00, '广州', '赵六'),
('AirPods Pro', '耳机', '2023-01-19', 5, 1999.00, 9995.00, '杭州', '钱七'),
('iPhone 13', '手机', '2023-01-20', 3, 5999.00, 17997.00, '南京', '孙八'),
('MacBook Air', '电脑', '2023-01-21', 2, 8999.00, 17998.00, '武汉', '周九'),
('iPad Pro', '平板', '2023-01-22', 1, 8599.00, 8599.00, '成都', '吴十'),
('Apple TV', '娱乐', '2023-01-23', 2, 1499.00, 2998.00, '西安', '郑一'),
('HomePod', '音响', '2023-01-24', 1, 2299.00, 2299.00, '青岛', '王二');
-- 5. 创建客户信息表
CREATE TABLE customers (
customer_id SERIAL PRIMARY KEY,
customer_name VARCHAR(100),
region VARCHAR(50),
registration_date DATE,
customer_type VARCHAR(20)
) DISTRIBUTED BY (customer_id);
INSERT INTO customers (customer_name, region, registration_date, customer_type) VALUES
('科技公司A', '北京', '2022-01-01', '企业客户'),
('个人用户B', '上海', '2022-02-01', '个人客户'),
('教育机构C', '深圳', '2022-03-01', '教育客户'),
('医疗机构D', '广州', '2022-04-01', '企业客户'),
('个人用户E', '杭州', '2022-05-01', '个人客户');
-- 6. 创建大数据量测试表
CREATE TABLE large_sales (
id SERIAL,
product_id INTEGER,
customer_id INTEGER,
sale_date DATE,
amount DECIMAL(10,2)
) DISTRIBUTED BY (id);
-- 插入大量测试数据
INSERT INTO large_sales (product_id, customer_id, sale_date, amount)
SELECT
(random() * 1000)::INTEGER + 1 as product_id,
(random() * 1000)::INTEGER + 1 as customer_id,
'2023-01-01'::DATE + (random() * 365)::INTEGER as sale_date,
(random() * 10000)::DECIMAL(10,2) as amount
FROM generate_series(1, 100000);
\echo '✅ 示例数据创建完成!'
🎯 第一个基础查询
📊 简单查询体验
🔍 数据探索查询
-- 🎉 你的第一个Greenplum查询!
-- 查看销售数据概览
SELECT '🎊 欢迎来到Greenplum世界!' as welcome_message;
-- 查看数据库版本信息
SELECT version();
-- 查看当前连接信息
SELECT
current_database() as 数据库名称,
current_user as 当前用户,
current_timestamp as 当前时间;
-- 查看表结构
\d sales_data
-- 基础数据查询
SELECT * FROM sales_data LIMIT 5;
-- 统计数据总览
SELECT
COUNT(*) as 总记录数,
COUNT(DISTINCT product_name) as 产品种类,
COUNT(DISTINCT customer_region) as 销售区域,
SUM(total_amount) as 总销售额,
AVG(total_amount) as 平均订单金额,
MAX(total_amount) as 最大订单金额,
MIN(total_amount) as 最小订单金额
FROM sales_data;
预期输出:
总记录数 | 产品种类 | 销售区域 | 总销售额 | 平均订单金额 | 最大订单金额 | 最小订单金额
----------+----------+----------+-------------+--------------+--------------+--------------
10 | 10 | 10 | 103679.00 | 10367.90 | 17998.00 | 2299.00
📈 分组聚合查询
📊 销售分析查询
-- 🏆 体验Greenplum的分析威力
-- 1. 按产品类别统计销售情况
SELECT
category as 产品类别,
COUNT(*) as 销售次数,
SUM(quantity) as 总销量,
SUM(total_amount) as 总销售额,
AVG(unit_price) as 平均单价,
ROUND(AVG(total_amount), 2) as 平均订单金额
FROM sales_data
GROUP BY category
ORDER BY 总销售额 DESC;
-- 2. 按地区统计销售情况
SELECT
customer_region as 销售区域,
COUNT(*) as 订单数量,
SUM(total_amount) as 区域销售额,
ROUND(
SUM(total_amount) * 100.0 / (SELECT SUM(total_amount) FROM sales_data),
2
) as 销售占比
FROM sales_data
GROUP BY customer_region
ORDER BY 区域销售额 DESC;
-- 3. 按销售人员统计业绩
SELECT
salesperson as 销售人员,
COUNT(*) as 订单数量,
SUM(total_amount) as 个人业绩,
AVG(total_amount) as 平均订单金额,
MAX(total_amount) as 最大订单,
RANK() OVER (ORDER BY SUM(total_amount) DESC) as 业绩排名
FROM sales_data
GROUP BY salesperson
ORDER BY 个人业绩 DESC;
-- 4. 按时间统计销售趋势
SELECT
DATE_TRUNC('month', sale_date) as 销售月份,
COUNT(*) as 月度订单数,
SUM(total_amount) as 月度销售额,
AVG(total_amount) as 月度平均订单
FROM sales_data
GROUP BY DATE_TRUNC('month', sale_date)
ORDER BY 销售月份;
🔍 复杂查询示例
📊 多表关联分析
-- 🌟 展示Greenplum高级查询能力
-- 1. 窗口函数分析
WITH sales_ranking AS (
SELECT
product_name,
category,
total_amount,
customer_region,
ROW_NUMBER() OVER (PARTITION BY category ORDER BY total_amount DESC) as category_rank,
PERCENT_RANK() OVER (ORDER BY total_amount) as sales_percentile,
LAG(total_amount) OVER (ORDER BY sale_date) as prev_sale_amount,
SUM(total_amount) OVER (PARTITION BY customer_region) as region_total
FROM sales_data
)
SELECT
product_name as 产品名称,
category as 类别,
total_amount as 销售额,
category_rank as 类别内排名,
ROUND((sales_percentile * 100)::NUMERIC, 1) as 销售额百分位,
COALESCE(total_amount - prev_sale_amount, 0) as 与上笔订单差额,
region_total as 所在区域总销售额
FROM sales_ranking
WHERE category_rank <= 3
ORDER BY category, category_rank;
-- 2. 复杂聚合分析
SELECT
customer_region as 销售区域,
COUNT(*) as 订单数量,
COUNT(DISTINCT product_name) as 产品种类,
SUM(total_amount) as 总销售额,
AVG(total_amount) as 平均订单金额,
STDDEV(total_amount) as 订单金额标准差,
-- 计算销售额分布
COUNT(CASE WHEN total_amount < 5000 THEN 1 END) as 小额订单数,
COUNT(CASE WHEN total_amount BETWEEN 5000 AND 15000 THEN 1 END) as 中额订单数,
COUNT(CASE WHEN total_amount > 15000 THEN 1 END) as 大额订单数,
-- 拼接月份与销售金额明细(不计算月度增长)
STRING_AGG(
DATE_PART('month', sale_date)::TEXT || ':' || total_amount::TEXT,
', ' ORDER BY sale_date
) as 月度明细
FROM sales_data
GROUP BY customer_region
HAVING SUM(total_amount) > 10000
ORDER BY 总销售额 DESC;
⚡ 体验MPP并行计算
📊 并行执行验证
🔍 查看执行计划
-- 🚀 查看Greenplum如何并行执行查询
-- 1. 查看简单查询的执行计划
EXPLAIN SELECT * FROM sales_data WHERE category = '手机';
-- 2. 查看聚合查询的执行计划
EXPLAIN (ANALYZE, BUFFERS)
SELECT
customer_region,
COUNT(*),
SUM(total_amount)
FROM sales_data
GROUP BY customer_region;
-- 3. 查看大数据量查询的执行计划
EXPLAIN (ANALYZE, BUFFERS, VERBOSE)
SELECT
product_id,
COUNT(*) as order_count,
SUM(amount) as total_sales,
AVG(amount) as avg_sale
FROM large_sales
WHERE sale_date >= '2023-06-01'
GROUP BY product_id
HAVING COUNT(*) > 50
ORDER BY total_sales DESC
LIMIT 10;
EXPLAIN ANALYZE 输出解读:
理解Greenplum执行计划时,可从叶子节点向父节点梳理数据流;下面仅为并行聚合的概念示意,不是所给SQL的实测计划,实际节点名称、Motion、排序及汇总位置会随版本、优化器和统计信息变化:
%%{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 BT
classDef default fill:#FDFBF5,stroke:#6E6A60,color:#2D2A26,stroke-width:1.5px
classDef startend fill:#2D2A26,stroke:#2D2A26,color:#FDFBF5,font-weight:bold
classDef decision fill:#F6EAD8,stroke:#C08A1A,color:#2D2A26,font-weight:bold
classDef hub fill:#3B5A80,stroke:#3B5A80,color:#FDFBF5,font-weight:bold
classDef io fill:#E5EAF0,stroke:#3B5A80,color:#2D2A26
classDef accent fill:#A6432C,stroke:#A6432C,color:#FDFBF5,font-weight:bold
classDef storage fill:#F3EEDF,stroke:#9A9488,color:#2D2A26
A["Seq Scan on large_sales\n(各 Segment 并行扫描)"] --> B
B["Partial HashAggregate\n(各 Segment 本地聚合)"] --> C
C["Redistribute Motion N:N\n(按 product_id 重分布数据)"] --> D
D["Finalize HashAggregate\n(汇总各 Segment 结果)"] --> E
E["Sort\n(ORDER BY total_sales DESC)"] --> F
F["Limit\n(LIMIT 10,返回前 10 行)"]
class A,B,C,D,E,F default
关键指标说明:
| 指标 | 含义 | 优化方向 |
|---|---|---|
cost=X..Y |
估算启动成本和总成本(优化器成本单位,不是毫秒) | 在同一查询、同一环境中辅助比较候选计划,不代表实际耗时 |
rows=N |
估算返回行数(基于统计信息) | 与实际差距大时需 ANALYZE 更新统计 |
actual time=X..Y |
实际执行时间(ANALYZE 时显示,注意循环次数及分布式汇总口径) | 不可直接与cost比较;结合实际行数、估算行数、I/O和数据移动定位原因 |
Redistribute Motion N:N |
数据在 Segment 间重分布,有网络开销 | 检查连接是否覆盖完整分布键、分布策略是否兼容;聚合等操作也可能需要重分布 |
Broadcast Motion |
将源执行节点的数据复制发送到目标Segment,不一定由Master发送 | 优化器可能为较小输入选择广播,需结合输入规模、计划和网络开销判断 |
tip EXPLAIN ANALYZE 使用技巧
EXPLAIN只显示执行计划(不实际执行),适合快速检查计划是否合理EXPLAIN ANALYZE实际执行查询并显示真实时间,适合定位性能瓶颈EXPLAIN (ANALYZE, BUFFERS, VERBOSE)同时显示缓冲区命中情况,适合深度调优- 看到
Redistribute Motion频繁出现时,检查 JOIN 字段是否与分布键对齐
📈 性能对比测试
-- 🏁 当前集群内的查询计时练习(未设置单节点对照)
-- 1. 创建性能测试函数
CREATE OR REPLACE FUNCTION performance_test(query_name TEXT, sql_query TEXT)
RETURNS TABLE(
test_name TEXT,
execution_time INTERVAL,
rows_processed BIGINT
) AS $$
DECLARE
start_time TIMESTAMP;
end_time TIMESTAMP;
row_count BIGINT;
BEGIN
start_time := clock_timestamp();
EXECUTE sql_query;
GET DIAGNOSTICS row_count = ROW_COUNT;
end_time := clock_timestamp();
RETURN QUERY SELECT query_name, end_time - start_time, row_count;
END;
$$ LANGUAGE plpgsql;
-- 2. 执行性能测试
SELECT * FROM performance_test(
'大数据聚合查询',
'SELECT product_id, COUNT(*), SUM(amount), AVG(amount)
FROM large_sales
GROUP BY product_id
HAVING COUNT(*) > 10'
);
-- 3. 比较不同查询复杂度的性能
\timing on
-- 简单查询
SELECT COUNT(*) FROM large_sales;
-- 复杂聚合查询
SELECT
DATE_TRUNC('month', sale_date) as month,
COUNT(*) as order_count,
SUM(amount) as total_amount,
AVG(amount) as avg_amount,
PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY amount) as median_amount,
STDDEV(amount) as amount_stddev
FROM large_sales
GROUP BY DATE_TRUNC('month', sale_date)
ORDER BY month;
-- 窗口函数查询
SELECT
id,
amount,
ROW_NUMBER() OVER (PARTITION BY product_id ORDER BY amount DESC) as rank,
SUM(amount) OVER (PARTITION BY product_id) as product_total,
amount / SUM(amount) OVER (PARTITION BY product_id) as percentage
FROM large_sales
WHERE product_id <= 10
ORDER BY product_id, rank;
\timing off
🔍 数据分布查询
📊 查看数据在Segment间的分布
-- 🗂️ 查看数据分布情况
-- 1. 查看各Segment的数据分布
SELECT
gp_segment_id as 节点ID,
COUNT(*) as 数据行数,
ROUND(COUNT(*) * 100.0 / SUM(COUNT(*)) OVER (), 2) as 数据占比
FROM sales_data
GROUP BY gp_segment_id
ORDER BY gp_segment_id;
-- 2. 查看大表的数据分布
SELECT
gp_segment_id as 节点ID,
COUNT(*) as 数据行数,
MIN(id) as 最小ID,
MAX(id) as 最大ID,
AVG(amount) as 平均金额,
SUM(amount) as 总金额
FROM large_sales
GROUP BY gp_segment_id
ORDER BY gp_segment_id;
-- 3. 检查数据分布均匀性
WITH segment_stats AS (
SELECT
gp_segment_id,
COUNT(*) as row_count
FROM large_sales
GROUP BY gp_segment_id
),
distribution_stats AS (
SELECT
AVG(row_count) as avg_rows,
STDDEV(row_count) as stddev_rows,
MIN(row_count) as min_rows,
MAX(row_count) as max_rows
FROM segment_stats
)
SELECT
'数据分布统计' as 统计项,
ROUND(avg_rows) as 平均行数,
ROUND(stddev_rows) as 标准差,
min_rows as 最少行数,
max_rows as 最多行数,
ROUND((max_rows - min_rows) * 100.0 / avg_rows, 2) as 分布差异百分比
FROM distribution_stats;
🎨 查询优化初体验
📈 查询性能分析
🔍 分析查询瓶颈
-- 🔧 查询性能分析和优化
-- 1. 查看当前活跃查询
SELECT
pid as 进程ID,
usename as 用户名,
datname as 数据库,
state as 状态,
now() - query_start as 运行时长,
substring(query, 1, 80) as 查询预览
FROM pg_stat_activity
WHERE state = 'active'
AND query NOT LIKE '%pg_stat_activity%'
ORDER BY query_start;
-- 2. 查看查询统计信息
SELECT
schemaname as 模式名,
relname as 表名,
n_tup_ins as 插入行数,
n_tup_upd as 更新行数,
n_tup_del as 删除行数,
n_live_tup as 活跃行数,
n_dead_tup as 死亡行数,
last_vacuum as 最后清理时间,
last_analyze as 最后分析时间
FROM pg_stat_user_tables
WHERE schemaname = 'public';
-- 3. 分析表的访问模式
SELECT
schemaname as 模式名,
relname as 表名,
seq_scan as 顺序扫描次数,
seq_tup_read as 顺序读取行数,
idx_scan as 索引扫描次数,
idx_tup_fetch as 索引获取行数,
CASE
WHEN seq_scan + idx_scan > 0
THEN ROUND(idx_scan * 100.0 / (seq_scan + idx_scan), 2)
ELSE 0
END as 索引使用率
FROM pg_stat_user_tables
WHERE schemaname = 'public'
ORDER BY seq_scan + idx_scan DESC;
🚀 简单优化示例
-- ⚡ 查询优化实例
-- 基准查询(实际耗时需测量)
EXPLAIN ANALYZE
SELECT *
FROM large_sales l
WHERE l.sale_date >= '2023-06-01'
AND l.amount > 5000
ORDER BY l.amount DESC;
-- 修改查询示例(与基准查询结果语义不同,不保证更快)
-- 1. 显式列出字段,但这里仍选择了表的全部五列
-- 2. 改变日期与金额筛选范围
-- 3. 改变排序并限制返回100行;仍然包含排序
EXPLAIN ANALYZE
SELECT
id,
product_id,
customer_id,
sale_date,
amount
FROM large_sales
WHERE sale_date BETWEEN '2023-06-01' AND '2023-12-31'
AND amount BETWEEN 5000 AND 50000
ORDER BY sale_date, amount DESC
LIMIT 100;
-- 创建索引优化查询(如果需要)
-- 注意:Greenplum中索引使用较少,主要依靠分布式扫描
CREATE INDEX idx_large_sales_date_amount
ON large_sales (sale_date, amount)
WHERE amount > 1000;
-- 验证索引效果
EXPLAIN (ANALYZE, BUFFERS)
SELECT product_id, COUNT(*), SUM(amount)
FROM large_sales
WHERE sale_date >= '2023-09-01'
AND amount > 1000
GROUP BY product_id;
📊 数据统计信息
🔍 更新表统计信息
-- 📈 维护统计信息以提升查询性能
-- 1. 查看当前统计信息状态
SELECT
schemaname,
tablename,
attname as column_name,
n_distinct as distinct_values,
correlation,
most_common_vals as common_values,
most_common_freqs as common_frequencies
FROM pg_stats
WHERE schemaname = 'public'
AND tablename IN ('sales_data', 'large_sales')
ORDER BY schemaname, tablename, attname;
-- 2. 手动更新统计信息
ANALYZE sales_data;
ANALYZE large_sales;
-- 3. 查看表的详细统计信息
SELECT
tablename as 表名,
attname as 列名,
n_distinct as 唯一值数量,
CASE
WHEN n_distinct > 0 THEN n_distinct
WHEN n_distinct < 0 THEN ABS(n_distinct) * reltuples
ELSE NULL
END as 估计唯一值,
correlation as 数据相关性,
most_common_vals[1:3] as 最常见值前3
FROM pg_stats s
JOIN pg_class c ON s.tablename = c.relname
WHERE s.schemaname = 'public'
AND s.tablename = 'large_sales'
ORDER BY attname;
-- 4. 检查数据分布倾斜
WITH skew_check AS (
SELECT
gp_segment_id,
COUNT(*) as row_count,
MIN(amount) as min_amount,
MAX(amount) as max_amount,
AVG(amount) as avg_amount
FROM large_sales
GROUP BY gp_segment_id
)
SELECT
gp_segment_id as 节点ID,
row_count as 行数,
ROUND(avg_amount, 2) as 平均金额,
min_amount as 最小金额,
max_amount as 最大金额,
ROUND(
(row_count - AVG(row_count) OVER ()) * 100.0 / AVG(row_count) OVER (),
2
) as 行数偏差百分比
FROM skew_check
ORDER BY gp_segment_id;
🎓 查询实践练习
📚 练习题目
🏆 初级练习题
-- 🎯 初级练习:基础查询技能
-- 练习1:基础统计查询
-- 题目:查询每个销售人员的订单数量和总销售额
-- 要求:按总销售额降序排列
-- 你的答案:
SELECT
salesperson as 销售人员,
COUNT(*) as 订单数量,
SUM(total_amount) as 总销售额
FROM sales_data
GROUP BY salesperson
ORDER BY 总销售额 DESC;
-- 练习2:时间范围查询
-- 题目:查询2023年1月18日之后的所有销售记录
-- 要求:显示产品名称、销售日期、数量、总金额
-- 你的答案:
SELECT
product_name as 产品名称,
sale_date as 销售日期,
quantity as 数量,
total_amount as 总金额
FROM sales_data
WHERE sale_date > '2023-01-18'
ORDER BY sale_date;
-- 练习3:条件筛选查询
-- 题目:查询总金额大于10000元的订单
-- 要求:显示所有字段,并按总金额降序排列
-- 你的答案:
SELECT *
FROM sales_data
WHERE total_amount > 10000
ORDER BY total_amount DESC;
🌟 中级练习题
-- 🎯 中级练习:分析函数和复杂查询
-- 练习4:窗口函数练习
-- 题目:为每个销售区域的订单按金额排名
-- 要求:显示区域、产品、金额、区域内排名
-- 你的答案:
SELECT
customer_region as 销售区域,
product_name as 产品名称,
total_amount as 销售金额,
RANK() OVER (
PARTITION BY customer_region
ORDER BY total_amount DESC
) as 区域内排名
FROM sales_data
ORDER BY customer_region, 区域内排名;
-- 练习5:数据透视分析
-- 题目:创建产品类别和销售区域的交叉分析
-- 要求:显示每个类别在各区域的销售额
-- 你的答案:
SELECT
category as 产品类别,
SUM(CASE WHEN customer_region = '北京' THEN total_amount ELSE 0 END) as 北京销售额,
SUM(CASE WHEN customer_region = '上海' THEN total_amount ELSE 0 END) as 上海销售额,
SUM(CASE WHEN customer_region = '深圳' THEN total_amount ELSE 0 END) as 深圳销售额,
SUM(CASE WHEN customer_region = '广州' THEN total_amount ELSE 0 END) as 广州销售额,
SUM(total_amount) as 总销售额
FROM sales_data
GROUP BY category
ORDER BY 总销售额 DESC;
-- 练习6:复杂聚合查询
-- 题目:分析大数据表中各产品的销售模式
-- 要求:包含产品ID、订单数、总销售额、平均订单、标准差
-- 你的答案:
SELECT
product_id as 产品ID,
COUNT(*) as 订单数量,
SUM(amount) as 总销售额,
ROUND(AVG(amount), 2) as 平均订单金额,
ROUND(STDDEV(amount), 2) as 金额标准差,
MIN(amount) as 最小订单,
MAX(amount) as 最大订单,
PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY amount) as 订单金额中位数
FROM large_sales
WHERE product_id <= 20
GROUP BY product_id
HAVING COUNT(*) >= 50
ORDER BY 总销售额 DESC
LIMIT 10;
🏆 高级练习题
-- 🎯 高级练习:优化和分布式特性
-- 练习7:性能优化练习
-- 题目:优化这个查询的性能
-- 原始查询(需要优化):
SELECT *
FROM large_sales l1
WHERE EXISTS (
SELECT 1
FROM large_sales l2
WHERE l2.customer_id = l1.customer_id
AND l2.amount > l1.amount
)
ORDER BY l1.amount DESC;
-- 保持结果语义的等价改写候选(是否更快需用执行计划和计时验证):
WITH customer_max_amounts AS (
SELECT
customer_id,
MAX(amount) as max_amount
FROM large_sales
GROUP BY customer_id
)
SELECT
l.id,
l.product_id,
l.customer_id,
l.sale_date,
l.amount
FROM large_sales l
JOIN customer_max_amounts c ON l.customer_id = c.customer_id
WHERE l.amount < c.max_amount
ORDER BY l.amount DESC;
-- 练习8:分布式特性验证
-- 题目:验证查询的并行执行效果
-- 要求:使用EXPLAIN分析执行计划
EXPLAIN (ANALYZE, VERBOSE, BUFFERS)
WITH monthly_sales AS (
SELECT
DATE_TRUNC('month', sale_date) as month,
product_id,
SUM(amount) as monthly_amount,
COUNT(*) as monthly_count
FROM large_sales
WHERE sale_date >= '2023-01-01'
GROUP BY DATE_TRUNC('month', sale_date), product_id
),
product_trends AS (
SELECT
product_id,
month,
monthly_amount,
LAG(monthly_amount) OVER (
PARTITION BY product_id
ORDER BY month
) as prev_month_amount,
monthly_amount - LAG(monthly_amount) OVER (
PARTITION BY product_id
ORDER BY month
) as month_growth
FROM monthly_sales
)
SELECT
product_id as 产品ID,
COUNT(CASE WHEN month_growth > 0 THEN 1 END) as 增长月数,
COUNT(CASE WHEN month_growth < 0 THEN 1 END) as 下降月数,
AVG(monthly_amount) as 平均月销售额,
MAX(month_growth) as 最大月增长额,
MIN(month_growth) as 最大月下降额
FROM product_trends
WHERE month_growth IS NOT NULL
GROUP BY product_id
HAVING COUNT(*) >= 6 -- 至少6个月数据
ORDER BY 平均月销售额 DESC
LIMIT 20;
🔧 常用查询工具和技巧
📊 系统信息查询
🔍 集群状态查询
-- 🏗️ Greenplum集群状态和配置查询
-- 1. 查看集群配置信息
SELECT
name as 配置参数,
setting as 当前值,
unit as 单位,
category as 分类,
short_desc as 描述
FROM pg_settings
WHERE category LIKE '%Greenplum%'
OR name IN ('max_connections', 'shared_buffers', 'work_mem')
ORDER BY category, name;
-- 2. 查看Segment节点信息
SELECT
content as 节点ID,
hostname as 主机名,
address as IP地址,
port as 端口,
CASE
WHEN content = -1 THEN 'Master'
ELSE 'Segment'
END as 节点类型,
CASE
WHEN preferred_role = 'p' THEN 'Primary'
WHEN preferred_role = 'm' THEN 'Mirror'
ELSE 'Unknown'
END as 角色
FROM gp_segment_configuration
ORDER BY content, preferred_role;
-- 3. 查看当前连接信息
SELECT
datname as 数据库,
usename as 用户,
client_addr as 客户端IP,
state as 状态,
COUNT(*) as 连接数
FROM pg_stat_activity
WHERE pid <> pg_backend_pid()
GROUP BY datname, usename, client_addr, state
ORDER BY 连接数 DESC;
-- 4. 查看表空间使用情况
SELECT
schemaname as 模式,
tablename as 表名,
pg_size_pretty(pg_total_relation_size(schemaname||'.'||tablename)) as 表大小,
pg_size_pretty(pg_relation_size(schemaname||'.'||tablename)) as 数据大小,
pg_size_pretty(pg_indexes_size(schemaname||'.'||tablename)) as 索引大小
FROM pg_tables
WHERE schemaname = 'public'
ORDER BY pg_total_relation_size(schemaname||'.'||tablename) DESC;
🛠️ 实用查询函数
📚 常用函数示例
-- 🔧 Greenplum实用函数集合
-- 1. 数据类型转换函数
SELECT
'数据类型转换示例' as 示例类型,
'123'::INTEGER as 字符转整数,
123::TEXT as 整数转字符,
'2023-01-15'::DATE as 字符转日期,
NOW()::DATE as 时间戳转日期,
ROUND(123.456, 2) as 数值四舍五入,
TRUNC(123.456, 1) as 数值截断;
-- 2. 字符串处理函数
SELECT
'字符串处理示例' as 示例类型,
LENGTH('Hello Greenplum') as 字符串长度,
UPPER('hello world') as 转大写,
LOWER('HELLO WORLD') as 转小写,
SUBSTRING('Greenplum', 1, 5) as 字符串截取,
REPLACE('Hello World', 'World', 'Greenplum') as 字符串替换,
CONCAT('Hello', ' ', 'Greenplum') as 字符串连接,
SPLIT_PART('apple,banana,orange', ',', 2) as 字符串分割;
-- 3. 日期时间函数
SELECT
'日期时间处理示例' as 示例类型,
NOW() as 当前时间,
CURRENT_DATE as 当前日期,
EXTRACT(YEAR FROM NOW()) as 提取年份,
EXTRACT(MONTH FROM NOW()) as 提取月份,
DATE_TRUNC('month', NOW()) as 截断到月,
NOW() + INTERVAL '7 days' as 七天后,
AGE('2023-12-31', '2023-01-01') as 计算时间差;
-- 4. 数学统计函数
SELECT
'数学统计函数示例' as 示例类型,
ABS(-123) as 绝对值,
CEIL(123.456) as 向上取整,
FLOOR(123.456) as 向下取整,
POWER(2, 3) as 幂运算,
SQRT(16) as 平方根,
RANDOM() as 随机数,
GENERATE_SERIES(1, 5) as 生成序列;
-- 5. 条件判断函数
SELECT
product_name,
total_amount,
CASE
WHEN total_amount > 15000 THEN '大额订单'
WHEN total_amount > 10000 THEN '中额订单'
ELSE '小额订单'
END as 订单级别,
COALESCE(NULL, total_amount, 0) as 空值处理,
NULLIF(total_amount, 0) as 零值转空,
GREATEST(quantity, unit_price) as 最大值,
LEAST(quantity, unit_price) as 最小值
FROM sales_data;
🎉 成功体验总结
✅ 完成成就清单
完成相应操作并核对结果后,可用以下清单确认学习成果:
- [x] 🔗 连接数据库:掌握了多种连接Greenplum的方式
- [x] 📊 执行查询:完成了第一个SQL查询并获得结果
- [x] ⚡ 体验并行:理解了MPP架构的并行执行特性
- [x] 📈 分析数据:使用聚合函数进行数据统计分析
- [x] 🔍 查看计划:学会查看和理解执行计划
- [x] 📊 分布验证:验证了数据在Segment间的分布
- [x] 🎯 性能分析:初步了解了查询性能分析方法
- [x] 💪 实践练习:完成了多个难度递增的练习题
🌟 关键收获
-
🎯 理解MPP威力
- 体验了分布式并行计算的强大能力
- 看到了数据在多个节点间的分布处理
- 学习了观察并行查询的方法;是否比其他数据库更快,需在相同数据、查询语义和资源条件下进行对照测试
-
📊 掌握基础技能
- 熟悉了Greenplum的SQL语法特性
- 学会了使用分析函数和窗口函数
- 掌握了查看执行计划的方法
-
🔧 获得实战经验
- 完成了从简单到复杂的查询实践
- 学会了基础的性能分析技巧
- 了解了常用的系统管理查询
🚀 下一步学习建议
📚 继续深入方向
-
SQL技能提升
- 学习更复杂的窗口函数
- 掌握CTE(通用表表达式)
- 练习复杂的多表关联
-
性能优化技能
- 深入学习查询优化原理
- 掌握索引使用技巧
- 学习表设计最佳实践
-
分布式特性
- 理解数据分布策略
- 学习分区表设计
- 掌握并行查询调优
🛠️ 实践项目建议
-
创建自己的分析项目
- 导入真实业务数据
- 设计分析报表
- 建立数据看板
-
性能测试项目
- 对比不同查询写法的性能
- 测试不同数据量的查询速度
- 分析瓶颈和优化方案
-
自动化脚本
- 编写数据导入脚本
- 创建定时分析任务
- 建立监控告警机制
💡 学习心得分享
🎯 初学者常见问题
| 问题类型 | 常见症状 | 解决方案 | 预防措施 |
|---|---|---|---|
| 连接问题 | 无法连接数据库 | 检查服务状态和网络 | 确认环境变量设置 |
| 权限问题 | 查询被拒绝 | 检查用户权限 | 使用正确的数据库用户 |
| 语法错误 | SQL执行失败 | 参考PostgreSQL语法 | 多练习基础SQL |
| 性能问题 | 查询很慢 | 检查执行计划 | 学习查询优化技巧 |
🌟 学习效果自评
请诚实回答以下问题,检验学习效果:
- 基础连接:我能独立连接到Greenplum数据库吗? □ 是 □ 否
- 查询编写:我能编写基本的SELECT查询吗? □ 是 □ 否
- 分组聚合:我能使用GROUP BY进行数据统计吗? □ 是 □ 否
- 执行计划:我能看懂基本的执行计划吗? □ 是 □ 否
- 并行理解:我理解MPP并行执行的原理吗? □ 是 □ 否
如果有任何"否"的回答,建议重新练习相关部分!
🎉 恭喜!你已经成功执行了第一个Greenplum查询,正式踏入了分布式数据分析的世界!接下来我们将学习更深入的SQL操作技巧。 🚀✨