不靠谱数据库去重

July 28, 2013

用的是SQLite。

现在有一个坑爹的表:

CREATE TABLE "users" (
    "id"        INTEGER PRIMARY KEY AUTOINCREMENT,
    "company"   VARCHAR,
    "province"  VARCHAR,
    "city"      VARCHAR,
    "name"      VARCHAR,
     ....
    unique (company, hangye, province, city, ... )
);

简单来说就是一个整型ID加一大堆可空的VARCHAR,UNIQUE限制包含除ID之外全部的属性。我现在也不知道为什么UNIQUE在INSEERT 的时候没有起作用,倒是在 UPDATE 的时候有效(后来我知道那是因为NULL != NULL)。

所以只好自己把重复的删掉,折腾一番SQL之后无果,只好换PHP~。~我手边能用的只有PHP~。~

我思路很简单,把全部非主键连接起来,哈希后作为键,以ID作为值插入一个哈希表,这样重复的行就会被映射到哈希表里同一个键,相同的键插入的时候覆盖掉之前的值,这样就能等到全部要保留的ID,然后直接构造一个NOT IN的SQL语句就可以了。

目测数据只会有五万条,虽然不多,但直接这么干可能构造出一个超大的SQL语句,所以是很不靠谱的方法。

<?php

function remove_duplicate_old() {
    $db = new PDO('sqlite:db.db3');

    $stm = $db->query("select * from users");
    $stm->setFetchMode(PDO::FETCH_NUM);

    $hashtable = [];
    while ($row = $stm->fetch()) {
        // hashrow返回hash后的非主键和ID
        $h = hashrow($row);
        // 插入到hashtable
        $hashtable[$h["key"]] = $h["id"];
    }

    // 构造ID列表,形如(1,2,9)
    $ids = join(",", array_values($hashtable));
    $s = "(". $s . ")";

    // 构造完整SQL语句
    $q = "delete from users where id not in " . $s;

    $db->exec($q);
}

function hashrow($row) {
    // 跳过ID,用#连接其他属性
	$key = md5(join("#", array_slice($row, 1)));
    return array(
        "key" => $key,
        "id"  => $row[0]
    );
}

然后我还用了个有点区分意义的键进行分组之后再用上面的方法去重,还是不靠谱的方法。

我还想过把要保留的ID发在一个临时表,这样就可以不用自己拼SQL语句了,只是想想。

后来我发现用GROUP BY又可以了:

delete from users
    where id not in
        (select id from users group by company, province, ... )

其实我最最初就是想这样用GROUP BY的,但是我试的时候发现不行所以才弄出上面不靠谱的方法,现在已经不知道我一开始是哪里写错了。

又折腾一番之后,发现还是建临时表好,把不重复的数据复制到临时表,然后删掉原来,再复制回去。或者直接建多一个数据库,然后在两个数据库间切换,就不用复制两次了。