【问题标题】:Query to remove "duplicate" rows using regexp使用正则表达式查询以删除“重复”行
【发布时间】:2019-05-20 10:08:57
【问题描述】:

我正在使用 PostgreSQL。我有一张桌子keywords:

# Table name: keywords
#
#  id         :integer not null, primary key
#  text       :string  not null
#  match_type :string  not null
#  adgroup_id :integer not null

表有一个唯一索引USING btree (match_type, adgroup_id, text)

现在,问题是对于相同的 adgroup_id 和 match_type 有类似 "Hello" 和 " Hello" 或 "Hello " 或 " Hello " 的文本(注意前导/尾随空格)。 问题是text 列在字符串的开头和结尾包含那些导致错误数据的空格(如果没有这些空格,它就不会通过 uniq 索引)。

我计划将来在插入之前添加空白修剪,但首先我需要清理数据。

如何删除“重复”数据而留下唯一数据(基于字符串比较没有前导和尾随空格)?

【问题讨论】:

  • 是否存在所有元素都包含空格的组?没有“你好”,只有“你好”和“你好”?
  • @S-Man 我不确定我是否理解您提出的问题。可以是 db 中的任何选项(带或不带空格)。所以可以有两行"Hello" 和" Hello" 和两行"Hi" 和"Hi " 甚至可能还有" Hi" 和"Hi " (在这种情况下我仍然必须在格式正确的数据库(修剪)
  • 好的添加了一个更通用的解决方案

标签: database postgresql unique-constraint


【解决方案1】:

demo:db<>dbfiddle (示例包含两组:“Hello”没有一个没有空格的元素;“Bye”包含两个没有空格的元素)

DELETE FROM keywords
WHERE id NOT IN (
    SELECT DISTINCT ON (trim(text))                 --1
        id
    FROM
        keywords
    ORDER BY 
        trim(text), 
        text = trim(text) DESC                   --2
)
  1. 对修剪过的文本进行分组。
  2. 如果文本是没有空格的文本,则按修剪文本和信息排序。如果有一个元素,那么它将首先排序并由DISTINCT ON 子句获取。如果没有其他元素将被采用

包含附加列的解决方案:

    DELETE FROM keywords
    WHERE id NOT IN (
        SELECT DISTINCT ON (match_type, adgroup_id, trim(text))
            id
        FROM
            keywords
        ORDER BY 
            match_type,
            adgroup_id,
            trim(text), 
            text = trim(text) DESC
    )

【讨论】:

  • @AndreyDeineko 添加了您的示例中缺少的两列
  • 所描述的步骤听起来完全正确,尽管我需要一些时间来理解排序魔法及其与 DELETE 语句的关系。非常感谢您的帮助,点赞!
  • 这部分keyword = trim(text)应该是text = trim(text)吗?
  • 是的,当然。抱歉,在我的示例中,我使用了另一个列名,因为“text”是 Postgres 中的保留名称,因此不建议进一步使用。为了确保您的示例,我之后手动更改了此处的列名。我忘记了这部分:)
  • aah,看,我们的架构存在缺陷,包括使用保留关键字......再次感谢!
【解决方案2】:

这是一种选择,使用 CTE。 CTE 查找所有具有 两个 或更多 text 值的 (match_type, adgroup_id) 组,这些值与修剪的前导和尾随空格相同。我们还计算了以下内容:

  • cnt - 对于每个组,文本的“纯”版本出现的次数。这里的纯是指没有前导或尾随空格的文本
  • rn - 每个(match_type, adgroup_id) 组的任意行号,从值 1 开始


然后,只有当它出现在重复组中并且它不是纯文本版本 (cnt &gt; 0) 或任意行号大于 1 时,我们才会删除该行。这意味着对于"Hello " 和" Hello" 的情况,这两条记录之一将被任意删除。但是,如果有第三个“纯”记录,"Hello",那么这将被保留,前两个 cased 都将被删除。

with cte as (
    select match_type, adgroup_id, trim(text) as text,
        count(case when text = trim(text) then 1 end) as cnt,
        row_number() over (partition by match_type, adgroup_id order by trim(text)) rn
    from keywords
    group by match_type, adgroup_id, trim(text)
    having count(*) > 1
)

delete
from keywords k1
where exists (select 1 from cte k2
              where k1.match_type = k2.match_type and
                    k1.adgroup_id = k2.adgroup_id and
                    k1.text <> k2.text and (k2.cnt > 0 or k2.rn > 1));

【讨论】:

  • 感谢您的建议!尽管我有一个担忧,但描述听起来差不多正确:鉴于在 DB 中没有“正确”版本 ("Hello"),此解决方案将如何处理 " Hello"(前导)和 "Hello "(尾随)。它会留下两者中的任何一个吗?
  • @AndreyDeineko 它会删除两条记录。我更新了我的答案,以便在您的示例情况下,它不会删除两条记录中的任何一条。如果您只想得到一个 "Hello" 记录(没有前导/尾随空格),那么您可能正在查看一个删除,然后是更新或插入。
  • I updated my answer so that in your example case, it would not delete either of the two records. 能否请你最后修改一个,删除两个之一?所以最终只留下一个?事实上,我们可以删除除一个之外的所有内容,然后更新以删除剩余的唯一一个中的前导/尾随空格。
  • 非常感谢您的回答!我投了赞成票,现在花点时间了解它是如何工作的
  • @AndreyDeineko 我搞砸了我对行号的使用。请尝试更新的答案。
猜你喜欢
  • 2019-08-20
  • 2016-11-24
  • 1970-01-01
  • 2011-06-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-02-16
  • 2010-10-23
相关资源
最近更新 更多