1 引言
天津南大通用数据技术股份有限公司(简称:GBASE)成立于2004年,注册资金1.88亿元。公司自成立以来始终坚持自主创新,产品的核心技术及底层代码自主可控,构建了覆盖数据管理全生命周期,包括分析型、事务型、分布式事务型、云原生数据仓库等全技术栈的数据产品体系及服务解决方案。
南大通用自主研发的GBase系列数据库产品及服务范围覆盖全国33个省级行政区域。为金融、电信、政务、能源交通、国防军工等百余个行业上万家用户提供产品和服务,建立节点超过10万个,管理数据总量超过500PB,并远销美国、巴西、墨西哥等五十余个国家及地区。
GBase 8a是南大通用自主研发的分布式数据库产品, 包含GBase 8a(OLAP)、GBase 8a(OLTP)、GBase 8a(GCDW)、GBase 8a(GCVD)、GBase 8a(GDOM)、GBase 8a(HD)和 GBase 8a (UP) 产品组件, 各个产品组件可以独立部署,也可以组合部署,通过积木式的组合方式满足不同应用场景的数据库需求,广泛应用于金融、电信、政务、能源等关键行业。
1.1 Gbase8a数据库的基本概念
Gbase8a是一款面向海量数据存储与分析的分布式列式数据库管理系统,其核心设计围绕高性能、高扩展性、高可用性展开,适用于大数据时代的复杂分析场景。以下从核心概念、技术原理、关键组件等维度进行全面解析:
1.1.1 核心概念
- 列式存储(Columnar Storage)
- 定义:数据按列而非行存储,同一列的数据连续存放。
- 优势 :
- 分析效率高:查询仅需读取相关列,减少I/O开销(例如:统计销售额时只需读取"金额"列)。
- 压缩率高:同列数据类型一致,压缩比可达1:5至1:20(例如:日期列可压缩为整数存储)。
- 对比行式存储:传统关系型数据库(如MySQL)采用行式存储,适合事务处理,但分析场景下效率较低。
- 分布式架构(Distributed Architecture)
- Shared Nothing模式:计算与存储节点独立,无共享资源,避免单点瓶颈。
- 扩展性:支持千节点级集群,通过横向扩展(Scale-Out)应对PB级数据规模。
- 容错性:节点故障时,数据自动重分布,保障服务连续性。
- 大规模并行处理(MPP, Massively Parallel Processing)
- 原理:将查询任务拆分为多个子任务,并行分发到集群节点执行,结果汇总返回。
- 效果:查询性能随节点数线性提升(例如:10节点集群查询速度是单节点的10倍)。
- 高可用性(High Availability)
- 数据冗余:支持副本(Replica)或纠删码(Erasure Coding)机制,确保数据不丢失。
- 故障切换:主节点故障时,备用节点自动接管,业务无感知。
1.1.2 关键组件
- 协调节点(Coordinator Node)
- 角色:接收用户SQL请求,解析并生成执行计划,分发任务到计算节点。
- 类比:类似餐厅的"服务员",负责接收订单并协调后厨(计算节点)工作。
- 计算节点(Compute Node)
- 角色:执行查询任务,处理本地数据,返回中间结果。
- 类比:后厨的"厨师",负责烹饪(计算)分配的菜品(任务)。
- 存储节点(Storage Node)
- 角色:存储数据文件,支持列式存储与压缩。
- 类比:餐厅的"仓库",存放食材(数据)。
- 元数据管理(Metadata Management)
- 内容:记录表结构、分区信息、节点分布等元数据。
- 作用:确保协调节点能快速定位数据位置。
1.1.3 核心特性与原理
- 智能压缩与编码
- 算法:支持字典压缩、游程编码(RLE)、位图压缩等。
- 示例 :
- 字典压缩:将"北京、上海、广州"编码为1、2、3,节省存储空间。
- 游程编码:连续重复值(如"0,0,0,1,1")压缩为"3个0,2个1"。
- 分区与分片(Partition & Sharding)
- 分区:按时间、范围等逻辑划分数据(例如:按日期分区,每天一个分区)。
- 分片:将分区数据分散到不同节点(例如:分区1存于节点A,分区2存于节点B)。
- 效果:加速查询(仅扫描相关分区)并提升并行度。
- 向量化执行(Vectorized Execution)
- 原理:批量处理数据(如一次处理1000行),减少CPU缓存未命中。
- 对比:传统行式处理一次处理一行,效率较低。
- 混合负载支持
- OLAP与轻量级OLTP:支持复杂分析查询(如聚合、关联)及高并发事务(如实时写入)。
- 应用场景:金融交易监控(实时写入+实时分析)。
1.1.4 与关系型数据库的对比
特性 | Gbase8a(列式) | 传统关系型数据库(行式) |
---|---|---|
存储方式 | 按列存储 | 按行存储 |
适用场景 | 分析型查询(如报表、数据挖掘) | 事务处理(如订单、用户信息) |
压缩率 | 高(1:5~1:20) | 低(通常1:1~1:3) |
扩展性 | 横向扩展(Scale-Out) | 纵向扩展(Scale-Up,加CPU/内存) |
查询性能 | 复杂查询快,简单查询可能较慢 | 简单查询快,复杂查询效率低 |
1.1.5 典型应用场景
- 数据仓库与集市
- 替代Oracle、Teradata等传统数据仓库,支持PB级数据存储与分析。
- 实时风控与反欺诈
- 在金融行业,实时分析交易数据,识别异常行为(如高频交易、异地登录)。
- 物联网(IoT)数据存储
- 处理海量设备数据(如传感器日志),支持实时趋势分析。
- 日志分析与用户行为分析
- 存储并分析用户点击流、操作日志,优化产品体验。
1.2 Gbase8a技术架构演进
Gbase8a的技术架构演进经历了列存数据库阶段、MPP+列存数据库阶段和逻辑数仓(LDW)阶段,每个阶段都在前一个阶段的基础上进行功能增强和架构优化,以适应不断增长的数据处理需求。以下是具体演进过程:
- 列存数据库阶段:这是Gbase8a MPP Cluster产品核心功能特性的新生阶段,包括列存储特性、数据压缩存储特性、智能索引特性和并行执行特性等。南大通用公司自2008年开始研发面向大数据分析领域的列存储分析型数据库,启动Gbase8a MPP Cluster产品基础版本的研发工作,并在2009年6月正式发布Gbase8a单机版。这一阶段的架构与传统的关系型数据库架构相近,但采用了列存储方式,显著提升了分析类查询语句的性能。
- MPP+列存数据库阶段:这是Gbase8a MPP Cluster产品成长壮大和成熟阶段,历经了多个大版本的迭代演进。在V8.3版本中,以Gbase8a单机版本为基础,研发大规模分布式集群数据库,实现了分布式SQL、高速分布式加载、高可用、负载均衡、在线扩容、备份/恢复功能特性,并在2011年5月正式发布Gbase8a MPP Cluster的第一个版本。随后的功能演进,分别完成了V8.5和V8.6两个大的产品版本迭代,支持了更大规模的节点集群,并提供了异构数据集成能力。这一阶段,Gbase8a MPP Cluster产品逐渐成为一款成熟的国产OLAP数据库,服务于电信、金融和政企等行业客户。
- 逻辑数仓(LDW)阶段:这是Gbase8a MPP Cluster产品全面超越国外同类竞品阶段。2018年研发Gbase8a v9大规模虚拟集群版本,支持1000节点以上集群规模,支持虚拟集群、支持分区表等。2019年面向国产服务器进行深度优化,研发V9.5版本,针对国产生态进行深度适配和优化,支持多种国产芯片和操作系统。此外,Gbase8a V9.5版本在性能、功能、安全等方面做了大量改进,完善了CBO优化器,在小文件批量加载、高并发查询、OLAP分析函数等性能均有成倍的提升。这一阶段,Gbase8a的技术架构更加灵活和高效,能够更好地满足大规模数据分析的需求。
1.3 Gbase8a的核心技术优势
Gbase8a作为一款分布式分析型数据库,其核心技术优势围绕高性能、高扩展性、高可用性、国产化适配四大核心展开,以下从技术维度进行结构化总结:
1.3.1 列式存储与智能压缩
- 核心原理 :
- 数据按列存储,而非传统行式存储,同一列数据连续存放,减少I/O开销。
- 支持多种压缩算法(如字典编码、游程编码、位图压缩),压缩比可达1:5~1:20。
- 优势体现 :
- 查询性能提升 :分析类查询(如聚合、统计)仅需扫描相关列,速度提升10倍以上。
- 存储成本降低 :同等数据量下,存储空间节省50%~90%。
- 案例类比 :
- 传统行式存储如"按行翻书",需逐页查找;列式存储如"按章节翻书",直接定位目标内容。
1.3.2 分布式架构与MPP并行计算
- 核心原理 :
- 采用Shared Nothing架构,计算与存储节点独立,无共享资源,避免单点瓶颈。
- 支持千节点级集群扩展,通过**MPP(大规模并行处理)**将查询任务拆分为子任务,并行执行。
- 优势体现 :
- 线性扩展能力:节点数增加,查询性能随之线性提升(如10节点集群速度是单节点的10倍)。
- 高吞吐量:支持PB级数据规模下的复杂分析,满足大数据场景需求。
- 技术对比 :
- 传统数据库(如Oracle RAC)依赖共享存储,扩展性受限;Gbase8a通过分布式架构突破瓶颈。
1.3.3 高可用性与容灾能力
- 核心原理 :
- 数据多副本存储(默认3副本),支持跨节点、跨数据中心容灾。
- 提供故障自动检测与切换机制,节点故障时业务无感知。
- 优势体现 :
- 业务连续性保障:RTO(恢复时间目标)<30秒,RPO(恢复点目标)=0,确保数据零丢失。
- 满足合规要求:支持金融、政务等行业的等保2.0、信创等安全标准。
- 场景示例 :
- 金融交易监控系统需7×24小时运行,Gbase8a通过多副本和自动切换保障服务不中断。
1.3.4 混合负载与实时分析能力
- 核心原理 :
- 兼顾OLAP(分析型) 与**轻量级OLTP(事务型)**需求,支持高并发写入与实时查询。
- 通过向量化执行引擎 和智能缓存优化,提升混合负载下的性能。
- 优势体现 :
- 实时风控:在金融场景中,支持每秒万级交易写入,同时实时分析风险行为。
- 用户行为分析:在互联网场景中,支持日志实时写入与用户画像实时更新。
- 技术对比 :
- 传统OLAP数据库(如Teradata)难以支持实时写入;Gbase8a通过混合负载能力填补空白。
1.3.5 国产化适配与生态兼容
- 核心原理 :
- 深度适配国产芯片(如鲲鹏、飞腾、海光)、操作系统(如麒麟、统信)及中间件。
- 提供全栈信创解决方案,支持从底层硬件到上层应用的自主可控。
- 优势体现 :
- 政策合规:满足政府、金融等行业的国产化替代要求。
- 生态开放:兼容主流BI工具(如Tableau、Power BI)和大数据平台(如Hadoop、Spark)。
- 行业案例 :
- 某国有银行通过Gbase8a替代Oracle,实现核心系统国产化,TCO(总拥有成本)降低60%。
1.3.6 智能优化与易用性
- 核心原理 :
- 内置基于成本的优化器(CBO),自动选择最优执行计划。
- 提供图形化管理工具(如GCluster Manager),简化集群部署、监控与运维。
- 优势体现 :
- 开发效率提升:兼容标准SQL语法,降低学习成本。
- 运维成本降低:支持自动化扩容、故障自愈,减少人工干预。
- 用户反馈 :
- 某电信运营商通过Gbase8a的智能优化器,将复杂查询性能提升3倍以上。
1.3.7 总结
维度 | 优势 |
---|---|
性能 | 列式存储+MPP并行计算,查询性能提升10倍以上 |
扩展性 | 支持千节点级集群,PB级数据规模下线性扩展 |
高可用 | 多副本+故障自动切换,保障业务连续性 |
混合负载 | 兼顾OLAP与轻量级OLTP,支持实时分析与高并发写入 |
国产化 | 深度适配信创生态,满足自主可控要求 |
易用性 | 兼容标准SQL,提供图形化管理工具,降低开发与运维成本 |
2 Gbase8a SQL编程入门
2.1 规范介绍
2.1.1 标识符语法规则
数据库、表、列和别名等对象的名称都称为标识符,这部分描述GBase8aMPP Cluster中标识符允许的语法规则。

