SQL中根据一张表的数据去掉另一张表的重复数据

本文通过一个具体的SQL查询案例,展示了如何从'user_a'表中按用户分组并计算总金额及计数,同时排除'user_b'表中存在的用户。该查询利用了子查询和分组函数,为读者提供了一个实用的SQL技巧示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

user_a表如下:


user_b表如下:


查询语句如下:

SELECT
user_a.`user`,
Sum(user_a.money),
Count(user_a.`user`)
FROM
user_a
GROUP BY
user_a.`user`
HAVING
user_a.`user` NOT IN ((select user from user_b group by user))

运行结果:


<think>我们有两张结构相同的,需要找出数据存在差异的列及对应的数据。在Oracle中,我们可以使用MINUS运算符或者使用JOIN进行逐列比较,但更高效的方式是使用以下方法: 方法1:使用MINUS运算符 注意:MINUS会返回第一个查询中存在而第二个查询中不存在的行。但是,它不能直接告诉我们具体哪些列不同。 方法2:使用全外连接(FULL OUTER JOIN)并逐列比较。这种方法可以逐列比较,但写起来比较繁琐,尤其是当列很多的时候。 方法3:使用DBMS_COMPARISON包(需要Oracle 11g及以上版本)。这是一个更高级的方法,可以比较两个并生成差异报告。 方法4:使用UNION ALL和GROUP BY,通过校验和(CHECKSUM)或逐列比较。 考虑到用户希望找出具体哪些列存在差异,我们选择方法2(逐列比较)或方法3(使用DBMS_COMPARISON)进行说明。 由于方法3需要额外的设置(创建比较对象),并且不是所有环境都可用,这里我们重点介绍方法2。同时,我们也可以介绍一种使用动态SQL的方法来避免手动写所有列名(如果列很多的话)。 步骤: 1. 假设我们有两张:table1和table2,它们具有相同的列(比如col1, col2, col3, ...)。 2. 我们可以使用全外连接将两张按主键或唯一键连接(如果没有主键,则需要使用所有列来匹配,但这样效率低且容易出错,所以通常要求有主键或唯一标识列)。 3. 然后,我们检查每一列的值是否相等,如果不相等,则标记该列。 但是,为了找出具体哪些列不同,我们需要在结果中展示每一列是否相同,或者将不同的列分别列出。这里我们可以采用两种方式: a) 在结果集中,将两张的所有列都列出,并标记哪些行有差异(通过比较两个的对应列)。 b) 将不同的列拆分成多行,每行显示一个列名和对应的不同值。 这里我们采用方式a),因为更直观。但是,当列很多时,结果集可能很宽。 假设我们有一个主键列(比如id),然后我们进行全外连接,并比较每个列的值。 示例SQL: 假设结构:id, name, age, address 我们可以这样写: ```sql SELECT COALESCE(t1.id, t2.id) AS id, t1.name AS t1_name, t2.name AS t2_name, t1.age AS t1_age, t2.age AS t2_age, t1.address AS t1_address, t2.address AS t2_address, CASE WHEN t1.name <> t2.name OR (t1.name IS NULL AND t2.name IS NOT NULL) OR (t1.name IS NOT NULL AND t2.name IS NULL) THEN 'name' END AS diff_column1, CASE WHEN t1.age <> t2.age OR (t1.age IS NULL AND t2.age IS NOT NULL) OR (t1.age IS NOT NULL AND t2.age IS NULL) THEN 'age' END AS diff_column2, CASE WHEN t1.address <> t2.address OR (t1.address IS NULL AND t2.address IS NOT NULL) OR (t1.address IS NOT NULL AND t2.address IS NULL) THEN 'address' END AS diff_column3 FROM table1 t1 FULL OUTER JOIN table2 t2 ON t1.id = t2.id WHERE t1.name <> t2.name OR t1.age <> t2.age OR t1.address <> t2.address OR (t1.id IS NULL OR t2.id IS NULL); ``` 但是,这样写只能将不同的列分别标记,而且结果集中每个列用一个字段标记。我们可能希望将不同的列名合并到一个字段中。 改进:将多个列合并成一个字段,显示所有不同的列名: ```sql SELECT COALESCE(t1.id, t2.id) AS id, t1.name AS t1_name, t2.name AS t2_name, t1.age AS t1_age, t2.age AS t2_age, t1.address AS t1_address, t2.address AS t2_address, ( CASE WHEN t1.name <> t2.name OR (t1.name IS NULL AND t2.name IS NOT NULL) OR (t1.name IS NOT NULL AND t2.name IS NULL) THEN 'name;' END || CASE WHEN t1.age <> t2.age OR (t1.age IS NULL AND t2.age IS NOT NULL) OR (t1.age IS NOT NULL AND t2.age IS NULL) THEN 'age;' END || CASE WHEN t1.address <> t2.address OR (t1.address IS NULL AND t2.address IS NOT NULL) OR (t1.address IS NOT NULL AND t2.address IS NULL) THEN 'address;' END ) AS diff_columns FROM table1 t1 FULL OUTER JOIN table2 t2 ON t1.id = t2.id WHERE t1.name <> t2.name OR t1.age <> t2.age OR t1.address <> t2.address OR (t1.id IS NULL OR t2.id IS NULL); ``` 注意:这里我们使用了字符串连接符`||`,并且每个列名后面加上了分号。这样,diff_columns字段会列出所有不同的列名,用分号分隔。 但是,上述查询在列很多的情况下写起来很麻烦。因此,我们可以考虑使用动态SQL生成查询语句。不过,在回答中,我们可以提供一个通用的方法,让用户自己替换列名。 外,我们也可以使用Oracle的SET操作符(如UNION ALL)和GROUP BY来比较,但这里不展开。 如果很大,我们可能需要考虑性能问题,比如在连接列上建立索引。 方法3:使用DBMS_COMPARISON包(Oracle 11g及以上) 步骤: 1. 创建比较(CREATE_COMPARISON) 2. 比较(COMPARE) 3. 获取差异(获取差异视图) 示例: ```sql BEGIN DBMS_COMPARISON.CREATE_COMPARISON( comparison_name => 'comp1', schema_name => 'schema1', table_name => 'table1', dblink_name => NULL, -- 如果是同一个数据库,可以不用dblink remote_schema_name => 'schema2', remote_table_name => 'table2' ); END; / -- 然后进行比较 DECLARE consistent BOOLEAN; scan_info DBMS_COMPARISON.COMPARISON_TYPE; BEGIN consistent := DBMS_COMPARISON.COMPARE( comparison_name => 'comp1', scan_info => scan_info, perform_row_dif => TRUE ); DBMS_OUTPUT.PUT_LINE('Scan ID: ' || scan_info.scan_id); IF consistent THEN DBMS_OUTPUT.PUT_LINE('No differences found.'); ELSE DBMS_OUTPUT.PUT_LINE('Differences found.'); END IF; END; / -- 查询差异 SELECT * FROM DBA_COMPARISON_SCAN_SUMMARY; SELECT * FROM DBA_COMPARISON_SCAN_VALUES; SELECT * FROM DBA_COMPARISON_ROW_DIF; ``` 但是,使用DBMS_COMPARISON需要一定的权限,并且需要创建比较对象,比较适合定期比较的场景。 根据用户的需求,如果只是临时比较,使用方法2可能更直接。如果经常需要比较,则方法3更合适。 考虑到用户的问题描述,我们重点介绍方法2,并提供一个通用的模板。 外,我们也可以使用以下方法(使用UNION和GROUP BY)来找出差异行,但不具体到列: ```sql SELECT id, 'table1' AS from_table, t1.* FROM table1 t1 MINUS SELECT id, 'table1', t2.* FROM table2 t2 ``` 但是,这种方法不能显示具体哪些列不同,而且需要两张完全一致(包括列顺序),并且无法处理一张有而一张没有的行。 因此,我们回到方法2。 总结:我们推荐使用全外连接的方法,并标记出不同的列。如果列很多,可以使用动态SQL生成查询语句。 下面,我们提供一个动态生成SQL的PL/SQL块,用于生成比较的SQL语句(假设有主键id,且名和列名已知): 假设名为table1和table2,列名列为col1, col2, col3, ...,主键列为id。 我们可以先查询出列名(排除主键列,因为主键用于连接,不需要比较),然后构建查询语句。 但是,由于在回答中直接写动态生成代码可能较长,我们可以提供一个示例,让用户替换名和列名。 下面是一个示例,假设我们已知列名(不包括主键)为:name, age, address。 生成的SQL如下: ```sql SELECT COALESCE(t1.id, t2.id) AS id, -- 列出所有列 t1.name AS t1_name, t2.name AS t2_name, t1.age AS t1_age, t2.age AS t2_age, t1.address AS t1_address, t2.address AS t2_address, -- 合并差异列 TRIM(TRAILING ';' FROM CASE WHEN t1.name <> t2.name OR (t1.name IS NULL AND t2.name IS NOT NULL) OR (t1.name IS NOT NULL AND t2.name IS NULL) THEN 'name;' ELSE '' END || CASE WHEN t1.age <> t2.age OR (t1.age IS NULL AND t2.age IS NOT NULL) OR (t1.age IS NOT NULL AND t2.age IS NULL) THEN 'age;' ELSE '' END || CASE WHEN t1.address <> t2.address OR (t1.address IS NULL AND t2.address IS NOT NULL) OR (t1.address IS NOT NULL AND t2.address IS NULL) THEN 'address;' ELSE '' END ) AS diff_columns FROM table1 t1 FULL OUTER JOIN table2 t2 ON t1.id = t2.id WHERE t1.name <> t2.name OR t1.age <> t2.age OR t1.address <> t2.address OR (t1.id IS NULL OR t2.id IS NULL); ``` 注意:这里我们使用了TRIM函数去掉最后多余的分号。 如果用户需要动态生成,可以编写一个PL/SQL函数来生成SQL文本,然后执行。 但是,根据问题,我们直接给出一个静态的示例,用户可以根据实际情况替换列名。 外,我们也可以使用LISTAGG函数在行内显示所有不同的列名,但是这里我们采用字符串连接的方式。 最后,我们提供一个方法:使用UNPIVOT将列转为行,然后比较。这种方法可以避免写很多CASE WHEN,但是SQL会复杂一些,且可能性能较差。 由于时间关系,我们不再展开。 综上所述,我们推荐使用全外连接并逐列比较的方法,并生成一个包含所有不同列名的字段。 希望这个回答对用户有帮助。 相关问题: 1. 在Oracle中,如何比较两个的结构(列名、数据类型等)是否相同? 2. 如果没有主键,如何比较两张数据差异? 3. Oracle中DBMS_COMPARISON包的使用详细步骤是什么? 4. 如何提高比较两张大数据差异的性能?</think>### Oracle SQL 对比两张结构相同的并找出数据差异 在 Oracle 中对比两张结构相同的并定位差异数据,可通过以下方法实现(假设名为 `table1` 和 `table2`,主键列为 `id`): #### 方法 1:使用 `MINUS` 集合操作(快速定位差异行) ```sql -- 找出 table1 有但 table2 没有的行 SELECT * FROM table1 MINUS SELECT * FROM table2; -- 找出 table2 有但 table1 没有的行 SELECT * FROM table2 MINUS SELECT * FROM table1; ``` **特点**: - 直接返回整行差异数据 - 不显示具体差异列 - 要求两列顺序完全一致[^1] --- #### 方法 2:全外连接 + 逐列比较(精确定位差异列) ```sql SELECT COALESCE(t1.id, t2.id) AS id, CASE WHEN t1.col1 <> t2.col1 OR (t1.col1 IS NULL AND t2.col1 IS NOT NULL) THEN 'col1' END AS diff_column, t1.col1 AS t1_value, t2.col1 AS t2_value FROM table1 t1 FULL OUTER JOIN table2 t2 ON t1.id = t2.id WHERE t1.col1 <> t2.col1 OR (t1.col1 IS NULL AND t2.col1 IS NOT NULL) OR (t1.col1 IS NOT NULL AND t2.col1 IS NULL) UNION ALL -- 重复此结构添加其他列比较 ... ``` **优化技巧**: - 使用 `NVL` 处理 NULL 值:`WHERE NVL(t1.col1, 'N/A') <> NVL(t2.col1, 'N/A')` - 动态生成 SQL(通过 `USER_TAB_COLUMNS` 获取列名): ```sql SELECT 'CASE WHEN t1.' || column_name || ' <> t2.' || column_name || ' OR (t1.' || column_name || ' IS NULL AND t2.' || column_name || ' IS NOT NULL)' || ' THEN ''' || column_name || ''' END AS diff_' || column_name || ',' FROM user_tab_columns WHERE table_name = 'TABLE1' AND column_name <> 'ID'; -- 排除主键 ``` --- #### 方法 3:DBMS_COMPARISON 包(Oracle 11g+ 推荐) ```sql BEGIN -- 创建比较对象 DBMS_COMPARISON.CREATE_COMPARISON( comparison_name => 'COMP_TABLES', schema_name => 'YOUR_SCHEMA', table_name => 'TABLE1', remote_schema_name => 'YOUR_SCHEMA', remote_table_name => 'TABLE2' ); -- 执行比较 DECLARE v_consistent BOOLEAN; v_scan_info DBMS_COMPARISON.COMPARISON_TYPE; BEGIN v_consistent := DBMS_COMPARISON.COMPARE( comparison_name => 'COMP_TABLES', scan_info => v_scan_info, perform_row_dif => TRUE ); END; -- 查询差异 SELECT * FROM DBA_COMPARISON_ROW_DIF WHERE COMPARISON_NAME = 'COMP_TABLES'; END; ``` **优势**: - 自动记录差异位置和类型 - 支持大增量比较 - 可生成详细差异报告[^2] --- #### 方法 4:校验和对比(高效但需哈希计算) ```sql SELECT id, ORA_HASH(COL1 || COL2 || ...) AS checksum FROM table1 MINUS SELECT id, ORA_HASH(COL1 || COL2 || ...) FROM table2; ``` **适用场景**: - 快速检测行级差异 - 不关心具体差异列时 > **关键注意事项**: > 1. 所有方法需确保比较列的数据类型兼容 > 2. 包含 `NULL` 处理:`NULL <> NULL` 返回 `UNKNOWN` > 3. 大建议添加 `WHERE` 条件分段比较 > 4. 对LOB类型列需特殊处理(如 `DBMS_LOB.COMPARE()`)
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值