【问题标题】:Delete duplicate rows from a BigQuery table从 BigQuery 表中删除重复的行
【发布时间】:2016-08-09 02:26:05
【问题描述】:

我有一个包含 >1M 行数据和 20 多列的表。

在我的表 (tableX) 中,我在一个特定列 (troubleColumn) 中识别出重复记录 (~80k)。

如果可能,我想保留原始表名并从有问题的列中删除重复记录,否则我可以创建一个具有相同架构但没有重复项的新表 (tableXfinal)。

我不精通 SQL 或任何其他编程语言,所以请原谅我的无知。

delete from Accidents.CleanedFilledCombined 
where Fixed_Accident_Index 
in(select Fixed_Accident_Index from Accidents.CleanedFilledCombined 
group by Fixed_Accident_Index 
having count(Fixed_Accident_Index) >1);

【问题讨论】:

  • 我刚刚读到 BigQuery 表只是追加的,所以我想我需要复制我的表!
  • 要对单个分区上的行进行重复数据删除,请参阅:stackoverflow.com/a/57900778/132438

标签: distinct google-bigquery


【解决方案1】:

您可以通过运行重写表的查询来删除重复项(您可以使用与目标相同的表,也可以创建一个新表,验证它是否具有您想要的内容,然后将其复制到旧表)。

一个应该可以工作的查询在这里:

SELECT *
FROM (
  SELECT
      *,
      ROW_NUMBER()
          OVER (PARTITION BY Fixed_Accident_Index)
          row_number
  FROM Accidents.CleanedFilledCombined
)
WHERE row_number = 1

【讨论】:

  • 请参阅下面的答案,了解#standardSQL 的更具可扩展性的替代方案
  • 有没有办法通过 API 做到这一点?
  • 覆盖的一个问题是新表的schema的字段都是可以为空的
  • 这是您在 S/O 上所能获得的最可靠的答案。谢谢乔丹。
  • 一般来说,覆盖现有表是不好的做法,因为您可能会发现您在查询的某个地方犯了错误。最好把它写成一个单独的表,一旦你确定它是好的,删除旧的并重命名新的。
【解决方案2】:

如果您的架构没有任何记录 - 以下 Jordan 的答案 variation 可以很好地覆盖同一张表或新表等。

SELECT <list of original fields>
FROM (
  SELECT *, ROW_NUMBER() OVER (PARTITION BY Fixed_Accident_Index) AS pos,
  FROM Accidents.CleanedFilledCombined
)
WHERE pos = 1

在更一般的情况下 - 具有带有记录/网络字段等的复杂架构 - 上述方法可能是一个挑战。

我建议尝试使用Tabledata: insertAll API,并将每行的rows[].insertId 设置为各自的Fixed_Accident_Index。 在这种情况下,BigQuery 将消除重复行

当然,这将涉及一些客户端编码 - 因此可能与这个特定问题无关。 我自己也没有尝试过这种方法,但觉得尝试一下可能会很有趣:o)

【讨论】:

  • 谢谢米哈伊尔,你已经救了我好几次培根了!
  • 如果您有嵌套/重复字段,我提到的查询应该可以工作,只要您将查询选项设置为允许大结果并防止展平。
  • 不列出原始字段,如果您使用标准 SQL,您可以使用类似:SELECT * except(pos) FROM (...) WHERE pos = 1;
  • 嗨,伙计们,就这个重复数据删除主题而言,假设我们在上面选择了一个有效的 SQL,并且我们希望优先调用它 (savedquery) 来执行,然后将重复数据数据集写回同一个表(有效地压倒一切)。假设在这种情况下,它使用诸如气流之类的东西进行调度,但是还有另一个进程定期加载新事件,如果说对于一个大表,sql 正在运行并且新数据同时到达,这里是否有可能丢失数据 - 然后您正在写回可能没有新数据的结果?这可能吗?如果是这样,如何最好地避免?谢谢
  • @AntsaR - 太棒了!很高兴它有帮助:o)
【解决方案3】:

2019 年更新:要使用 MERGE 对单个分区上的行进行重复数据删除,请参阅:


Jordan 答案的替代方案 - 当重复次数过多时,这个可以更好地扩展:

#standardSQL
SELECT event.* FROM (
  SELECT ARRAY_AGG(
    t ORDER BY t.created_at DESC LIMIT 1
  )[OFFSET(0)]  event
  FROM `githubarchive.month.201706` t 
  # GROUP BY the id you are de-duplicating by
  GROUP BY actor.id
)

或者更短的版本(取任意行,而不是最新的行):

SELECT k.*
FROM (
  SELECT ARRAY_AGG(x LIMIT 1)[OFFSET(0)] k 
  FROM `fh-bigquery.reddit_comments.2017_01` x 
  GROUP BY id
)

要对现有表中的行进行重复数据删除:

CREATE OR REPLACE TABLE `deleting.deduplicating_table`
AS
# SELECT id FROM UNNEST([1,1,1,2,2]) id
SELECT k.*
FROM (
  SELECT ARRAY_AGG(row LIMIT 1)[OFFSET(0)] k 
  FROM `deleting.deduplicating_table` row
  GROUP BY id
)