- 注意
- 除了表内注明的限制,标识符不可以包含ASCII(0)或ASCII(255)。数据库、表和列名不应以空格结尾。
- 如果标识符是一个限制词或包含特殊字符,当用户使用它时,必须总是用``引用它,比如:SELECT*FROM`select`.id>100。
- 如果标识符长度超过最大长度限制,数据库、表、列、视图、存储过程的命令将报错,而别名将会截断至256个字符进行显示。
- 实际应用系统中,标识符不得使用GBase8aMPPCluster的保留字,也不能包含特殊字符。
2.1.2 标识符限定词
GBase 8a MPPCluster 允许名称由一个或多个标识符组成。组合名称的各个组成成分应该用英文句号字符"."分隔开。组合名称的开始部分做为限定词来使用,它影响了上下文中后面的标识符的解释。
在GBase8a MPPCluster 中,用户可以使用下列表格中的任一种方式引用一个列:

组合标识符如果需要引用,则标识符的各部分都要各自引用,而不是把组合标识符作为一个整体来引用。例如:`gs-table`.`gs-column`合法, `gs-table.gs-column`不合法。
在 一条语句的列引用中,不需要明确指定一个 table_name、database_name.table_name 或 vc_name.database_name.table_name 前缀,除非这个引用存在二义性。例如:
1)假设表t1和t2均包含一个字段c,当用一个使用了t1和t2的SELECT检索c时,在这种情况下,字段c存在二义性,因为它在这个语句所使用的表不是唯一的,因而必须通过写出t1.c或t2.c来指明用户所需的是哪个表。
2)如果从数据库db1的表t和数据库db2的表t中检索,用户必须用db1.t.col_name和db2.t.col_name 来指定引用哪个库表的列。
3)如果从vc1中数据库db1的表t和vc2中数据库db2的表t中检索,用户必须用vc1.db1.t.col_name 和 vc2.db2.t.col_name 来指定引用哪个库表的列
2.1.3 注释语法
GBase 8a MPPCluster 支持三种注释风格:
- "#":单行注释;
- "--":单行注释,以"--"开头到该行结束为注释内容。注意"--"(引导号)注释要求第二个引导号后至少跟着一个空格;
- "/*注释内容*/":这种注释支持注释内容为一行或者连续的多行,还支持注释内容在行中间。/**/这个封闭的序列不一定在同一行表示,因此该语法允许多行注释。
注意
- "--"(引导号)注释风格要求第二个引导号后至少跟着一个空格。这个语法和标准的SQL注释风格略有不同。
2.1.4 遵循的SQL标
默认支持SQL-92的主要特性。
2.1.5 数据类型
GBase 8a MPP Cluster 支持 SQL-92 中定义的绝大多数数据类型,同时也支持
SQL99 和SQL2003中定义的大部分数据类型。
GBase 8a MPPCluster 支持的数据类型,如下表所示:
|--------|-----------|
| GBase 8a MPPCluster 的数据类型 ||
| 数值型 | TINYINT |
| 数值型 | SMALLINT |
| 数值型 | INT |
| 数值型 | BIGINT |
| 数值型 | FLOAT |
| 数值型 | DOUBLE |
| 数值型 | DECIMAL |
| 数值型 | NUMERIC |
| 字符型 | CHAR |
| 字符型 | VARCHAR |
| 字符型 | TEXT |
| 二进制类型 | BLOB |
| 二进制类型 | LONGBLOB |
| 日期和时间型 | DATE |
| 日期和时间型 | DATETIME |
| 日期和时间型 | TIME |
| 日期和时间型 | TIMESTAMP |
注意:以下数据类型在gcluster层与gnode层范围有差异,gcluster层支持范围较gnode 支持范围大,建议应用开发中以较小的支持范围为准,便于应用在gcluster和gnode 层的统一处理。本章节数据类型的范围描述统一为gcluster和gnode共同支持的范围,即以较小的gnode范围为准
2.1.6 运算符与常用函数
2.1.6.1 运算符
Gbase8a支持标准SQL运算符,包括算术、比较、逻辑和位运算符。
- 算术运算符
+
,-
,*
,/
,%
(取模)
sql
SELECT salary, salary * 0.1 AS 奖金 FROM employees;
- 比较运算符
=
,<>
/!=
,>
,<
,>=
,<=
,BETWEEN
,IN
,LIKE
sql
SELECT * FROM employees
WHERE salary BETWEEN 15000 AND 25000;
- 逻辑运算符
AND
,OR
,NOT
sql
SELECT * FROM employees
WHERE dept = 'Technology' AND salary > 20000;
- 位运算符
&
,|
,^
,~
(按位与、或、异或、非)
sql
SELECT 4 & 2; -- 结果:0
优先级 :算术运算符 > 比较运算符 > 逻辑运算符,可通过()
调整优先级。
2.1.6.2 常用函数
Gbase8a支持丰富的内置函数,覆盖字符串、数值、日期等类型。
- 字符串函数
CONCAT(str1, str2)
: 拼接字符串
sql
SELECT CONCAT(name, '_', dept) FROM employees;
SUBSTRING(str, start, length)
: 截取子串
sql
SUBSTRING(str, start, length): 截取子串
UPPER(str)
, LOWER(str)
: 转换大小写
sql
SELECT UPPER(name) FROM employees;
- 数值函数
ROUND(num, decimals)
: 四舍五入
sql
SELECT ROUND(salary, -3) FROM employees; -- 保留到千位
ABS(num)
: 绝对值
sql
SELECT ABS(-100) FROM employees;
- 日期函数
CURRENT_DATE
: 当前日期
sql
SELECT CURRENT_DATE;
DATE_ADD(date, INTERVAL expr unit)
: 日期加减
sql
SELECT DATE_ADD(hire_date, INTERVAL '3 MONTH') FROM employees;
- 聚合函数
COUNT()
, SUM()
, AVG()
, MAX()
, MIN()
sql
SELECT dept, COUNT(*) AS 人数, AVG(salary) AS 平均薪资
FROM employees
GROUP BY dept;
- 窗口函数
ROW_NUMBER(), RANK(), NTILE()
sql
SELECT
emp_id,
salary,
RANK() OVER (ORDER BY salary DESC) AS 全局排名
FROM employees;
2.2 DDL语法
此处不再赘述,请参考:第五章 SQL编程系列-Oracle数据库从入门到进阶 2.1 SQL基础
2.3 数据库管理工具
2.3.1 官方管理工具
2.3.1.1 GCluster Manager(集群管理工具)
- 核心功能 :
- 集群监控:实时展示节点状态(CPU、内存、磁盘I/O)、查询性能(QPS、TPS)、任务进度等。
- 故障诊断:自动检测节点故障、网络延迟、数据倾斜等问题,提供日志分析与报警功能。
- 扩容与缩容:支持在线添加/删除节点,自动数据重平衡,保障业务连续性。
- 适用场景 :
- 金融级高可用集群的日常运维。
- 电商大促前的资源弹性扩展。
2.3.1.2 GBase Migration Toolkit(数据迁移工具)
- 核心功能 :
- 异构数据迁移:支持从Oracle、MySQL、SQL Server等数据库迁移到Gbase8a,自动转换数据类型与SQL语法。
- 全量/增量迁移:提供全量数据导入与基于日志的增量同步,最小化业务停机时间。
- 数据校验:迁移后自动比对源端与目标端数据一致性,确保零丢失。
- 适用场景 :
- 传统数据库向Gbase8a的国产化替代。
- 多数据源合并分析(如合并MySQL业务数据与Hadoop日志数据)。
2.3.1.3 GBaseDataStudio(全链路数据库管理)
1 功能说明
- 开发功能 :
- SQL编辑器:支持语法高亮、自动补全、多标签页查询,内置Gbase8a函数模板库。
- 调试器:支持逐行执行、断点调试复杂存储过程,实时查看变量状态。
- 版本控制:集成Git,实现SQL脚本的版本管理与团队协作。
- 运维功能 :
- 集群监控:可视化展示节点状态、查询性能、资源使用率,支持自定义报警阈值。
- 备份恢复:一键执行全量/增量备份,支持备份文件加密与压缩,可验证备份集完整性。
- 权限管理:基于角色的访问控制(RBAC),支持批量授权与审计日志追踪。
- 优化功能 :
- 执行计划分析:图形化展示查询计划,标记全表扫描、数据倾斜等性能瓶颈。
- 索引推荐:根据查询模式自动生成索引优化建议,支持模拟执行验证效果。
- 慢查询治理:捕获历史慢查询,提供重写建议(如避免子查询、减少JOIN操作)。
2 使用简介
- 用户数据库 - 虚拟集群 - 新建数据库

