【问题标题】:Find and Delete Duplicate rows in MySQL在 MySQL 中查找和删除重复行
【发布时间】:2016-08-03 20:21:26
【问题描述】:

我在使用以下设置的数据库表中查找重复项时遇到问题:

==========================================================================
| stock_id  | product_id  | store_id  | stock_qty  | updated_at          |
==========================================================================
| 9990      | 51          | 1         | 13         | 2014-10-25 16:30:01 |
| 9991      | 90          | 2         | 5          | 2014-10-25 16:30:01 |
| 9992      | 161         | 1         | 3          | 2014-10-25 16:30:01 |
| 9993      | 254         | 1         | 18         | 2014-10-25 16:30:01 |
| 9994      | 284         | 2         | 12         | 2014-10-25 16:30:01 |
| 9995      | 51          | 1         | 11         | 2014-10-25 17:30:02 |
| 9996      | 90          | 2         | 5          | 2014-10-25 17:30:02 |
| 9997      | 161         | 1         | 3          | 2014-10-25 17:30:02 |
| 9998      | 254         | 1         | 16         | 2014-10-25 17:30:02 |
| 9999      | 284         | 2         | 12         | 2014-10-25 17:30:02 |
==========================================================================

每小时都会将库存更新导入此表,我正在尝试查找重复的库存条目(任何具有匹配产品 ID 和商店 ID 的行),以便删除最旧的。下面的查询是我的尝试,通过在这样的连接上比较产品 ID 和商店 ID,我可以找到一组重复项:

SELECT s.`stock_id`, s.`product_id`, s.`store_id`, s.`stock_qty`, s.`updated_at`
FROM `stock` s
INNER JOIN `stock` j ON s.`product_id`=j.`product_id` AND s.`store_id`=j.`store_id`
GROUP BY `stock_id`
HAVING COUNT(*) > 1
ORDER BY s.updated_at DESC, s.product_id ASC, s.store_id ASC, s.stock_id ASC;

虽然此查询有效,但它不会找到所有重复项,只有 1 组,这意味着如果导入出错并且直到早上才被注意到,那么我们可能会留下大量重复项股票条目。遗憾的是,我缺乏 MySQL 技能,我完全不知道如何以快速、可靠的方式查找和删除所有重复项。

欢迎任何帮助或想法。谢谢

【问题讨论】:

  • 为什么不将它们设置为复合PK?
  • 嗨@jbutler483,很好的问题,我们正在使用的系统有一个需要存在单个主键的ORM。我们真的在 ORM 上以其他方式通过同一系统构建 CRUD 管理。
  • 加上数据库将保留最旧的数据,这不是必需的功能:)
  • 可能是this describes?this 之类的命令
  • @HerbageOnion 我不明白为什么只有一个 PK 很重要——只要你可以在 (product_id,store_id) 上有一个唯一的键约束。那么INSERT... ON DUPLICATE KEY UPDATE就可以解决问题了

标签: mysql duplicates


【解决方案1】:

store_idproduct_id 和“较旧”上的自联接与 DISTINCT 结合应该会为您提供所有存在较新版本的行:

