10

レコードを 1 つにマージしながら重複を削除する最良の方法は何ですか?

テーブルがプレイヤー名とその記録を次のように追跡する状況があります。

stats
-------------------------------
nick     totalgames     wins   ...
John     100            40
john     200            97
Whistle  50             47
wHiStLe  75             72
...

次のように、ニックが重複している行をマージし (大文字と小文字を区別しない場合)、レコードを 1 つにマージする必要があります。

    stats
    -------------------------------
    nick     totalgames     wins   ...
    john     300            137
    whistle  125            119
    ...

私はPostgresでこれをやっています。これを行う最良の方法は何ですか?

これを行うことで、重複が存在する名前を取得できることを私は知っています:

select lower(nick) as nick, totalgames, count(*) 
from stats 
group by lower(nick), totalgames
having count(*) > 1;

私は次のようなことを考えました:

update stats
set totalgames = totalgames + s.totalgames
from (that query up there) s
where lower(nick) = s.nick

これが正しく機能しないことを除いて。そして、重複した名前を含む他の重複行を削除できないようです。私に何ができる?助言がありますか?

4

4 に答える 4

10

SQL フィドル

これがあなたの更新です:

 UPDATE stats
 SET totalgames = x.games, wins = x.wins
 FROM (SELECT LOWER(nick) AS nick, SUM(totalgames) AS games, SUM(wins) AS wins
     FROM stats
      GROUP BY LOWER(nick) ) AS x
 WHERE LOWER(stats.nick) = x.nick;

重複行を吹き飛ばす削除は次のとおりです。

 DELETE FROM stats USING stats s2
 WHERE lower(stats.nick) = lower(s2.nick) AND stats.nick < s2.nick;

(「update...from」および「delete...using」構文はPostgres固有であり、この回答この回答から恥知らずに盗まれたことに注意してください。)

おそらく、これを実行してすべての名前を小文字にすることもできます。

 UPDATE STATS SET nick = lower(nick);

そして、'nick' の小文字バージョンに一意のインデックスを挿入します (または、その列に制約を追加して、小文字以外の値を禁止します)。

CREATE UNIQUE INDEX ON stats (LOWER(nick)); 
于 2013-08-17T05:30:51.607 に答える
3

を使用して、すべてを 1 つのステートメントで実行できますRETURNING

-- The data
CREATE TABLE stats
        ( nick VARCHAR PRIMARY KEY
        , totalgames INTEGER NOT NULL DEFAULT 0
        , wins INTEGER NOT NULL DEFAULT 0
        );

INSERT INTO stats(nick, totalgames,wins) VALUES
 ( 'John', 100, 40) ,( 'john', 200, 97)
,( 'Whistle', 50, 47) ,( 'wHiStLe', 75, 72)
, ( 'Single', 42, 13 ) -- this person has only one record
        ;
SELECT * FROM stats;

-- The query:
WITH upd AS (
        UPDATE stats dst
        SET totalgames = src.totalgames
                , wins = src.wins
        FROM ( SELECT MIN(nick) AS nick -- pick the "lowest" nick as the canonical nick
                , SUM(totalgames) AS totalgames
                , SUM(wins) AS wins
                FROM stats
                GROUP BY lower(nick)
                ) src
        WHERE dst.nick = src.nick
        RETURNING dst.nick -- only the records that have been updated
        )
-- Delete the records that were NOT updated.
DELETE FROM stats del
WHERE NOT EXISTS (
        SELECT * FROM upd
        WHERE upd.nick = del.nick
        )
        ;

SELECT * FROM stats;

出力:

INSERT 0 5
  nick   | totalgames | wins 
---------+------------+------
 John    |        100 |   40
 john    |        200 |   97
 Whistle |         50 |   47
 wHiStLe |         75 |   72
 Single  |         42 |   13
(5 rows)

DELETE 2
  nick   | totalgames | wins 
---------+------------+------
 wHiStLe |        125 |  119
 john    |        300 |  137
 Single  |         42 |   13
(3 rows)
于 2013-08-17T15:25:54.297 に答える
0

UPDATE stats SET totalgames=s.totalgames, wins=s.wins
FROM (SELECT lower(nick) AS nick,SUM(totalgames) AS totalgames,SUM(wins) AS wins FROM stats GROUP BY lower(nick))s WHERE lower(nick)=s.nick;
DELETE FROM stats WHERE
lower(nick) IN (SELECT lower(nick) FROM stats GROUP BY lower(nick) HAVING COUNT(*)>1)
AND NOT lower(nick) IN (SELECT first(nick) FROM stats GROUP BY lower(nick)動作するはずです。

于 2013-08-17T05:34:58.820 に答える