- 新建表

- 设置表属性

注意:创建为NOCOPIES表,慎用!无副本、无分片备份信息,有一定的风险。
- 设置列属性

- 查看表属性

2.3.2 第三方工具兼容性
Gbase8a通过标准协议与接口,兼容主流第三方数据库管理工具,降低用户学习成本:
1. 通用客户端工具
- Navicat:支持通过JDBC/ODBC连接Gbase8a,提供可视化表设计、数据导入导出、会话管理等功能。
- DBeaver:开源多数据库工具,支持Gbase8a的元数据浏览、SQL执行、ER图生成等。
3 Gbase8a SQL编程进阶
3.1 函数
3.1.1 控制流函数
3.1.1.1 CASE函数
- 语法1 -简单 CASE 表达式
简单 CASE 表达式将一个表达式与一组简单表达式进行比较,语法如下:
sql
CASE expression
WHEN value1 THEN result1
WHEN value2 THEN result2
...
[ELSE else_result]
END
函数说明
逐一匹配,当满足value=compare-value 时,返回对应的result,如果未找到匹配项,则返回ELSE后的result。如果没有ELSE子句,默认返回NULL。
如果条件中compare-value 有重叠,即value值满足多个compare-value条件时,只返回第一个满足的值。
示例 1:根据员工部门编号返回部门名称
sql
SELECT
employee_id,
employee_name,
department_id,
CASE department_id
WHEN 10 THEN '财务部'
WHEN 20 THEN '人力资源部'
WHEN 30 THEN '销售部'
WHEN 40 THEN '技术部'
ELSE '其他部门'
END AS department_name
FROM employees;
示例 2:根据订单状态代码返回状态描述
sql
SELECT
order_id,
order_date,
status_code,
CASE status_code
WHEN 'P' THEN '待支付'
WHEN 'S' THEN '已发货'
WHEN 'D' THEN '配送中'
WHEN 'C' THEN '已完成'
ELSE '未知状态'
END AS status_description
FROM orders;
- 语法2 -搜索 CASE 表达式
搜索 CASE 表达式允许在 WHEN 子句中编写复杂的条件表达式,语法如下:
sql
CASE
WHEN condition1 THEN result1
WHEN condition2 THEN result2
...
[ELSE else_result]
END
函数说明
逐一判断,当condition为TRUE时,返回对应的result,如果condition全为FALSE,则返回ELSE后的result。如果没有ELSE子句,默认返回NULL。
一个CASE表达式的默认返回值类型是所有返回值的相容集合类型,具体情况视其所在语境而定:
如用在字符串语境中,则返回结果为字符串;
如用在数字语境中,则返回结果为十进制值的实数值或整数值。
示例 1:根据销售额分级
sql
SELECT
salesperson_id,
salesperson_name,
total_sales,
CASE
WHEN total_sales > 1000000 THEN '钻石销售'
WHEN total_sales > 500000 THEN '黄金销售'
WHEN total_sales > 200000 THEN '白银销售'
WHEN total_sales > 0 THEN '普通销售'
ELSE '无业绩'
END AS sales_level
FROM sales_performance;
示例 2:多条件组合判断
sql
SELECT
student_id,
student_name,
score,
CASE
WHEN score >= 90 THEN 'A'
WHEN score >= 80 AND score < 90 THEN 'B'
WHEN score >= 70 AND score < 80 THEN 'C'
WHEN score >= 60 AND score < 70 THEN 'D'
WHEN score < 60 AND score >= 0 THEN 'F'
ELSE '无效分数'
END AS grade
FROM student_scores;
示例 3:在 UPDATE 语句中使用 CASE WHEN
sql
UPDATE products
SET price = CASE
WHEN stock_quantity > 100 THEN price * 0.9 -- 库存多打9折
WHEN stock_quantity < 10 THEN price * 1.1 -- 库存少涨价10%
ELSE price
END,
discount_flag = CASE
WHEN stock_quantity > 100 THEN 1
ELSE 0
END;
示例 4:在 ORDER BY 中使用 CASE WHEN
sql
SELECT
product_id,
product_name,
category,
price
FROM products
ORDER BY
CASE category
WHEN '电子产品' THEN 1
WHEN '家居用品' THEN 2
WHEN '服装' THEN 3
ELSE 4
END,
price DESC;
- 注意事项
- CASE 表达式会按顺序评估条件,一旦满足某个 WHEN 子句,就会返回对应的结果,不再评估后续条件
- 如果没有匹配的条件且没有 ELSE 子句,CASE 表达式将返回 NULL
- 所有 THEN 子句返回的结果数据类型应该兼容
- GBase 8a 的 CASE WHEN 语法与标准 SQL 一致,可以在 SELECT、WHERE、ORDER BY、GROUP BY、HAVING 等子句中使用
3.1.1.2 DECODE函数
DECODE 函数是 GBase 8a 数据库中的一个条件表达式函数,它类似于 CASE 表达式,但语法更简洁。DECODE 函数通过比较表达式的值与一组搜索值,返回对应的结果。
- DECODE 函数语法
sql
DECODE(expression, search1, result1,
[search2, result2, ...],
[default_result])
参数说明:
expression
:要比较的表达式或列名searchN
:与 expression 比较的值resultN
:当 expression 等于 searchN 时返回的结果default_result
:可选参数,当没有匹配项时返回的默认值
示例 1:基本用法 - 员工部门转换
sql
SELECT
employee_id,
employee_name,
department_id,
DECODE(department_id,
10, '财务部',
20, '人力资源部',
30, '销售部',
40, '技术部',
'其他部门') AS department_name
FROM employees;
示例 2:性别代码转换
sql
SELECT
student_id,
student_name,
DECODE(gender,
'M', '男',
'F', '女',
'未知') AS gender_name
FROM students;
示例 3:多级评分转换
sql
SELECT
exam_id,
student_id,
score,
DECODE(SIGN(score-90), 1, 'A',
DECODE(SIGN(score-80), 1, 'B',
DECODE(SIGN(score-70), 1, 'C',
DECODE(SIGN(score-60), 1, 'D', 'F')))) AS grade
FROM exam_results;
示例 4:在 UPDATE 语句中使用 DECODE
sql
UPDATE products
SET discount_rate = DECODE(category_id,
1, 0.1, -- 类别1打9折
2, 0.15, -- 类别2打85折
3, 0.2, -- 类别3打8折
0), -- 其他类别不打折
stock_status = DECODE(SIGN(stock_quantity-10),
1, '充足',
-1, '短缺',
'临界');
示例 5:在 ORDER BY 中使用 DECODE
sql
SELECT
product_id,
product_name,
category
FROM products
ORDER BY DECODE(category,
'电子产品', 1,
'家居用品', 2,
'服装', 3,
'食品', 4,
5),
product_name;
示例 6:复杂条件判断(DECODE 嵌套)
sql
SELECT
employee_id,
employee_name,
salary,
DECODE(SIGN(salary-10000),
1, DECODE(SIGN(salary-20000),
1, '高级',
'中级'),
'初级') AS salary_level
FROM employees;
- DECODE 与 CASE WHEN 的对比
- 简洁性:DECODE 语法更简洁,特别是对于简单的值映射
- 功能:CASE WHEN 更灵活,可以处理复杂条件(如 >, <, BETWEEN 等)
- 可读性:对于简单映射,DECODE 更易读;对于复杂逻辑,CASE WHEN 更清晰
- 等价转换示例
以下 DECODE 语句:
sql
SELECT DECODE(department_id, 10, '财务', 20, '人事', '其他') FROM employees;
等价于 CASE 表达式:
sql
SELECT
CASE department_id
WHEN 10 THEN '财务'
WHEN 20 THEN '人事'
ELSE '其他'
END
FROM employees;
- 注意事项
- DECODE 在 GBase 8a 中是大小写敏感的
- 如果没有匹配项且未指定默认值,DECODE 返回 NULL
- DECODE 只能进行等值比较,不能用于范围判断
- 对于复杂的条件逻辑,建议使用 CASE WHEN 表达式
3.1.2 字符串函数
3.1.2.1 REPLACE 函数
REPLACE 函数是 GBase 8a 数据库中用于字符串替换的函数,它可以将字符串中指定的子串替换为新的子串。
- REPLACE 函数语法
sql
REPLACE(original_string, search_string, [replacement_string])
参数说明:
original_string
:原始字符串或列名search_string
:要查找的子字符串replacement_string
:可选参数,替换后的字符串。如果省略,则删除所有找到的 search_string
示例 1:基本字符串替换
sql
-- 将字符串中的 'abc' 替换为 'XYZ'
SELECT REPLACE('abcdefgabc', 'abc', 'XYZ') AS replaced_string;
-- 结果: 'XYZdefgXYZ'
示例 2:替换表中的列值
sql
-- 将产品描述中的 '旧型号' 替换为 '新款'
SELECT
product_id,
product_name,
description,
REPLACE(description, '旧型号', '新款') AS new_description
FROM products;
示例 3:删除字符串中的特定字符(省略替换字符串)
sql
-- 删除电话号码中的连字符
SELECT
customer_id,
phone,
REPLACE(phone, '-', '') AS clean_phone
FROM customers;
-- 如果 phone 是 '138-1234-5678',结果为 '13812345678'
示例 4:在 UPDATE 语句中使用 REPLACE
sql
-- 更新产品描述,将 '免费送货' 替换为 '包邮'
UPDATE products
SET description = REPLACE(description, '免费送货', '包邮')
WHERE description LIKE '%免费送货%';
示例 5:替换 JSON 字符串中的内容
sql
-- 替换 JSON 字符串中的键名
SELECT
config_id,
config_data,
REPLACE(config_data, '"old_key":', '"new_key":') AS updated_config
FROM system_configs;
示例 6:替换特殊字符
sql
-- 替换产品名称中的特殊字符
SELECT
product_id,
product_name,
REPLACE(REPLACE(product_name, '&', '和'), '#', '号') AS clean_name
FROM products;
示例 7:替换 XML 标签内容
sql
-- 替换 XML 数据中的特定标签值
SELECT
record_id,
xml_content,
REPLACE(xml_content, '<status>old</status>', '<status>new</status>') AS updated_xml
FROM xml_records;
示例 8:批量替换多种内容
sql
-- 嵌套使用 REPLACE 函数进行多次替换
SELECT
article_id,
content,
REPLACE(
REPLACE(
REPLACE(content, 'GBase', 'GBase 8a'),
'数据库', '数据库管理系统'
),
'高级', '企业级'
) AS enhanced_content
FROM articles;
- 注意事项
- REPLACE 函数是区分大小写的,除非数据库或列使用不区分大小写的排序规则
- 如果要替换的字符串不存在,则返回原始字符串不变
- REPLACE 函数会替换所有出现的搜索字符串,而不仅仅是第一个
- 对于大量数据的替换操作,考虑性能影响,特别是在 WHERE 子句中使用时
- 性能提示
对于大数据量的替换操作,特别是在 UPDATE 语句中:
- 先测试 SELECT 语句确认替换效果
- 考虑分批更新以减少锁表时间
- 在非高峰期执行大规模数据更新
3.1.2.2 正则表达式函数
REGEXP_REPLACE 是 GBase 8a 数据库中用于正则表达式替换的强大函数,它允许您使用正则表达式模式进行复杂的字符串替换操作。
- REGEXP_REPLACE 函数语法
sql
REGEXP_REPLACE(source_string, pattern, [replacement_string], [position], [occurrence], [match_parameter])
参数说明:
source_string
:要搜索的原始字符串或列名pattern
:正则表达式模式replacement_string
:可选,替换后的字符串(默认为空字符串)position
:可选,开始搜索的位置(默认为1)occurrence
:可选,要替换的匹配项出现次数(0表示全部替换,默认为0)match_parameter
:可选,修改匹配行为的参数(如'i'表示不区分大小写)
示例1:基本正则替换
sql
-- 将所有数字替换为#
SELECT REGEXP_REPLACE('abc123def456', '[0-9]', '#') AS result;
-- 结果: 'abc###def###'
示例2:替换手机号中间四位
sql
-- 将手机号中间4位替换为****
SELECT
user_id,
phone,
REGEXP_REPLACE(phone, '(\\d{3})\\d{4}(\\d{4})', '\\1****\\2') AS masked_phone
FROM user_profiles;
-- 输入: '13812345678' → 输出: '138****5678'
示例3:格式化日期字符串
sql
-- 将YYYYMMDD格式日期转换为YYYY-MM-DD
SELECT
event_id,
event_date,
REGEXP_REPLACE(event_date, '(\\d{4})(\\d{2})(\\d{2})', '\\1-\\2-\\3') AS formatted_date
FROM events;
-- 输入: '20231015' → 输出: '2023-10-15'
示例4:删除所有非数字字符
sql
-- 从字符串中提取纯数字
SELECT
order_id,
order_code,
REGEXP_REPLACE(order_code, '[^0-9]', '') AS pure_numbers
FROM orders;
-- 输入: 'ORD-2023-ABC-456' → 输出: '2023456'
示例5:替换特定模式的单词
sql
-- 将所有以"test"开头的单词替换为"example"
SELECT
doc_id,
content,
REGEXP_REPLACE(content, '\\btest\\w*\\b', 'example') AS modified_content
FROM documents;
-- 输入: 'this is a test and testing' → 输出: 'this is a example and example'
示例6:不区分大小写替换
sql
-- 不区分大小写替换
SELECT
product_id,
description,
REGEXP_REPLACE(description, 'color', 'colour', 1, 0, 'i') AS uk_spelling
FROM products;
-- 会替换'Color'、'COLOR'等变体
示例7:格式化金额显示
sql
-- 将数字格式化为带千位分隔符的金额
SELECT
transaction_id,
amount,
REGEXP_REPLACE(TO_CHAR(amount, '999999999.99'), '([0-9])([0-9]{3})([0-9]{3},)?(\\.[0-9]+)?$', '\\1,\\2\\3\\4') AS formatted_amount
FROM transactions;
-- 输入: 1234567.89 → 输出: '1,234,567.89'
示例8:在UPDATE语句中使用
sql
-- 更新电子邮件地址,将旧域名替换为新域名
UPDATE users
SET email = REGEXP_REPLACE(email, '@old\\.com$', '@new.com')
WHERE email REGEXP '@old\\.com$';
示例9:使用反向引用重组字符串
sql
-- 交换名字和姓氏的位置
SELECT
user_id,
full_name,
REGEXP_REPLACE(full_name, '([A-Za-z]+) ([A-Za-z]+)', '\\2, \\1') AS name_reversed
FROM users;
-- 输入: 'John Smith' → 输出: 'Smith, John'
示例10:条件性替换
sql
-- 只在特定模式后替换
SELECT
log_id,
log_message,
REGEXP_REPLACE(log_message, '(ERROR: ).*', '\\1[详细信息已隐藏]') AS sanitized_log
FROM system_logs
WHERE log_message REGEXP 'ERROR: ';
示例11:处理多行文本
sql
-- 替换多行文本中的特定模式(GBase 8a可能需要设置特定模式)
SELECT
doc_id,
REGEXP_REPLACE(document_text, '^--.*$', '', 1, 0, 'm') AS without_comments
FROM documents;
-- 删除以--开头的注释行
- 注意事项
- GBase 8a 的正则表达式实现基于 POSIX 标准,某些高级特性可能不可用
- 复杂的正则表达式可能影响查询性能
- 反斜杠()在字符串中需要转义,通常需要使用双反斜杠(\)
- 不同版本的 GBase 8a 可能对正则表达式的支持程度不同
- 性能优化建议
- 对于大数据量表,先在小样本上测试正则表达式
- 尽可能使用更简单的模式
- 考虑在应用层处理特别复杂的正则替换
- 为正则表达式查询创建适当的函数索引(如果支持)
3.1.3 日期函数
3.1.3.1 日期提取函数
- YEAR() / MONTH() / DAY()
- 示例
sql
-- 从日期中提取年、月、日
SELECT
order_date,
YEAR(order_date) AS order_year,
MONTH(order_date) AS order_month,
DAY(order_date) AS order_day
FROM orders;
- HOUR() / MINUTE() / SECOND()
- 示例
sql
-- 从时间中提取时、分、秒
SELECT
event_time,
HOUR(event_time) AS event_hour,
MINUTE(event_time) AS event_minute,
SECOND(event_time) AS event_second
FROM event_logs;
- DAYOFWEEK() / DAYOFMONTH() / DAYOFYEAR()
sql
-- 获取星期几、月中的第几天、年中的第几天
SELECT
appointment_date,
DAYOFWEEK(appointment_date) AS day_of_week, -- 1=周日, 2=周一,...,7=周六
DAYOFMONTH(appointment_date) AS day_of_month,
DAYOFYEAR(appointment_date) AS day_of_year
FROM appointments;
4.WEEK() / WEEKOFYEAR()
sql
-- 获取一年中的第几周
SELECT
sale_date,
WEEK(sale_date) AS sale_week,
WEEKOFYEAR(sale_date) AS week_of_year
FROM sales;
5.QUARTER() / MONTHNAME() / DAYNAME()
sql
-- 获取季度、月份名称、星期名称
SELECT
report_date,
QUARTER(report_date) AS quarter,
MONTHNAME(report_date) AS month_name,
DAYNAME(report_date) AS day_name
FROM reports;
3.1.3.2 日期计算函数
- DATE_ADD() / ADDDATE()
sql
-- 日期加法运算
SELECT
order_date,
DATE_ADD(order_date, INTERVAL 7 DAY) AS expected_delivery,
DATE_ADD(order_date, INTERVAL 1 MONTH) AS next_month_date
FROM orders;
- DATE_SUB() / SUBDATE()
sql
-- 日期减法运算
SELECT
event_date,
DATE_SUB(event_date, INTERVAL 1 DAY) AS previous_day,
DATE_SUB(event_date, INTERVAL 1 WEEK) AS last_week_date
FROM events;
- DATEDIFF()
sql
-- 计算两个日期之间的天数差
SELECT
DATEDIFF('2023-12-31', '2023-01-01') AS days_in_year;
-- 结果: 364 (闰年365)
SELECT
order_date,
ship_date,
DATEDIFF(ship_date, order_date) AS days_to_ship
FROM orders;
- TIMESTAMPDIFF()
sql
-- 计算两个时间戳之间的差值(可指定单位)
SELECT
TIMESTAMPDIFF(HOUR, '2023-01-01 08:00:00', '2023-01-02 10:30:00') AS hours_diff;
-- 结果: 26 (小时差)
SELECT
start_time,
end_time,
TIMESTAMPDIFF(MINUTE, start_time, end_time) AS duration_minutes
FROM sessions;
- PERIOD_ADD() / PERIOD_DIFF()
sql
-- 添加月份到年月周期 / 计算两个年月周期之间的月份差
SELECT PERIOD_ADD(202311, 3); -- 结果: 202402 (2023年11月加3个月)
SELECT PERIOD_DIFF(202312, 202301); -- 结果: 11 (月份差)
3.1.3.3 日期格式化函数
- DATE_FORMAT()
sql
-- 格式化日期输出
SELECT
NOW() AS current_time,
DATE_FORMAT(NOW(), '%Y-%m-%d') AS formatted_date,
DATE_FORMAT(NOW(), '%W, %M %d, %Y') AS long_date,
DATE_FORMAT(NOW(), '%H:%i:%s') AS time_only,
DATE_FORMAT(NOW(), '%Y%m%d%H%i%s') AS timestamp_str
FROM dual;
- TIME_FORMAT()
sql
-- 格式化时间输出
SELECT
CURTIME() AS current_time,
TIME_FORMAT(CURTIME(), '%h:%i %p') AS formatted_time
FROM dual;
-- 结果示例: '02:30 PM'
3.1.3.4 日期转换函数
- STR_TO_DATE()
sql
-- 字符串转日期
SELECT STR_TO_DATE('15,11,2023', '%d,%m,%Y') AS converted_date;
-- 结果: '2023-11-15'
SELECT STR_TO_DATE('Nov 15 2023 2:30PM', '%b %d %Y %h:%i%p') AS converted_datetime;
-- 结果: '2023-11-15 14:30:00'
- UNIX_TIMESTAMP() / FROM_UNIXTIME()
sql
-- Unix时间戳转换
SELECT UNIX_TIMESTAMP('2023-11-15 14:30:00') AS timestamp;
-- 结果: 1700058600
SELECT FROM_UNIXTIME(1700058600) AS datetime;
-- 结果: '2023-11-15 14:30:00'
SELECT FROM_UNIXTIME(1700058600, '%Y年%m月%d日 %H时%i分%s秒') AS formatted_datetime;
-- 结果: '2023年11月15日 14时30分00秒'
3.1.3.5 实用查询示例
示例1:计算年龄
sql
SELECT
name,
birth_date,
TIMESTAMPDIFF(YEAR, birth_date, CURDATE()) AS age
FROM employees;
示例2:获取本季度第一天
sql
SELECT
DATE_ADD(
MAKEDATE(YEAR(CURDATE()), 1),
INTERVAL QUARTER(CURDATE())*3-3 MONTH
) AS first_day_of_quarter;
示例3:工作日计算(排除周末)
sql
SELECT
order_date,
ship_date,
(DATEDIFF(ship_date, order_date) + 1) -
(FLOOR((DATEDIFF(ship_date, order_date) + 1 +
DAYOFWEEK(order_date) + 5) / 7) * 2 -
IF(DAYOFWEEK(order_date) = 1, 1, 0) -
IF(DAYOFWEEK(ship_date) = 7, 1, 0)) AS working_days
FROM orders;
示例4:按周分组统计
sql
SELECT
YEAR(order_date) AS order_year,
WEEK(order_date) AS order_week,
COUNT(*) AS order_count,
SUM(amount) AS total_amount
FROM orders
GROUP BY YEAR(order_date), WEEK(order_date)
ORDER BY order_year, order_week;
- 注意事项
- GBase 8a 的日期函数可能与 MySQL 有差异,建议测试确认
- 日期格式字符串在不同版本中可能有所不同
- 对于大量历史数据的日期计算,考虑性能影响
- 时区处理可能需要额外配置
3.1.4 加密函数
GBase 8a 提供了一系列加密函数用于数据安全保护,主要包括哈希加密、对称加密和随机数生成等功能。以下是详细的用法示例:
3.1.4.1 AES 对称加密函数
- AES_ENCRYPT() - 数据加密
sql
-- 基本用法:使用 AES 加密数据(返回二进制数据)
SELECT AES_ENCRYPT('敏感数据', '加密密钥') AS encrypted_data;
-- 实际示例:加密存储用户身份证号
CREATE TABLE user_info (
user_id INT,
id_card VARBINARY(255), -- 存储加密后的二进制数据
name VARCHAR(50)
);
-- 插入加密数据(建议对密钥进行适当处理)
INSERT INTO user_info
VALUES (1, AES_ENCRYPT('123456199001011234', 'secret_key_2023'), '张三');
-- 查询时解密数据
SELECT
user_id,
name,
CAST(AES_DECRYPT(id_card, 'secret_key_2023') AS CHAR) AS id_card
FROM user_info;
- AES_DECRYPT() - 数据解密
sql
-- 基本用法:解密 AES 加密的数据
SELECT AES_DECRYPT(encrypted_column, '加密密钥') FROM encrypted_table;
-- 示例:解密并转换为可读格式
SELECT
user_id,
AES_DECRYPT(id_card, 'secret_key_2023') AS raw_binary_id, -- 二进制格式
CAST(AES_DECRYPT(id_card, 'secret_key_2023') AS CHAR) AS readable_id -- 字符串格式
FROM user_info;
3.1.4.2 哈希函数
- MD5() - MD5 哈希
sql
-- 计算字符串的 MD5 哈希值(32位十六进制)
SELECT MD5('password123') AS md5_hash;
-- 结果示例: '482c811da5d5b4bc6d497ffa98491e38'
-- 实际应用:存储密码哈希值
UPDATE users SET password = MD5(password) WHERE user_id = 1;
- SHA1() / SHA() - SHA-1 哈希
sql
-- 计算 SHA-1 哈希值(40位十六进制)
SELECT SHA1('secure_data') AS sha1_hash;
-- 结果示例: 'e5e9fa1ba31ecd1aa8a66e4b6fb197e02f9580f3'
-- SHA() 是 SHA1() 的别名
SELECT SHA('secure_data') AS sha_hash;
- SHA2() - SHA-2 系列哈希
sql
-- 计算 SHA-2 系列哈希(支持 224/256/384/512 位)
SELECT
SHA2('重要数据', 224) AS sha224,
SHA2('重要数据', 256) AS sha256,
SHA2('重要数据', 384) AS sha384,
SHA2('重要数据', 512) AS sha512;
-- 实际应用:存储高安全性要求的哈希值
INSERT INTO secure_data (data_hash)
VALUES (SHA2(CONCAT('data', 'salt_value'), 256));
3.1.4.3 Base64 编解码函数
- TO_BASE64() - 编码为 Base64
sql
-- 将字符串编码为 Base64
SELECT TO_BASE64('Hello GBase') AS base64_encoded;
-- 结果示例: 'SGVsbG8gR0Jhc2U='
-- 加密数据后进行 Base64 编码(便于存储)
SELECT TO_BASE64(AES_ENCRYPT('敏感数据', '密钥')) AS encrypted_base64;
- FROM_BASE64() - 解码 Base64
sql
-- 解码 Base64 字符串
SELECT FROM_BASE64('SGVsbG8gR0Jhc2U=') AS decoded_string;
-- 结果示例: 'Hello GBase' (二进制格式)
-- 转换为可读格式
SELECT CAST(FROM_BASE64('SGVsbG8gR0Jhc2U=') AS CHAR) AS readable_string;
-- 解码之前加密的数据
SELECT CAST(
AES_DECRYPT(
FROM_BASE64('U2FsdGVkX1+3n6Bj5m5QJZ7vJ5J5J5J5J5J5J5J5J5M='),
'密钥'
) AS CHAR
) AS original_data;
3.1.4.4 综合应用示例
示例1:安全密码存储
sql
-- 创建带盐值的密码存储表
CREATE TABLE user_accounts (
user_id INT PRIMARY KEY,
username VARCHAR(50) UNIQUE,
password_hash CHAR(64), -- 存储 SHA256 哈希
salt CHAR(8)
);
-- 注册用户(生成随机盐值并存储密码哈希)
SET @salt = SUBSTRING(MD5(RAND()), 1, 8);
INSERT INTO user_accounts
VALUES (
1,
'testuser',
SHA2(CONCAT('mypassword123', @salt), 256),
@salt
);
-- 验证登录
SELECT user_id
FROM user_accounts
WHERE username = 'testuser'
AND SHA2(CONCAT('mypassword123', salt), 256) = password_hash;
示例2:加密敏感字段存储
sql
-- 创建存储加密数据的表
CREATE TABLE employee_records (
emp_id INT,
name VARCHAR(100),
salary VARBINARY(255), -- 加密的薪资信息
contact VARBINARY(512) -- 加密的联系信息
);
-- 设置加密密钥
SET @crypto_key = 'secure_key_2023_!';
-- 插入加密数据
INSERT INTO employee_records
VALUES (
101,
'李四',
AES_ENCRYPT('85000', @crypto_key),
AES_ENCRYPT('13800138000|lisi@example.com', @crypto_key)
);
-- 查询解密数据
SELECT
emp_id,
name,
CAST(AES_DECRYPT(salary, @crypto_key) AS CHAR) AS salary,
CAST(AES_DECRYPT(contact, @crypto_key) AS CHAR) AS contact_info
FROM employee_records;
示例3:生成安全令牌
sql
-- 生成 API 令牌(UUID + 时间戳 + 随机数的 SHA256 哈希)
SELECT SHA2(
CONCAT(
UUID(),
UNIX_TIMESTAMP(),
RAND()
),
256
) AS api_token;
-- 生成带有效期的令牌(Base64 编码)
SELECT TO_BASE64(
AES_ENCRYPT(
CONCAT(
'user123|',
DATE_ADD(NOW(), INTERVAL 1 HOUR), -- 1小时后过期
'|',
UUID()
),
'token_secret_key'
)
) AS expiring_token;
- 性能与安全建议
- 密钥管理 :
- 不要将密钥硬编码在 SQL 中
- 考虑使用环境变量或密钥管理服务
- 哈希算法选择 :
- 密码存储推荐使用 SHA256 或 SHA512 加盐
- 避免使用 MD5 和 SHA1 作为密码哈希
- 加密字段类型 :
- 使用 VARBINARY 或 BLOB 存储加密数据
- 加密数据不宜建立索引
- Base64 使用场景 :
- 适合需要将二进制数据转为文本存储的场景
- 增加了约 33% 的数据体积
- AES 加密建议 :
- 使用强密钥(至少 16 字节)
- 考虑使用 CBC 模式(如果 GBase 8a 支持)
- 注意事项
- 不同版本的 GBase 8a 可能支持不同的加密函数
- 某些高级功能可能需要特定配置或版本
- 对称加密需要安全地管理密钥,丢失密钥将导致数据无法解密
- 加密/解密操作会增加 CPU 负载,影响查询性能
3.1.5 窗口函数
3.1.5.1 基础窗口函数
- ROW_NUMBER() - 为行分配唯一序号
sql
-- 按部门分区,按薪资降序排列,为每行分配序号
SELECT
employee_id,
name,
department,
salary,
ROW_NUMBER() OVER(PARTITION BY department ORDER BY salary DESC) AS dept_rank
FROM employees;
- RANK() 和 DENSE_RANK() - 排名函数
sql
-- RANK(): 相同值有相同排名,但会跳过后续排名
-- DENSE_RANK(): 相同值有相同排名,但不跳过排名
SELECT
employee_id,
name,
salary,
RANK() OVER(ORDER BY salary DESC) AS rank_pos,
DENSE_RANK() OVER(ORDER BY salary DESC) AS dense_rank_pos
FROM employees;
- NTILE() - 将结果集分成指定数量的组
sql
-- 将员工按薪资分成4个等级
SELECT
employee_id,
name,
salary,
NTILE(4) OVER(ORDER BY salary DESC) AS salary_quartile
FROM employees;
3.1.5.2 聚合窗口函数
- SUM() OVER() - 累计求和
sql
-- 计算每个部门的薪资累计和
SELECT
employee_id,
name,
department,
salary,
SUM(salary) OVER(PARTITION BY department ORDER BY hire_date) AS dept_running_total
FROM employees;
- AVG() OVER() - 移动平均
sql
-- 计算3个月的销售移动平均
SELECT
sale_date,
amount,
AVG(amount) OVER(ORDER BY sale_date ROWS BETWEEN 2 PRECEDING AND CURRENT ROW) AS moving_avg
FROM sales;
- COUNT() OVER() - 计数窗口
sql
-- 计算每个客户最近的5笔订单
SELECT
order_id,
customer_id,
order_date,
amount,
COUNT(order_id) OVER(PARTITION BY customer_id ORDER BY order_date
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS total_orders,
ROW_NUMBER() OVER(PARTITION BY customer_id ORDER BY order_date DESC) AS recent_rank
FROM orders
WHERE ROW_NUMBER() OVER(PARTITION BY customer_id ORDER BY order_date DESC) <= 5;
-- 注意:GBase 8a可能不支持在WHERE子句中直接使用窗口函数
3.1.5.3 分布函数
- PERCENT_RANK() - 百分比排名
sql
-- 计算员工薪资在部门中的百分比排名
SELECT
employee_id,
name,
department,
salary,
ROUND(PERCENT_RANK() OVER(PARTITION BY department ORDER BY salary) * 100, 2) AS percentile
FROM employees;
- CUME_DIST() - 累积分布
sql
-- 计算薪资的累积分布
SELECT
salary,
CUME_DIST() OVER(ORDER BY salary) AS cumulative_distribution
FROM employees;
3.1.5.4 前后函数
- LAG() 和 LEAD() - 访问前后行数据
sql
-- 比较当前行与前一行和后一行的数据
SELECT
date,
temperature,
LAG(temperature, 1) OVER(ORDER BY date) AS prev_day_temp,
LEAD(temperature, 1) OVER(ORDER BY date) AS next_day_temp,
temperature - LAG(temperature, 1) OVER(ORDER BY date) AS temp_change
FROM daily_weather;
- FIRST_VALUE() 和 LAST_VALUE() - 获取首尾值
sql
-- 获取每个部门薪资最高和最低的员工信息
SELECT
employee_id,
name,
department,
salary,
FIRST_VALUE(name) OVER(PARTITION BY department ORDER BY salary DESC) AS highest_paid_emp,
LAST_VALUE(name) OVER(PARTITION BY department ORDER BY salary DESC
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS lowest_paid_emp
FROM employees;
3.1.5.5 高级用法示例
示例1:计算移动平均和累计和
sql
-- 计算7天移动平均和30天累计和
SELECT
report_date,
daily_sales,
AVG(daily_sales) OVER(ORDER BY report_date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS weekly_avg,
SUM(daily_sales) OVER(ORDER BY report_date ROWS BETWEEN 29 PRECEDING AND CURRENT ROW) AS monthly_total
FROM sales_reports;
示例2:顶级客户分析
sql
-- 分析顶级客户的购买行为
WITH customer_stats AS (
SELECT
customer_id,
COUNT(order_id) AS order_count,
SUM(amount) AS total_spent,
AVG(amount) AS avg_order_value,
RANK() OVER(ORDER BY SUM(amount) DESC) AS spending_rank
FROM orders
GROUP BY customer_id
)
SELECT
customer_id,
order_count,
total_spent,
avg_order_value,
spending_rank,
ROUND(100 * total_spent / SUM(total_spent) OVER(), 2) AS pct_of_total
FROM customer_stats
WHERE spending_rank <= 20;
示例3:库存管理分析
sql
-- 分析产品库存周转情况
SELECT
product_id,
warehouse_id,
current_inventory,
days_since_last_receipt,
daily_demand,
current_inventory / daily_demand AS days_inventory_left,
AVG(current_inventory) OVER(PARTITION BY product_id) AS avg_inventory,
RANK() OVER(PARTITION BY warehouse_id ORDER BY days_inventory_left) AS priority_rank
FROM inventory
WHERE current_inventory > 0;
示例4:员工绩效分析
sql
-- 员工绩效与部门平均对比
SELECT
employee_id,
name,
department,
performance_score,
ROUND(performance_score - AVG(performance_score) OVER(PARTITION BY department), 2) AS dept_diff,
ROUND(100 * performance_score / AVG(performance_score) OVER(PARTITION BY department), 0) - 100 AS pct_diff,
CASE
WHEN performance_score > AVG(performance_score) OVER(PARTITION BY department) THEN 'Above Avg'
ELSE 'Below Avg'
END AS dept_comparison
FROM employee_performance;
- 性能优化建议
- 分区选择:合理使用 PARTITION BY 可以减少计算量
- 框架范围:尽量使用小的窗口框架(ROWS BETWEEN ...),避免大范围计算
- 索引利用:确保 ORDER BY 列有适当的索引
- 避免重复计算:对同一窗口的多次引用,考虑使用子查询或CTE
- 注意事项
- GBase 8a 不同版本对窗口函数的支持程度可能不同
- 某些复杂窗口函数可能在较旧版本中不可用
- 在 WHERE 或 HAVING 子句中直接使用窗口函数可能会受到限制
- 大数据量上的窗口函数计算可能消耗较多资源
3.2 性能优化
3.2.1 性能优化核心步骤
1.诊断阶段
使用 EXPLAIN 分析执行计划
检查系统资源使用情况
识别慢查询日志
- 优化阶段
索引优化
SQL 语句重写
表结构优化
配置参数调整
- 验证阶段
基准测试
A/B 测试对比
持续监控
3.2.2 具体优化方法及示例
3.2.2.1 索引优化
- 创建合适索引
sql
-- 为常用查询条件创建复合索引
CREATE INDEX idx_customer_order ON orders(customer_id, order_date);
-- 为排序字段创建索引
CREATE INDEX idx_sales_date ON sales(transaction_date DESC);
-- 为连接字段创建索引
CREATE INDEX idx_product_category ON products(category_id);
- 避免索引失效
sql
-- 错误示例:索引失效(对索引列使用函数)
SELECT * FROM orders WHERE DATE(order_date) = '2023-01-01';
-- 优化后:
SELECT * FROM orders
WHERE order_date >= '2023-01-01' AND order_date < '2023-01-02';
-- 错误示例:隐式类型转换导致索引失效
SELECT * FROM products WHERE product_code = 12345; -- 如果product_code是字符串
-- 优化后:
SELECT * FROM products WHERE product_code = '12345';
3.2.2.2 SQL 语句优化
- 避免 SELECT *
sql
-- 不推荐
SELECT * FROM customers WHERE status = 'active';
-- 推荐:只查询需要的列
SELECT customer_id, name, email FROM customers WHERE status = 'active';
- 优化 JOIN 操作
sql
-- 确保JOIN字段有索引
SELECT o.order_id, c.name, o.amount
FROM orders o
JOIN customers c ON o.customer_id = c.customer_id
WHERE o.order_date > '2023-01-01';
-- 小表驱动大表
SELECT /*+ LEADING(t2) */ t1.*, t2.*
FROM large_table t1
JOIN small_table t2 ON t1.id = t2.id;
- 优化子查询
sql
-- 不推荐:相关子查询
SELECT * FROM employees e
WHERE salary > (SELECT AVG(salary) FROM employees WHERE dept_id = e.dept_id);
-- 优化为JOIN
SELECT e1.*
FROM employees e1
JOIN (SELECT dept_id, AVG(salary) AS avg_sal FROM employees GROUP BY dept_id) e2
ON e1.dept_id = e2.dept_id
WHERE e1.salary > e2.avg_sal;
3.2.2.3 查询重写技巧
- 使用 EXISTS 替代 IN
sql
-- 不推荐(当子查询结果集大时)
SELECT * FROM customers
WHERE customer_id IN (SELECT customer_id FROM vip_list);
-- 推荐
SELECT * FROM customers c
WHERE EXISTS (SELECT 1 FROM vip_list v WHERE v.customer_id = c.customer_id);
- 合理使用 UNION ALL 替代 UNION
sql
-- UNION 会去重,增加开销
SELECT product_id FROM current_products
UNION
SELECT product_id FROM discontinued_products;
-- 如果确定没有重复或不需要去重,使用UNION ALL
SELECT product_id FROM current_products
UNION ALL
SELECT product_id FROM discontinued_products;
- 分页查询优化
sql
-- 低效分页(随着页码增加越来越慢)
SELECT * FROM large_table ORDER BY create_time LIMIT 100000, 10;
-- 优化方法1:使用索引覆盖+延迟关联
SELECT t1.* FROM large_table t1
JOIN (SELECT id FROM large_table ORDER BY create_time LIMIT 100000, 10) t2
ON t1.id = t2.id;
-- 优化方法2:记住上一页最后一条记录的值
SELECT * FROM large_table
WHERE create_time > '上一页最后一条记录的时间值'
ORDER BY create_time LIMIT 10;
3.2.2.4 表结构优化
- 合理设计分区表
sql
-- 按范围分区(例如按时间)
CREATE TABLE sales_history (
sale_id INT,
sale_date DATE,
amount DECIMAL(10,2),
...
) PARTITION BY RANGE (YEAR(sale_date)) (
PARTITION p2020 VALUES LESS THAN (2021),
PARTITION p2021 VALUES LESS THAN (2022),
PARTITION p2022 VALUES LESS THAN (2023),
PARTITION pmax VALUES LESS THAN MAXVALUE
);
-- 按列表分区(例如按地区)
CREATE TABLE customer_data (
cust_id INT,
cust_name VARCHAR(100),
region VARCHAR(20),
...
) PARTITION BY LIST (region) (
PARTITION p_east VALUES IN ('北京','天津','河北'),
PARTITION p_west VALUES IN ('四川','重庆','云南'),
PARTITION p_other VALUES IN (DEFAULT)
);
- 适当使用列存储
sql
-- GBase 8a 支持列式存储,适合分析型查询
CREATE TABLE fact_sales (
sale_id INT,
product_id INT,
customer_id INT,
sale_date DATE,
amount DECIMAL(10,2),
quantity INT
) DISTRIBUTE BY HASH(sale_id)
TABLEGROUP analytics_tg
STORAGE_TYPE CSTORE; -- 指定列存储
3.2.2.5 数据库配置优化
- 内存相关参数
sql
-- 设置合理的排序缓冲区大小
SET GLOBAL sort_buffer_size = 4*1024*1024; -- 4MB
-- 设置连接缓冲区大小
SET GLOBAL join_buffer_size = 2*1024*1024; -- 2MB
-- 设置读取缓冲区大小
SET GLOBAL read_buffer_size = 1*1024*1024; -- 1MB
- 并发相关参数
sql
-- 设置最大连接数
SET GLOBAL max_connections = 500;
-- 设置线程缓存大小
SET GLOBAL thread_cache_size = 50;
-- 设置表定义缓存
SET GLOBAL table_definition_cache = 1024;
- 查询缓存优化
sql
-- 启用查询缓存(根据版本支持情况)
SET GLOBAL query_cache_type = ON;
SET GLOBAL query_cache_size = 64*1024*1024; -- 64MB
3.2.2.6 高级优化技术
- 使用物化视图
sql
-- 创建物化视图(如果GBase 8a版本支持)
CREATE MATERIALIZED VIEW mv_daily_sales AS
SELECT
product_id,
SUM(amount) AS total_sales,
COUNT(*) AS transaction_count,
AVG(amount) AS avg_sale
FROM sales
GROUP BY product_id;
-- 定期刷新物化视图
REFRESH MATERIALIZED VIEW mv_daily_sales;
- 使用查询提示
sql
-- 强制使用特定索引
SELECT /*+ INDEX(orders idx_customer_order) */ *
FROM orders
WHERE customer_id = 1001 AND order_date > '2023-01-01';
-- 控制连接顺序
SELECT /*+ LEADING(employees departments) */ *
FROM employees e
JOIN departments d ON e.dept_id = d.dept_id
JOIN salaries s ON e.emp_id = s.emp_id;
-- 并行查询提示(如果支持)
SELECT /*+ PARALLEL(4) */ * FROM large_table WHERE condition;
- 批量操作优化
sql
-- 使用批量插入替代单条插入
INSERT INTO order_items (order_id, product_id, quantity)
VALUES
(1001, 201, 2),
(1001, 205, 1),
(1002, 210, 3),
(1002, 211, 1);
-- 使用LOAD DATA INFILE批量导入(如果支持)
LOAD DATA INFILE '/path/to/data.csv'
INTO TABLE sales_data
FIELDS TERMINATED BY ','
LINES TERMINATED BY '\n';
3.2.3 性能监控与分析
- 使用 EXPLAIN 分析查询
sql
EXPLAIN SELECT * FROM customers WHERE status = 'active' ORDER BY create_time LIMIT 100;
-- 详细输出示例:
/*
+----+-------------+-----------+------+---------------+------+---------+------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-----------+------+---------------+------+---------+------+------+-------------+
| 1 | SIMPLE | customers | ref | idx_status | idx_status | 1 | const | 500 | Using where |
+----+-------------+-----------+------+---------------+------+---------+------+------+-------------+
*/
- 慢查询日志分析
sql
-- 查看慢查询日志设置
SHOW VARIABLES LIKE '%slow_query%';
-- 启用慢查询日志
SET GLOBAL slow_query_log = ON;
SET GLOBAL long_query_time = 2; -- 记录超过2秒的查询
SET GLOBAL slow_query_log_file = '/var/log/gbase/slow.log';
- 性能监控视图
sql
-- 查看当前会话执行状态
SHOW STATUS LIKE 'Handler%';
-- 查看全局状态
SHOW GLOBAL STATUS;
-- 查看进程列表
SHOW PROCESSLIST;
3.2.4 实际应用示例
示例1:复杂分析查询优化
sql
-- 优化前(全表扫描+文件排序)
SELECT
c.category_name,
YEAR(o.order_date) AS order_year,
MONTH(o.order_date) AS order_month,
COUNT(DISTINCT c.customer_id) AS unique_customers,
SUM(oi.quantity) AS total_items,
SUM(oi.quantity * oi.unit_price) AS total_sales
FROM orders o
JOIN order_items oi ON o.order_id = oi.order_id
JOIN products p ON oi.product_id = p.product_id
JOIN categories c ON p.category_id = c.category_id
WHERE o.order_date BETWEEN '2022-01-01' AND '2022-12-31'
GROUP BY c.category_name, YEAR(o.order_date), MONTH(o.order_date)
ORDER BY c.category_name, order_year, order_month;
-- 优化后(使用索引+提前过滤)
SELECT
c.category_name,
YEAR(o.order_date) AS order_year,
MONTH(o.order_date) AS order_month,
COUNT(DISTINCT c.customer_id) AS unique_customers,
SUM(oi.quantity) AS total_items,
SUM(oi.quantity * oi.unit_price) AS total_sales
FROM (SELECT * FROM orders WHERE order_date BETWEEN '2022-01-01' AND '2022-12-31') o
JOIN order_items oi ON o.order_id = oi.order_id
JOIN products p ON oi.product_id = p.product_id
JOIN categories c ON p.category_id = c.category_id
GROUP BY c.category_name, YEAR(o.order_date), MONTH(o.order_date)
ORDER BY c.category_name, order_year, order_month;
示例2:报表查询优化
sql
-- 优化前(每日报表查询)
SELECT
user_id,
user_name,
department,
(SELECT COUNT(*) FROM orders WHERE user_id = u.user_id AND order_date = CURDATE()) AS today_orders,
(SELECT SUM(amount) FROM orders WHERE user_id = u.user_id AND order_date = CURDATE()) AS today_amount,
(SELECT COUNT(*) FROM orders WHERE user_id = u.user_id AND order_date BETWEEN DATE_SUB(CURDATE(), INTERVAL 7 DAY) AND CURDATE()) AS weekly_orders,
(SELECT SUM(amount) FROM orders WHERE user_id = u.user_id AND order_date BETWEEN DATE_SUB(CURDATE(), INTERVAL 7 DAY) AND CURDATE()) AS weekly_amount
FROM users u
WHERE u.status = 'active';
-- 优化后(使用JOIN和条件聚合)
SELECT
u.user_id,
u.user_name,
u.department,
COUNT(CASE WHEN o.order_date = CURDATE() THEN o.order_id END) AS today_orders,
SUM(CASE WHEN o.order_date = CURDATE() THEN o.amount END) AS today_amount,
COUNT(CASE WHEN o.order_date BETWEEN DATE_SUB(CURDATE(), INTERVAL 7 DAY) AND CURDATE() THEN o.order_id END) AS weekly_orders,
SUM(CASE WHEN o.order_date BETWEEN DATE_SUB(CURDATE(), INTERVAL 7 DAY) AND CURDATE() THEN o.amount END) AS weekly_amount
FROM users u
LEFT JOIN orders o ON u.user_id = o.user_id
WHERE u.status = 'active'
GROUP BY u.user_id, u.user_name, u.department;
- 总结
- 诊断优先:使用 EXPLAIN 和慢查询日志找出问题SQL
- 索引为王:创建合适的索引并避免索引失效
- 语句优化:重写低效SQL,避免全表扫描
- 结构优化:合理设计表结构和分区
- 配置调优:根据工作负载调整内存和并发参数
- 持续监控:建立性能基准并定期检查
3.3 存储过程
GBase 8a 支持存储过程的创建和使用,可以帮助封装复杂业务逻辑、提高性能并增强安全性。以下是存储过程的基本语法和示例:
3.3.1 存储过程基础
3.3.1.1 创建存储过程
sql
DELIMITER //
CREATE PROCEDURE procedure_name([IN|OUT|INOUT] parameter_name parameter_type, ...)
BEGIN
-- 过程体
-- 可以包含声明、SQL语句、流程控制等
END //
DELIMITER ;
3.3.1.2 调用存储过程
sql
CALL procedure_name(parameter_list);
3.3.1.3 删除存储过程
sql
DROP PROCEDURE IF EXISTS procedure_name;
3.3.2 完整示例
示例1:简单存储过程(无参数)
sql
DELIMITER //
CREATE PROCEDURE sp_get_active_customers()
BEGIN
SELECT customer_id, name, email
FROM customers
WHERE status = 'active'
ORDER BY registration_date DESC
LIMIT 100;
END //
DELIMITER ;
-- 调用
CALL sp_get_active_customers();
示例2:带输入参数的存储过程
sql
DELIMITER //
CREATE PROCEDURE sp_get_customer_orders(IN cust_id INT, IN start_date DATE, IN end_date DATE)
BEGIN
SELECT
o.order_id,
o.order_date,
o.total_amount,
o.status
FROM
orders o
WHERE
o.customer_id = cust_id
AND o.order_date BETWEEN start_date AND end_date
ORDER BY
o.order_date DESC;
END //
DELIMITER ;
-- 调用
CALL sp_get_customer_orders(1001, '2023-01-01', '2023-12-31');
示例3:带输出参数的存储过程
sql
DELIMITER //
CREATE PROCEDURE sp_get_order_stats(
IN cust_id INT,
OUT order_count INT,
OUT total_amount DECIMAL(12,2),
OUT avg_amount DECIMAL(12,2)
)
BEGIN
SELECT
COUNT(*),
SUM(total_amount),
AVG(total_amount)
INTO
order_count,
total_amount,
avg_amount
FROM
orders
WHERE
customer_id = cust_id;
END //
DELIMITER ;
-- 调用(需要会话变量接收输出参数)
SET @cust_id = 1001;
SET @order_count = 0;
SET @total_amount = 0;
SET @avg_amount = 0;
CALL sp_get_order_stats(@cust_id, @order_count, @total_amount, @avg_amount);
-- 查看结果
SELECT @order_count AS total_orders,
@total_amount AS total_spent,
@avg_amount AS average_order_value;
示例4:带输入输出参数的存储过程
sql
DELIMITER //
CREATE PROCEDURE sp_update_product_price(
INOUT product_id INT,
IN price_change DECIMAL(10,2),
OUT new_price DECIMAL(10,2)
)
BEGIN
-- 获取当前价格
DECLARE current_price DECIMAL(10,2);
SELECT unit_price INTO current_price FROM products WHERE id = product_id;
-- 更新价格
UPDATE products
SET unit_price = unit_price + price_change
WHERE id = product_id;
-- 返回新价格
SET new_price = current_price + price_change;
END //
DELIMITER ;
-- 调用
SET @prod_id = 101;
SET @price_change = 5.99;
SET @new_price = 0;
CALL sp_update_product_price(@prod_id, @price_change, @new_price);
-- 查看结果
SELECT @prod_id AS product_id, @new_price AS updated_price;
3.3.3 高级特性
- 条件控制
sql
DELIMITER //
CREATE PROCEDURE sp_process_order(IN order_id INT)
BEGIN
DECLARE order_status VARCHAR(20);
DECLARE customer_balance DECIMAL(12,2);
-- 获取订单状态
SELECT status INTO order_status FROM orders WHERE id = order_id;
-- 条件处理
IF order_status = 'pending' THEN
-- 检查客户余额
SELECT balance INTO customer_balance FROM customers
WHERE id = (SELECT customer_id FROM orders WHERE id = order_id);
IF customer_balance >= (SELECT total_amount FROM orders WHERE id = order_id) THEN
-- 余额充足,处理订单
UPDATE orders SET status = 'processing' WHERE id = order_id;
INSERT INTO order_logs (order_id, action, timestamp)
VALUES (order_id, 'Order processing started', NOW());
ELSE
-- 余额不足
INSERT INTO order_logs (order_id, action, timestamp)
VALUES (order_id, 'Insufficient funds', NOW());
END IF;
ELSEIF order_status = 'processing' THEN
-- 其他处理逻辑
INSERT INTO order_logs (order_id, action, timestamp)
VALUES (order_id, 'Order already processing', NOW());
ELSE
-- 默认处理
INSERT INTO order_logs (order_id, action, timestamp)
VALUES (order_id, 'No action taken', NOW());
END IF;
END //
DELIMITER ;
- 循环处理
sql
DELIMITER //
CREATE PROCEDURE sp_batch_update_status(IN days_past INT)
BEGIN
DECLARE done INT DEFAULT FALSE;
DECLARE order_id_val INT;
DECLARE order_cursor CURSOR FOR
SELECT id FROM orders
WHERE status = 'shipped'
AND DATEDIFF(NOW(), ship_date) > days_past;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
OPEN order_cursor;
read_loop: LOOP
FETCH order_cursor INTO order_id_val;
IF done THEN
LEAVE read_loop;
END IF;
-- 更新订单状态
UPDATE orders SET status = 'completed' WHERE id = order_id_val;
-- 记录日志
INSERT INTO order_logs (order_id, action, timestamp)
VALUES (order_id_val, 'Order completed automatically', NOW());
END LOOP;
CLOSE order_cursor;
END //
DELIMITER ;
- 错误处理
sql
DELIMITER //
CREATE PROCEDURE sp_safe_delete_customer(IN cust_id INT)
BEGIN
DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGIN
ROLLBACK;
SELECT 'An error occurred, transaction rolled back' AS message;
END;
START TRANSACTION;
-- 先删除相关订单
DELETE FROM orders WHERE customer_id = cust_id;
-- 再删除客户
DELETE FROM customers WHERE id = cust_id;
COMMIT;
SELECT CONCAT('Customer ', cust_id, ' and related data deleted successfully') AS message;
END //
DELIMITER ;
3.3.4 实际业务场景示例
示例:月度销售报表生成
sql
DELIMITER //
CREATE PROCEDURE sp_generate_monthly_report(
IN report_year INT,
IN report_month INT,
OUT success_flag INT
)
BEGIN
DECLARE exit handler for sqlexception
BEGIN
SET success_flag = 0;
ROLLBACK;
END;
SET success_flag = 1;
START TRANSACTION;
-- 检查是否已存在该月报表
IF EXISTS (SELECT 1 FROM monthly_reports WHERE year = report_year AND month = report_month) THEN
-- 更新现有报表
UPDATE monthly_reports mr
SET
total_sales = (
SELECT SUM(amount)
FROM orders
WHERE YEAR(order_date) = report_year
AND MONTH(order_date) = report_month
),
total_orders = (
SELECT COUNT(*)
FROM orders
WHERE YEAR(order_date) = report_year
AND MONTH(order_date) = report_month
),
avg_order_value = (
SELECT AVG(amount)
FROM orders
WHERE YEAR(order_date) = report_year
AND MONTH(order_date) = report_month
),
last_updated = NOW()
WHERE mr.year = report_year AND mr.month = report_month;
ELSE
-- 插入新报表
INSERT INTO monthly_reports (year, month, total_sales, total_orders, avg_order_value, created_at, last_updated)
SELECT
report_year,
report_month,
SUM(amount),
COUNT(*),
AVG(amount),
NOW(),
NOW()
FROM
orders
WHERE
YEAR(order_date) = report_year
AND MONTH(order_date) = report_month;
END IF;
-- 生成报表明细
DELETE FROM monthly_report_details
WHERE year = report_year AND month = report_month;
INSERT INTO monthly_report_details (year, month, product_id, product_name, category, total_quantity, total_amount)
SELECT
report_year,
report_month,
p.id,
p.name,
c.name AS category,
SUM(oi.quantity),
SUM(oi.quantity * oi.unit_price)
FROM
orders o
JOIN order_items oi ON o.id = oi.order_id
JOIN products p ON oi.product_id = p.id
JOIN categories c ON p.category_id = c.id
WHERE
YEAR(o.order_date) = report_year
AND MONTH(o.order_date) = report_month
GROUP BY
p.id, p.name, c.name;
COMMIT;
END //
DELIMITER ;
-- 调用示例
SET @success = 0;
CALL sp_generate_monthly_report(2023, 11, @success);
SELECT @success AS report_generation_status;
3.3.5 调试技巧
- 使用 SELECT 输出中间结果
sql
CREATE PROCEDURE sp_debug_example()
BEGIN
DECLARE var1 INT DEFAULT 0;
SET var1 = 10;
-- 调试输出
SELECT CONCAT('Debug - var1 value: ', var1) AS debug_message;
-- 继续处理...
END
- 临时表记录执行过程
sql
CREATE PROCEDURE sp_with_logging()
BEGIN
-- 创建临时日志表
DROP TEMPORARY TABLE IF EXISTS temp_proc_log;
CREATE TEMPORARY TABLE temp_proc_log (
step_no INT,
step_name VARCHAR(100),
log_time DATETIME,
details VARCHAR(500)
);
-- 记录步骤
INSERT INTO temp_proc_log VALUES (1, 'Start', NOW(), 'Procedure started');
-- 业务逻辑...
INSERT INTO temp_proc_log VALUES (2, 'Query executed', NOW(), 'Main query completed');
-- 查看日志
SELECT * FROM temp_proc_log ORDER BY step_no;
END
- 注意事项
- 命名规范 :使用
sp_
前缀标识存储过程 - 参数清晰:明确指定 IN/OUT/INOUT 参数类型
- 错误处理:始终包含错误处理逻辑
- 事务管理:对数据修改操作使用事务
- 注释充分:复杂逻辑添加注释说明
- 性能考虑:避免在存储过程中执行大量数据操作
- 版本控制:将存储过程定义纳入数据库变更管理