> SHOW CREATE TABLE stock;
CREATE TABLE `stock` (
  `stock_id` int(11) NOT NULL,
  `product_id` int(11) DEFAULT NULL,
  `store_id` int(11) DEFAULT NULL,
  `stock_qty` int(11) DEFAULT NULL,
  `updated_at` datetime DEFAULT NULL,
  PRIMARY KEY (`stock_id`)

> select * from stock;
+----------+------------+----------+-----------+---------------------+
| stock_id | product_id | store_id | stock_qty | updated_at          |
+----------+------------+----------+-----------+---------------------+
|        1 |          1 |        1 |         1 | 2001-01-01 12:00:00 |
|        2 |          2 |        2 |         1 | 2001-01-01 12:00:00 |
|        3 |          2 |        2 |         1 | 2002-01-01 12:00:00 |
+----------+------------+----------+-----------+---------------------+


> SELECT DISTINCT s1.stock_id, s1.store_id, s1.product_id, s1.updated_at   
   FROM stock s1   JOIN stock s2     
     ON s1.store_id = s2.store_id    
    AND s1.product_id = s2.product_id    
    AND s1.updated_at < s2.updated_at;
+----------+----------+------------+---------------------+
| stock_id | store_id | product_id | updated_at          |
+----------+----------+------------+---------------------+
|        2 |        2 |          2 | 2001-01-01 12:00:00 |
+----------+----------+------------+---------------------+

> DELETE stock FROM stock 
               JOIN stock s2  ON stock.store_id = s2.store_id  
                             AND stock.product_id = s2.product_id 
                             AND stock.updated_at < s2.updated_at;
Query OK, 1 row affected (0.02 sec)

> select * from stock;
+----------+------------+----------+-----------+---------------------+
| stock_id | product_id | store_id | stock_qty | updated_at          |
+----------+------------+----------+-----------+---------------------+
|        1 |          1 |        1 |         1 | 2001-01-01 12:00:00 |
|        3 |          2 |        2 |         1 | 2002-01-01 12:00:00 |
+----------+------------+----------+-----------+---------------------+

【讨论】:

  • 可以与DELETE 组合,只需选择s1.stock_id 并将该查询与DELETE 组合为`DELETE FROM stock WHERE stock_id IN (SELECT DISTINCT s1.stock_id FROM ...)`
  • 感谢您的帮助 Hartmut,您的意思是使用 s1.store_id = s2.store_id 而不是 s1.stock_id = s2.stock_id?我试过了,我没有得到任何结果......
  • 明确一点,没有结果,也没有错误。感谢您迄今为止的帮助。
  • 请以当前(编辑/修复)形式尝试查询
  • 我又试了一次,我认为sq是另一个错字,所以我用了s1,但仍然没有结果
【解决方案2】:

或者你可以使用存储过程:

DELIMITER //
DROP PROCEDURE IF EXISTS removeDuplicates;


 CREATE PROCEDURE removeDuplicates(
   stockID INT
 )
 BEGIN


    DECLARE stockToKeep INT;
    DECLARE storeID INT;
    DECLARE productID INT;

 -- gets the store and product value
 SELECT DISTINCT store_id, product_id
 FROM stock
  WHERE stock_id = stockID  
  LIMIT 1
 INTO
  storeID, productID;

 SELECT stock_id
 FROM stock
  WHERE product_id = productID AND store_id = storeID  
  ORDER BY updated_at DESC
  LIMIT 1
 INTO
  stockToKeep;

    DELETE FROM stock 
    WHERE product_id = productID AND store_id = storeID 
    AND stock_id != stockToKeep;
END //
DELIMITER ;

然后通过游标过程为每对产品 ID 和商店 ID 调用它: 分隔符 // 创建过程 updateTable() BEGIN DECLARE done BOOLEAN DEFAULT FALSE; 声明 stockID INT 未签名; DECLARE CURSOR FOR SELECT DISTINCT stock_id FROM stock; DECLARE CONTINUE HANDLER FOR NOT FOUND SET done := TRUE;

  OPEN cur;

  testLoop: LOOP
    FETCH cur INTO stockID;
    IF done THEN
      LEAVE testLoop;
    END IF;
    CALL removeDuplicates(stockID);
  END LOOP testLoop;

  CLOSE cur;
END//
DELIMITER ;

然后调用第二个过程

CALL updateTable();

【讨论】:

  • 嗨,谢谢你的详细回答,老实说,这把我吓坏了,我不怀疑你知道你的东西,这可能是一个很好的解决方案,但我需要能够维护我实施的解决方案,但我认为我无法做到这一点:(非常感谢您抽出宝贵时间,我将解构您的解决方案以了解有关存储过程的更多信息!!:)
【解决方案3】:

您可以使用此查询:

DELETE st FROM stock st, stock st2 
WHERE st.stock_id < st2.stock_id AND st.product_id = st2.product_id AND 
st.store_id = st2.store_id;

此查询将删除具有相同product_idstore_id 的旧记录并保留最新记录。

【讨论】:

    猜你喜欢
    • 2015-03-06
    • 2020-07-07
    • 1970-01-01
    • 2020-08-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-01-16
    • 1970-01-01
    相关资源
    最近更新 更多