【问题标题】:SQL: How to merge case-insensitive duplicatesSQL:如何合并不区分大小写的重复项
【发布时间】:2013-08-19 13:17:30
【问题描述】:

在将记录合并为一个时删除重复项的最佳方法是什么?

我有一种情况,表格会像这样跟踪玩家姓名和他们的记录:

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

我需要合并 nick 重复的行(忽略大小写时)并将记录合并为一个,如下所示:

    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

除非这不能正常工作。而且我似乎仍然无法删除包含重复名称的其他重复行。我能做些什么?有什么建议吗?

【问题讨论】:

  • 您的统计表是否有唯一的数字 id 字段?
  • 不,它没有。主键是 nick 列。我可以制作 id,但我也必须更改软件......(我一开始没有制作这些,后来我加入了)所以我认为这可能是不可能的。
  • 没问题 - 这只是改变了我删除的方式。答案即将到来。

标签: sql postgresql duplicates


【解决方案1】:

SQL Fiddle

这是您的更新:

 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 特有的,是从 this answerthis answer 偷来的。)

您可能还想运行它来将所有名称小写:

 UPDATE STATS SET nick = lower(nick);

Aaa 并在小写版本的 'nick' 上添加唯一索引(或向该列添加约束以禁止非小写值):

CREATE UNIQUE INDEX ON stats (LOWER(nick)); 

【讨论】:

  • 哇。该更新几乎与我已经拥有的相同!我是如此接近 :( 嗯,问题是我已经在列 nick 上有一个主键 - 你认为摆脱它并创建唯一索引是必要的吗?
  • 嗯,这取决于您要如何处理这些值。如果您想完全禁止非小写值,您可以在该列上添加一个约束(“alter table stats add CONSTRAINT downcase_nick CHECK ((nick)::text = LOWER((nick)::text))”)。 (这就是我要做的。)如果您想允许混合大小写的值,只要小写版本是唯一的以避免您所处的情况,您可以更新现有索引以使用小写值。
  • 是的,我想。我只是决定强制软件在与数据库交互之前将所有内容小写。感谢您的帮助。
  • 您可能仍想添加我在上面发布的约束。您的代码可能正在做正确的事情,但在数据库级别进行额外检查永远不会有坏处。我总是喜欢防止其他未来的开发人员更改或添加允许脏数据的输入法。
【解决方案2】:

我认为在一个查询中最简单的方法是使用common table expressions:

with cte as (
    delete from stats
    where lower(nick) in (
      select lower(nick) from stats group by lower(nick) having count(*) > 1
    )
    returning *
)
insert into stats(nick, totalgames, wins)
select lower(nick), sum(totalgames), sum(wins)
from cte
group by lower(nick);

如您所见,在 cte 内部,我正在删除重复项并返回已删除的行,然后将分组的已删除数据插入回表中。

sql fiddle demo

【讨论】:

    【解决方案3】:

    这一切都可以在一个语句中完成,使用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)
    

    【讨论】:

      【解决方案4】:

      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) 应该可以工作。

      【讨论】:

        猜你喜欢
        • 2011-02-11
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-03-06
        • 2014-02-05
        • 1970-01-01
        • 2021-02-18
        • 1970-01-01
        相关资源
        最近更新 更多