【oracle进阶】01:with临时集cte,全外连接所有数据,NVL非null三元式,MINUS结果集
复杂SQL查询:
使用WITH子句创建临时结果集
使用FULL OUTER JOIN找出两个表中所有记录
使用MINUS操作找出仅存在于一个表中的记录
使用UNION合并不同来源的差异
最后使用INNER JOIN将结果与源数据关联
1.CTE with临时结果集
CTE(Common Table Expression)是SQL中的一种临时结果集,它存在于单个SQL语句的执行范围内。CTE通过WITH子句定义,允许你创建一个命名的临时结果集,可以在后续的SELECT、INSERT、UPDATE、DELETE或其他CTE定义中引用。
CTE的语法结构
WITH cte_name (column1, column2, ...) AS (
SELECT ...
)
SELECT * FROM cte_name;
CTE的主要特点
- 临时性:CTE只在包含它的SQL语句执行期间存在,执行完毕后自动消失
- 可读性:使代码更具结构性和可读性,特别是对于复杂查询
- 递归能力:可以创建递归查询,处理层次结构数据
- 复用性:同一个CTE可以在查询的多个部分被引用多次
- 模块化:可以将复杂查询分解为更小、更易管理的部分
CTE的使用场景
- 简化复杂查询:将多层嵌套的子查询分解为更易理解的部分
- 递归查询:处理树状或层次结构数据,如组织结构图、产品分类等
- 多次引用同一个子查询:避免重复编写同样的子查询
- 替代视图:当不需要永久存储视图时,CTE是一个很好的替代方案
- 窗口函数和聚合:结合窗口函数进行复杂计算
在你之前的SQL示例中
我使用CTE来定义两个临时结果集"差分元"和"差分先":
WITH 差分元 AS (
SELECT
番号,
再リース回数,
会計処理区分,
履歴番号,
未实现总额,
'只在原始数据中存在' AS 数据状态
FROM [1a.比較元データクエリ]
MINUS
SELECT
番号,
再リース回数,
会計処理区分,
履歴番号,
未实现总额,
'只在原始数据中存在' AS 数据状态
FROM [1b.比較先データクエリ]
),
差分先 AS (
-- 第二个CTE定义
...
)
-- 主查询使用这两个CTE
SELECT ... FROM 差分元
UNION
SELECT ... FROM 差分先;
这使得查询更加模块化和可读,同时避免了在主查询中重复编写复杂的子查询逻辑。
一个简单的CTE示例
-- 使用CTE计算每个部门的平均薪资和全公司的平均薪资
WITH 部门平均薪资 AS (
SELECT 部门ID, 部门名称, AVG(薪资) AS 平均薪资
FROM 员工表
GROUP BY 部门ID, 部门名称
),
全公司平均薪资 AS (
SELECT AVG(薪资) AS 公司平均薪资
FROM 员工表
)
SELECT
d.部门ID,
d.部门名称,
d.平均薪资,
c.公司平均薪资,
d.平均薪资 - c.公司平均薪资 AS 差异
FROM 部门平均薪资 d, 全公司平均薪资 c
ORDER BY 差异 DESC;
这个示例清晰地展示了如何使用CTE来结构化复杂查询,使其更易于理解和维护。
2.全外连接
在这个SQL查询中使用全外连接(FULL OUTER JOIN)是为了完整地比较两个数据源中的所有记录,不漏掉任何一条数据。全外连接有几个关键优势:
为什么使用全外连接(FULL OUTER JOIN)
-
完整性比较:全外连接可以找出两个数据源中的所有记录,包括:
- 两边都有的匹配记录
- 只在"元数据"中存在的记录
- 只在"后数据"中存在的记录
-
数据差异分析:通过全外连接,你可以:
- 发现哪些记录在一个数据源中存在而在另一个数据源中不存在
- 对于两边都存在的记录,可以比较它们的"未实现总金额"值是否有差异
-
完整的数据审计:这种查询常用于数据迁移验证、系统升级前后的数据一致性检查、或定期数据质量审计
与其他连接方式相比
- 内连接(INNER JOIN):只会返回两边都匹配的记录,会丢失只存在于一边的数据
- 左连接(LEFT JOIN):只会返回"元数据"中的所有记录,可能会漏掉只存在于"后数据"中的记录
- 右连接(RIGHT JOIN):只会返回"后数据"中的所有记录,可能会漏掉只存在于"元数据"中的记录
实际应用场景
这种查询在以下场景特别有用:
- 数据验证:例如,验证数据从旧系统迁移到新系统后是否完整且正确
- 对账:比较两个时间点的账务数据,找出变化和差异
- 系统同步检查:检查两个系统间的数据是否正确同步
- 数据更新前后比较:在批量更新数据前,先进行比较以了解将发生的变化
在你的例子中,使用全外连接能够全面检查两组契约相关数据的差异,不管是记录的存在与否,还是具体的金额差异,都能一目了然地呈现出来。
好的,我会给你一个具体的数据例子,来展示这个全外连接查询的工作方式。
示例数据
元数据(原始数据)
番号 | 再リース回数 | 処理区分 | 履歴番号 | 未实现总金额 |
---|---|---|---|---|
J001 | 0 | A | H100 | 500,000 |
J002 | 1 | B | H101 | 750,000 |
J003 | 0 | A | H102 | 1,200,000 |
J004 | 2 | C | H103 | 300,000 |
J005 | 0 | B | H104 | 650,000 |
后数据 (比较数据)
番号 | 再リース回数 | 処理区分 | 履歴番号 | 未实现总金额 |
---|---|---|---|---|
J001 | 0 | A | H100 | 500,000 |
J003 | 0 | A | H102 | 1,250,000 |
J004 | 2 | C | H103 | 300,000 |
J006 | 1 | A | H105 | 800,000 |
J007 | 0 | B | H106 | 920,000 |
执行全外连接SQL后的结果
SELECT
NVL(元数据.番号, 后数据.番号) AS 番号,
NVL(元数据.再リース回数, 后数据.再リース回数) AS 再リース回数,
NVL(元数据.会計処理区分, 后数据.会計処理区分) AS 会計処理区分,
NVL(元数据.履歴番号, 后数据.履歴番号) AS 履歴番号,
元数据.未实现总额 AS 元_未实现总额,
后数据.未实现总额 AS 先_未实现总额
FROM
元数据
FULL OUTER JOIN
后数据
ON
元数据.番号 = 后数据.番号
AND 元数据.再リース回数 = 后数据.再リース回数
AND 元数据.会計処理区分 = 后数据.会計処理区分
AND 元数据.履歴番号 = 后数据.履歴番号
查询结果
番号 | 再リース回数 | 会計処理区分 | 履歴番号 | 元_未实现总额 | 先_未实现总额 | 说明 |
---|---|---|---|---|---|---|
J001 | 0 | A | H100 | 500,000 | 500,000 | 两边都存在且金额相同 |
J002 | 1 | B | H101 | 750,000 | NULL | 只在元数据中存在 |
J003 | 0 | A | H102 | 1,200,000 | 1,250,000 | 两边都存在但金额不同 |
J004 | 2 | C | H103 | 300,000 | 300,000 | 两边都存在且金额相同 |
J005 | 0 | B | H104 | 650,000 | NULL | 只在元数据中存在 |
J006 | 1 | A | H105 | NULL | 800,000 | 只在后数据中存在 |
J007 | 0 | B | H106 | NULL | 920,000 | 只在后数据中存在 |
结果分析
-
完全匹配且金额相同的记录:J001和J004
- 这些记录在两个数据源中完全一致
-
完全匹配但金额不同的记录:J003
- 这条记录在两个数据源中的主键值相同,但未实现调整总金额不同
- 元数据中是1,200,000,后数据中是1,250,000
- 这可能表示有数据更新或差异
-
只在元数据中存在的记录:J002和J005
- 这些记录可能在后数据中被删除,或尚未添加
-
只在后数据中存在的记录:J006和J007
- 这些记录可能是新增的,或者在元数据中被删除
通过这个全外连接查询,你可以一眼看出两个数据源之间的所有差异,无论是记录的存在性还是字段值的差异。这对于数据验证、对账和一致性检查非常有用。
3、NVL函数基本语法
NVL(expr1, expr2)
参数解释:
-
expr1
:要检查的表达式(可能包含NULL值) -
expr2
:如果expr1
为NULL时要返回的值
NVL函数工作原理
NVL函数会检查第一个参数expr1
:
- 如果
expr1
不是NULL,则返回expr1
的值 - 如果
expr1
是NULL,则返回expr2
的值
使用示例
基本示例
SELECT NVL(commission, 0) FROM employees;
这个查询会返回员工的commission值,如果commission为NULL,则返回0。
在计算中使用
SELECT employee_id,
salary,
commission_pct,
NVL(commission_pct, 0) * salary AS commission_amount
FROM employees;
这个查询计算员工的佣金金额,如果commission_pct为NULL,则用0替代进行计算。
字符串类型的NULL处理
SELECT NVL(phone_number, '未提供') FROM employees;
这个查询会返回员工的电话号码,如果电话号码为NULL,则返回’未提供’。
注意事项
-
expr1
和expr2
的数据类型应该相同或可以隐式转换 - 如果需要检查多个可能的值,可以考虑使用NVL2或COALESCE函数
- 在MySQL中,对应的函数是IFNULL
- 在SQL Server中,对应的函数是ISNULL
1的查询中 NVL函数的用法解释
在1的查询中,NVL函数的使用方式为:
sqlCopyNVL(比較元.字段名, 比較先.字段名)
这种用法的目的是:
如果"比較元"表中的值不为NULL,就使用"比較元"表中的值
如果"比較元"表中的值为NULL(可能是因为该记录在"比較元"中不存在),就使用"比較先"表中的值
这样做的目的是确保输出的字段值不为NULL(除非两个数据源中都是NULL)。
这种设计在FULL OUTER JOIN中特别有用,因为当某条记录只在一个表中存在时,另一个表中对应的字段值会是NULL。通过NVL函数,你可以确保始终获取到非NULL的值(除非两个数据源中都是NULL)。
查询结果分析
最终返回的结果集包括:
四个合并后的主键字段(使用NVL保证尽可能非NULL,除非两个数据源中都是NULL)
两个未实现调整总金额字段(分别来自两个表,未合并)
这种查询通常用于比较两组数据之间的差异,例如比较两个时间点的数据快照,或者比较预期值和实际值。
4. MINUS操作
先来看,假设一个数据库查询配置界面,分为三个部分:
- 左侧 [2a.差分抽出クエリ(元)] - 差分抽出查询(原始):
- 抽出项目:全项目
- 抽出对象:[1a.元数据] 元数据
- 抽出条件:无
- 集计条件:无
- 抽出顺序:无
- MINUS操作
- 抽出项目:全项目
- 抽出对象:[1b.后数据] 后数据
- 抽出条件:无
- 集计条件:无
- 抽出顺序:无
- 中间 [2b.差分抽出クエリ(先)] - 差分抽出查询(目标):
- 抽出项目:全项目
- 抽出对象:[1b.后数据] 后数据
- 抽出条件:无
- 集计条件:无
- 抽出顺序:无
- MINUS操作
- 抽出项目:全项目
- 抽出对象:[1a.元数据] 元数据
- 抽出条件:无
- 集计条件:无
- 抽出顺序:无
- 右侧 [2c.差分統合クエリ] - 差分统合查询:
- 抽出项目:
- 差分元.番号
- 差分元.再リース回数
- 差分元.会計処理区分
- 差分元.履歴番号
- 抽出对象:[2a.差分抽出クエリ(元)] 差分元
- UNION操作
- 抽出项目:
- 差分先.番号
- 差分先.再リース回数
- 差分先.会計処理区分
- 差分先.履歴番号
- 抽出对象:[2b.差分抽出クエリ(先)] 差分先
- 抽出条件:无
- 集计条件:无
- 抽出顺序:无
- 抽出项目:
这整个配置描述了一个数据比较流程:
1. 首先,左侧查询获取只存在于"元数据"中而不存在于"后数据"中的记录
2. 然后,中间查询获取只存在于"后数据"中而不存在于"元数据"中的记录
3. 最后,右侧查询将这两部分结果合并在一起,形成完整的差异报告**
这与我们之前讨论的全外连接(FULL OUTER JOIN)查询有相似之处,但实现方式不同。这里使用了MINUS(差集)和UNION(并集)操作来实现数据比较,而不是直接使用FULL OUTER JOIN。
MINUS是SQL中的一个集合运算符,用于从第一个查询结果集中减去第二个查询结果集,只保留在第一个结果集中存在而在第二个结果集中不存在的行。
MINUS 语法
SELECT column1, column2, ... FROM table1
MINUS
SELECT column1, column2, ... FROM table2;
MINUS 的特点
- 集合操作:MINUS执行的是集合的差集操作
- 消除重复:MINUS自动消除结果中的重复行
- 列要求:两个查询必须有相同数量的列,且对应列的数据类型必须兼容
- 列名采用:结果集的列名采用第一个SELECT语句中的列名
数据库支持
- Oracle:完全支持MINUS
- PostgreSQL:使用EXCEPT代替MINUS(功能相同,语法不同)
- SQL Server:使用EXCEPT代替MINUS
- MySQL:不直接支持MINUS或EXCEPT,需要使用子查询和NOT IN/NOT EXISTS实现
- SQLite:支持EXCEPT
实际示例
假设有两个表:
表1:原始员工表(Employees1)
ID | Name | Department |
---|---|---|
1 | 张三 | 销售 |
2 | 李四 | 财务 |
3 | 王五 | IT |
4 | 赵六 | 市场 |
表2:更新后员工表(Employees2)
ID | Name | Department |
---|---|---|
1 | 张三 | 销售 |
3 | 王五 | IT |
4 | 赵六 | 市场 |
5 | 钱七 | HR |
使用MINUS查询仅存在于原始表而不存在于更新表中的记录:
SELECT ID, Name, Department FROM Employees1
MINUS
SELECT ID, Name, Department FROM Employees2;
结果:
ID | Name | Department |
---|---|---|
2 | 李四 | 财务 |
同样,查询仅存在于更新表而不存在于原始表中的记录:
SELECT ID, Name, Department FROM Employees2
MINUS
SELECT ID, Name, Department FROM Employees1;
结果:
ID | Name | Department |
---|---|---|
5 | 钱七 | HR |
在MySQL中实现MINUS
由于MySQL不直接支持MINUS,可以使用LEFT JOIN和IS NULL实现类似功能:
SELECT e1.ID, e1.Name, e1.Department
FROM Employees1 e1
LEFT JOIN Employees2 e2 ON
e1.ID = e2.ID AND
e1.Name = e2.Name AND
e1.Department = e2.Department
WHERE e2.ID IS NULL;
在开始的案例中
在提供的查询中,MINUS用于找出只存在于"比較元"而不存在于"比較先"的记录,以及反之。这种操作对于数据比较、数据验证和变更跟踪非常有用。
5,使用INNER JOIN将结果与源数据关联
在已经有了差分统合结果的情况下,再与原来的总结果集进行INNER JOIN确实需要解释一下。
这种设计可能有以下几个原因:
-
获取完整信息:
- 差分统合查询([2c.差分统合クエリ])可能只包含了关键的匹配字段,而没有包含未实现调整总金额等详细数据
- 通过与比較元先結合クエリ进行JOIN,可以获取这些记录的完整信息,特别是两边的金额数据
-
筛选验证:
- 这可能是一个验证步骤,确保差分统合中的记录确实在原始数据中存在
- 有时候在复杂的数据处理流程中,这种重复验证可以防止因为中间处理步骤导致的数据异常
-
获取特定视图:
- 差分結合中的记录可能是经过某种条件筛选的(例如只有金额差异大于某个阈值的记录)
- 通过JOIN,可以获取这些特定记录的完整信息
-
业务流程要求:
- 在某些审计或对账系统中,可能需要先生成差异报告,然后再针对差异记录进行详细分析
- 这个JOIN操作可能是为后续的分析或报告准备数据
实际上,这种设计可能有点冗余,但在复杂的数据处理系统中,有时会优先考虑流程的清晰性和结果的可验证性,而不是查询的效率。在这个案例中,这个最终查询似乎是将关注点限定在那些已被确认有差异的记录上,同时保留了两边的未实现调整总金额,方便后续的差异分析。
这种模式在金融、审计系统中很常见,因为这些领域通常需要多层次的数据验证和清晰的处理流程。