【讨论】:

  • 嗨 Felipe,非常酷!出于对此的好奇,您将如何构造一个标准 SQL 查询(仅)在源表上使用“DELETE”DML 或重写以删除重复项?
  • 答案已更新,对现有表进行了一步重复数据删除
  • 当我运行较短的版本时,我的查询需要很长时间才能响应。
  • @intotecho 很奇怪 - 更长的版本需要更少的时间来执行?尝试在 bigquery 问题跟踪器上发布您的工作 ID
  • 啊,我忘了包括第一行 CREATE OR REPLACE TABLE deleting.deduplicating_table。这就是它没有完成的原因。
【解决方案4】:

不知道为什么没有人提到 DISTINCT 查询。

这里是清除重复行的方法:

CREATE OR REPLACE TABLE project.dataset.table
AS
SELECT DISTINCT * FROM project.dataset.table

【讨论】:

  • 如果您的表中有多个列,这将不起作用(或者我做错了什么?)
  • 绝对是我想做的最简单的方法 - 谢谢!不直接回答 OP 的问题,但它回答了我为何来到这里 :) @OriolNieto - 它适用于您的所有专栏。如果您想验证它是如何工作的,您可以将 * 换成特定列的列表
  • 如果现有表已分区,则此方法不起作用。
  • 我认为如果你有一个结构列,它就不能与 *.这可能就是@OriolNieto 看到的。
  • 或者如果我们想要删除其他列中具有相同 id 但不同值的行,即 updated_at
【解决方案5】:

如果你有一个大尺寸的分区表,并且只有在某个分区范围内有重复。您不想过度扫描或处理整个表。将下面的 MERGE SQL 与分区范围的谓词一起使用:

-- WARNING: back up the table before this operation
-- FOR large size timestamp partitioned table 
-- -------------------------------------------
-- -- To de-duplicate rows of a given range of a partition table, using surrage_key as unique id
-- -------------------------------------------

DECLARE dt_start DEFAULT TIMESTAMP("2019-09-17T00:00:00", "America/Los_Angeles") ;
DECLARE dt_end DEFAULT TIMESTAMP("2019-09-22T00:00:00", "America/Los_Angeles");

MERGE INTO `gcp_project`.`data_set`.`the_table` AS INTERNAL_DEST
USING (
  SELECT k.*
  FROM (
    SELECT ARRAY_AGG(original_data LIMIT 1)[OFFSET(0)] k 
    FROM `gcp_project`.`data_set`.`the_table` AS original_data
    WHERE stamp BETWEEN dt_start AND dt_end
    GROUP BY surrogate_key
  )

) AS INTERNAL_SOURCE
ON FALSE

WHEN NOT MATCHED BY SOURCE
  AND INTERNAL_DEST.stamp BETWEEN dt_start AND dt_end -- remove all data in partiion range
    THEN DELETE

WHEN NOT MATCHED THEN INSERT ROW

信用:https://gist.github.com/hui-zheng/f7e972bcbe9cde0c6cb6318f7270b67a

【讨论】:

    【解决方案6】:

    Felipe's answer 是大多数情况下的最佳方法。这是实现相同目的的更优雅的方法:

    CREATE OR REPLACE TABLE Accidents.CleanedFilledCombined
    AS
    SELECT 
      Fixed_Accident_Index, 
      ARRAY_AGG(x LIMIT 1)[SAFE_OFFSET(0)].* EXCEPT(Fixed_Accident_Index)
    FROM Accidents.CleanedFilledCombined AS x
    GROUP BY Fixed_Accident_Index;
    

    为安全起见,请确保在运行此之前备份原始表 ^^

    如果可能,我不建议使用 ROW NUMBER() OVER() 方法,因为您可能会遇到 BigQuery 内存限制并出现意外错误。

    【讨论】:

      【解决方案7】:
      1. 更新 BigQuery 架构,将新表列设为 bq_uuid,使其为 NULLABLE 并键入 STRING
      2. 例如通过运行相同的命令 5 次来创建重复的行

      插入beginner-290513.917834811114.messages (id, type, flow, updated_at) 值(19999,"hello", "inbound", '2021-06-08T12:09:03.693646')

      1. 检查是否存在重复条目 select * from beginner-290513.917834811114.messages where id = 19999

      2. 使用generate uuid函数生成每条消息对应的uuid 更新beginner-290513.917834811114.messages 设置 bq_uuid = GENERATE_UUID() 其中id>0

      3. 清除重复条目

      beginner-290513.917834811114.messages删除 bq_uuid 在哪里 (选择 bq_uuid 从 (选择 bq_uuid, ROW_NUMBER() OVER( PARTITION BY updated_at 按 bq_uuid 排序)作为 row_num 来自beginner-290513.917834811114.messages) t WHERE t.row_num > 1 );

      【讨论】:

        【解决方案8】:

        更简单的答案,没有子选择

          SELECT
              *,
              ROW_NUMBER()
                  OVER (PARTITION BY Fixed_Accident_Index)
                  row_number
          FROM Accidents.CleanedFilledCombined
          WHERE TRUE
          QUALIFY row_number = 1
        

        Where True 是必需的,因为qualify 需要where、group by 或having 子句

        【讨论】:

          猜你喜欢
          • 2019-09-13
          • 1970-01-01
          • 1970-01-01
          • 2020-03-30
          • 2012-10-21
          • 1970-01-01
          • 2019-05-08
          • 1970-01-01
          相关资源
          最近更新 更多