MariaDB/MySQL 比较所有列的 EXCEPT 的替代方法

Alternative for EXCEPT for MariaDB/MySQL comparing all columns

我知道 MariaDB 和 MySQL 不支持 EXCEPT。 我想找到这样的替代方法:

SELECT * FROM table
EXCEPT
SELECT * FROM backup_table

其中 table 和 backup_table 具有相同的架构。

我看到的所有帖子都建议我使用 "WHERE column IN (...)" 比较单个列。 我的问题是我需要比较每个 table 的两个 table 之间的所有列。我希望将其编写为遍历所有 table 的过程或函数,以查找数据库中的任何更改。基本上,我想找出我所有 table 中已更新或插入的所有记录。

我对 MySQL 或 MariaDB 没有太多经验,但我发现这是对另一个可能对您有用的问题的回答。

SELECT  *
FROM    match m
WHERE   NOT EXISTS
        (
        SELECT  1
        FROM    email e
        WHERE   e.id = m.id
        )

Quassnoi 的功劳来自以下 post: MySQL SELECT x FROM a WHERE NOT IN ( SELECT x FROM b ) - Unexpected result

如果我面临那个任务,我会使用反加入模式。这是一个外部联接,return 来自当前 table 的所有行,以及来自备份 table 的 "matching" 行。然后在 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 列(或 table 的 PRIMARY KEY 的一部分的任何列,或任何具有 NOT NULL 约束的列。)

此查询仅 return 与备份 table 中的行不匹配的行。它不表示它的行是否不存在,或者列的值是否被更改。

要获取原始 table 中与备份 table 中的行不匹配的行,只需交换 table 名称即可。

对于所有列都定义为 NOT NULL 的 table 的特殊情况,我们可以在连接谓词上采取快捷方式。

    FROM mytable t
 NATURAL
    LEFT
    JOIN backup_mytable s
   WHERE s.id IS NULL

这相当于在两个 table 中具有相同名称的所有列的 USING 子句的 LEFT JOIN。

    FROM mytable t
    LEFT
    JOIN backup_mytable s
   USING (id, col_two, col_three, ...)
  WHERE s.id IS NULL

这相当于在每一列上指定相等比较(如果两个 table 具有相同的列)

    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 值都会影响相等比较,return NULL。

这就是第一个查询使用空值安全比较 <=>(宇宙飞船)运算符的原因。 NULL <=> NULL 将 return 为真,其中 NULL = NULL 将 return 为空。

对于第一个查询模式,与其单调乏味地输入每一列的所有这些比较,我会使用 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

我会采用该查询 return 编辑的行,并将其粘贴到

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的行; returning 行 col_one 不同。

同样,我会使用 SQL 而不是 information_schema.columns 来帮助加快查询编写过程。

version 10.3.0, MariaDB added support for missing set operations, including but not limited to EXCEPT 开始。

CREATE TABLE `table` ( `item` VARCHAR(1) NOT NULL );
CREATE TABLE `backup_table` ( `item` VARCHAR(1) NOT NULL );
INSERT INTO `table` VALUES ( 'a' ), ( 'b' ), ( 'c' );
INSERT INTO `backup_table` VALUES ( 'a' ), ( 'b' ), ( 'd' );

SELECT * FROM `table`
EXCEPT
SELECT * FROM `backup_table`;

+------+
| item |
+------+
| c    |
+------+