如果我面临这个任务,我会使用反连接模式。这是一个外连接,用于返回当前表中的所有行,以及备份表中的“匹配”行。然后在 WHERE 子句中,我们排除所有完全匹配的行。返回不匹配的行。
SELECT t.*
FROM mytable t
LEFT
JOIN backup_mytable s
ON s.id <=> t.id
AND s.col_two <=> t.col_two
AND s.col_three <=> t.col_three
AND ...
WHERE s.id IS NULL
这假定列id 保证为非NULL。 PRIMARY KEY 列(或作为表的 PRIMARY KEY 一部分的任何列,或具有 NOT NULL 约束的任何列都可以使用。)
此查询仅返回与备份表中的行不匹配的行。它不指示它的行是否不存在,或者列的值是否已更改。
要获取原始表中与备份表中的行不匹配的行,只需交换表名即可。
对于所有列都定义为 NOT NULL 的表的特殊情况,我们可以在连接谓词上采用快捷方式。
FROM mytable t
NATURAL
LEFT
JOIN backup_mytable s
WHERE s.id IS NULL
这相当于一个 LEFT JOIN 与一个 USING 子句的所有列在两个表中命名相同。
FROM mytable t
LEFT
JOIN backup_mytable s
USING (id, col_two, col_three, ...)
WHERE s.id IS NULL
这相当于在每一列上指定一个相等比较(如果两个表具有相同的列)
FROM mytable t
LEFT
JOIN backup_mytable s
ON s.id = t.id
AND s.col_two = t.col_two
AND s.col_three = t.col_three
任何列中出现的任何 NULL 值都会与相等比较发生冲突,并返回 NULL。
这就是为什么第一个查询使用空安全比较<=>(宇宙飞船)运算符。 NULL <=> NULL 将返回 TRUE,NULL = NULL 将返回 NULL。
对于第一个查询模式,我会使用 SQL 来帮助我生成所需的 SQL,而不是繁琐地输入每一列的所有比较。
SELECT CONCAT(' AND s.`',c.column_name,'` <=> t.`',c.column_name,'`') AS `-- stmt`
FROM information_schema.columns c
WHERE c.table_schema = 'mydatabase'
AND c.table_name = 'mytable'
ORDER BY c.ordinal_position
我会获取该查询返回的行,并将其粘贴到
SELECT t.*
FROM ... t
JOIN ... s
ON 1=1
-- paste here --
WHERE s.id IS NULL
ORDER BY t.id
如果我需要只匹配id 列的查询,并且需要识别哪些 列发生了变化,我会在SELECT 列表中使用表达式。例如:
SELECT s.`id` <=> t.`id` AS `match_id`
, s.`col_one` <=> t.`col_one` AS `match_col_one`
, s.`col_three` <=> t.`col_three` AS `match_col_three`
FROM mytable t
JOIN backup_mytable s
ON s.id = t.id
HAVING NOT match_col_one
此处在HAVING 子句中引用SELECT 列表中的列别名,以排除具有相同col_one 值的行;返回 col_one 不同的行。
再次,我将针对 information_schema.columns 使用 SQL 来帮助加快查询编写过程。