如果我有一个具有多个列值相同的MySQL表,如何删除除最近两个条目之外的所有条目?

时间:2014-11-18 19:54:43

标签: php mysql

我知道这听起来像几个问题的重复,很可能是,但我已经搜索过并尝试了我自己实现的几种可能的解决方案,但所有这些解决方案似乎都会产生某种形式的无限只是咀嚼100%CPU并且什么都不做的递归。这可能是因为我做错了或者他们不适合我,我不知道。

我的MySQL表格结构如下:

        +--------+------+-----+-------+--------+--------+----------------+
        |   id   | fid  | bid | dec_a | varc_a | varc_b | dec_b | varc_c |
        +--------+------+-----+-------+--------+--------+----------------+
        | 106861 | 4192 |  22 | 1.40  | blah   | blahbr | 0.2   | blahca |
        | 108620 | 4192 |  22 | 1.55  | blah   | blahbe | 0.2   | blahca |
        | 108621 | 4192 |  22 | 1.55  | blah   | blahbq | 0.2   | blahca | 
        | 108622 | 4192 |  22 | 1.55  | blah   | blahbw | 0.2   | blahca | 
        | 108623 | 4192 |  22 | 1.55  | blah   | blahbe | 0.2   | blahca | 
        | 108624 | 4192 |  22 | 1.55  | blah   | blahbf | 0.2   | blahca | 
        | 106863 | 4192 |  33 | 1.40  | blah   | blahba | 0.2   | blahca | 
        +--------+------+-----+-------+--------+--------+-------+--------+

" id" value是一个BIGINT自动递增值,数据按照源的正确时间顺序添加,所以我将其视为时间戳。

要确定哪些数据是重复的,我使用" fid"," bid"," varc_a"," dec_b"和" varc_c"列。从上面的示例中可以看出,基于这些列有6个重复项,这些是前六行,第七行显示了" bid"列,但显然任何列中的任何变体都将该行排除为副本。

我可以很容易地看到我想要做的事情:数据库中可能存在数百万个条目,我想根据条目ID排除最近的2个数据行,其中" fid", "出价"," varc_a"," dec_b"和" varc_c"列值相同,然后扫除剩下的内容。

对于我的生活,我无法用MySQL来弄清楚如何做到这一点,正如我所说的,我所看到的所有问题和答案似乎都不是做我想做的事情,或者我不理解提议的内容。

我知道我可以通过浏览数据并删除重复数据来使用PHP + MySQL来做到这一点,但考虑到我可以很容易地以非常低效的方式做到这一点我认为我错过了某些东西很明显,我应该只用MySQL做到这一点?

:注意:

迈克的答案很棒,根据我的问题背景,它只是稍微调整了我所需要的。我最终使用的是:

DROP TEMPORARY TABLE IF EXISTS keepers1, keepers2, keepers_all;

CREATE TEMPORARY TABLE keepers1 (KEY(id)) ENGINE=MEMORY AS 
SELECT fid, bid, varc_a, dec_b, var_c, MAX(id) AS id 
FROM market_prices
GROUP BY fid, bid, varc_a, dec_b, varc_c;

CREATE TEMPORARY TABLE keepers2 AS
SELECT fid, bid, varc_a, dec_b, varc_c, MAX(id) AS id
FROM market_prices AS k
WHERE NOT EXISTS (SELECT 1 FROM keepers1 WHERE id = k.id)
GROUP BY  fid, bid, varc_a, dec_b, varc_c;

CREATE TEMPORARY TABLE keepers_all (KEY(id)) ENGINE=MEMORY AS
SELECT id FROM keepers1
UNION ALL
SELECT id FROM keepers2;

DELETE k.* FROM market_prices AS k WHERE NOT EXISTS (SELECT 2 FROM keepers_all WHERE id = k.id);

当分组确保只使用重复的列并且在最后一个语句中时,SELECT应该是你想要保留的记录数,我在那里需要一个SELECT 2。

是时候把杯子举到一小时了!

2 个答案:

答案 0 :(得分:1)

您需要写一个stored procedure。您可以通过PHP或MySQL直接创建存储过程:

通过PHP创建

$createProc = "DROP PROCEDURE IF EXISTS `remove_dups`;
    CREATE DEFINER=`root`@`localhost` PROCEDURE `remove_dups`( In id varchar(255))
    BEGIN
        ...my code...
    END;";

$conn = new PDO("mysql:host=$host;dbname=$dbname", $username, $password);

//create the stored procedure
$stmt = $conn->prepare($createProc);

$stmt->execute();

通过MySQL GUI创建

只需将create语句放在文本框中并运行它(针对正确的数据库):

    CREATE DEFINER=`root`@`localhost` PROCEDURE `remove_dups`( In id varchar(255))
    BEGIN
        ...my code...
    END;";

然后,您可以从PHP或MySQL调用此过程。

在存储过程中,您需要声明一些变量来存储值并检查以查找具有相同值的行(使用cursor),然后检查以前的ID行的。如果所有值都相同,请删除具有较低ID的那个。

答案 1 :(得分:1)

这可能是解决您问题的方法。

但是,由于没有日期时间列,我假设id列是主键。它是Auto_increment。所以我的假设是记录越新越好。 (除非你有一些旧的数据转储到表中,否则它应该是真的)

确保在删除之前备份数据,因为这会导致永久性数据丢失。更好的是,您可以将当前表的副本复制到另一个表中并处理新表以确保下面的逻辑是正确的。然后将我下面的查询更改为tbl_new,而不是tbl

您可以通过

之类的内容复制表格
CREATE TABLE tbl_new LIKE tbl;

我已为每个查询留下评论

DROP TEMPORARY TABLE IF EXISTS keepers1, keepers2, keepers_all;
-- get the #1 top records
CREATE TEMPORARY TABLE keepers1 (KEY(id)) ENGINE=MEMORY AS
SELECT fid, bid, dec_a, varc_a, varc_b, dec_b, varc_c, MAX(id) AS id
FROM tbl
GROUP BY fid, bid, dec_a, varc_a, varc_b, dec_b, varc_c;

-- get the #2 top records
CREATE TEMPORARY TABLE keepers2 AS
SELECT fid, bid, dec_a, varc_a, varc_b, dec_b, varc_c, MAX(id) AS id
FROM tbl AS k
WHERE NOT EXISTS (SELECT 1 FROM keepers1 WHERE id = k.id)
GROUP BY fid, bid, dec_a, varc_a, varc_b, dec_b, varc_c;


-- create a temp table where you have all he ids that you want to keep
CREATE TEMPORARY TABLE keepers_all (KEY(id)) ENGINE=MEMORY AS
SELECT id FROM keepers1
UNION ALL
SELECT id FROM keepers2;


-- delete all records that you don't want to keep
DELETE k.* FROM tbl AS k WHERE NOT EXISTS (SELECT 1 FROM keepers_all WHERE id = k.id);

如果这是一次性清理作业,那么您应该能够从控制台执行查询。但如果您正在寻找招聘工作,您应该将这些代码放入程序中。

注意:这里我使用MEMORY TEMPORARY表来获得更好的性能。您可能会遇到一个问题"Table is Full",这是因为您有太多记录。然后你可以增加会话的max_heap_table_size值

之类的东西
SET SESSION tmp_table_size = 1024 * 1024 * 1024 * 2; -- this will set it to 2G
SET SESSION max_heap_table_size = 1024 * 1024 * 1024 * 2; -- this will set it to 2G

这将为您提供当前值

SELECT VARIABLES LIKE 'max_heap_table_size';
SELECT VARIABLES LIKE 'tmp_table_size';