为什么我的 `delete where id in` 语句会删除 table 中的所有记录?

why my `delete where id in` statement drop all records in the table?

我正在使用 MySQL 版本 5.7.24。我想删除具有相同 ex_id 和 ex_type 的记录回复 table:

CREATE TABLE `reply` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `content` varchar(1024) NOT NULL,
  `ex_id` bigint(20) DEFAULT '0',
  `ex_type` int(11) DEFAULT '0',
  PRIMARY KEY (`id`),
  KEY `idx_ex_id_type` (`ex_id`,`ex_type`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

有以下数据:

+----+-------------------+-------+---------+
| id | content           | ex_id | ex_type |
+----+-------------------+-------+---------+
|  1 | this is a content |     1 |       1 |
|  2 | this a test       |     2 |       1 |
|  3 | this a contet     |     1 |       1 |
|  4 | the 4th content   |     3 |       1 |
+----+-------------------+-------+---------+

记录1和记录3共享相同的ex_id和ex_type,其中我想删除id较小的记录(记录1),所以我写了以下查询:

delete from reply where id in (
    select id from (
        select min(id) from reply group by ex_type and ex_id having count(1) > 1
    ) tmp
)
-- Query OK, 4 rows affected

这应该删除一条记录,但实际上所有记录都被删除了。

其实这个SQL有错误,内SQLselect min(id) from reply group by ex_type and ex_id having count(1) > 1return只有字段的结果:'min(id)',外sql select id from () tmp 选择了一个不存在的字段 id 导致错误,但 MySQL 仍然执行此 sql 并删除所有记录。

我想知道为什么会这样。

有趣 - 这似乎是一个错误,因为在这种情况下 DELETE 应该会失败!!

无论如何,只要给 min(id) 附加一个别名(并删除 GROUP BY 之前那个奇怪的 'and'),一切都会好起来的......(虽然,我不会写这个查询这样)

DROP TABLE IF EXISTS reply;

CREATE TABLE `reply` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `content` varchar(1024) NOT NULL,
  `ex_id` bigint(20) DEFAULT '0',
  `ex_type` int(11) DEFAULT '0',
  PRIMARY KEY (`id`),
  KEY `idx_ex_id_type` (`ex_id`,`ex_type`)
);

INSERT INTO reply VALUES
(1,'this is a content',1,1),
(2,'this a test',2,1),
(3,'this a contet',1,1),
(4,'the 4th content',3,1);


delete from reply where id in 
(
    select id from (
        select min(id) id from reply group by ex_type, ex_id having count(1) > 1
    ) tmp
);

SELECT * FROM reply;
+----+-----------------+-------+---------+
| id | content         | ex_id | ex_type |
+----+-----------------+-------+---------+
|  2 | this a test     |     2 |       1 |
|  3 | this a contet   |     1 |       1 |
|  4 | the 4th content |     3 |       1 |
+----+-----------------+-------+---------+

FWIW,对于小数据集,我可能会这样写查询...

DELETE r 
  FROM reply r
  JOIN 
     ( SELECT MIN(id) id 
         FROM reply 
        GROUP 
           BY ex_type
            , ex_id 
       HAVING COUNT(0) > 1
     ) x
    ON x.id = r.id 

您的查询在逻辑上是错误的。
这部分:

select min(id) from reply group by ex_type and ex_id having count(1) > 1

分组 ex_type and ex_id 而不是 ex_type, ex_id
最重要的是,它不会 return 列 named/aliased 作为 id.
这样:

select id from...

其实指的是table的id和return的全部id的table 结果是所有行都被删除了。
您可以看到此行为 here.
我相信这就是你想要做的:

delete from reply where id in 
(
    select id from (
        select min(id) id from reply group by ex_type, ex_id having count(*) > 1
    ) tmp
); 

...outer sql select id from () tmp selects a non exsit field id which lead to an error, but mysql execute this sql and delete all records.

I want to konw why this happened.

这个子查询不会运行自己:

select id from (
    select min(id) from reply group by ex_type and ex_id having count(1) > 1
) tmp
/* SQL Error (1054): Unknown column 'id' in 'field list' */

但是当它在子查询中 运行 时,根据范围解析规则 id 列解析为外部查询的 id 列,因为请求的列是不出现在 FROM 子句 中。查询本质上是这样的:

delete from reply where id in (
    select reply.id from (
        select min(id) from reply group by ex_type and ex_id having count(1) > 1
    ) tmp
)
/* Affected rows: 4  Found rows: 0  Warnings: 0  Duration for 1 query: 0.031 sec. */

所有行的条件都为真,因为 1 IN (1)、2 IN (2)、3 IN (3)... 都为真。更正拼写错误 (group by ex_type and ex_id) 不会解决问题,请将您的查询更改为:

delete from reply where id in (
    select tmp.id from (
        select min(id) as id from reply group by ex_type, ex_id having count(1) > 1
    ) tmp
)