簡體   English   中英

BigQuery“薛定諤行”或為什么 ROW_NUMBER() 不是一個好的標識符

[英]BigQuery "Schrödingers Row" or why ROW_NUMBER() is not a good identifier

情況

我們有一個相當復雜的內部邏輯來將營銷支出分配給各種渠道,並且目前已經開始修改我們的一些查詢以簡化設置。 我們最近遇到了一個非常令人費解的案例,其中使用ROW_NUMBER() OVER()來識別唯一行會導致非常奇怪的結果。

問題

本質上,使用ROW_NUMBER() OVER()會產生我所說的薛定諤行。 因為它們似乎同時匹配和不匹配(請在下面找到可復制的查詢)。 在隨附的屏幕截圖(這是查詢的結果)中可以清楚地看到

german_spend + non_german_spend > total_spend

事實並非如此。

在此處輸入圖像描述

詢問

請注意,每次運行查詢都會給您不同的結果,因為它依賴於 RAND() 來生成虛擬數據。 另外請注意,該查詢是我們正在做的事情的一個非常簡化的版本。 由於超出本文 scope 的原因,我們需要唯一標識存儲桶。

###################
# CREATE Dummy Data
###################
DECLARE NUMBER_OF_DUMMY_RECORDS DEFAULT 1000000;

WITH data AS (
  SELECT
    num as campaign_id, 
    RAND() as rand_1,
    RAND() as rand_2
  FROM
    UNNEST(GENERATE_ARRAY(1, NUMBER_OF_DUMMY_RECORDS)) AS num
),

spend_with_categories AS (
  SELECT
    campaign_id,
    CASE 
      WHEN rand_1 < 0.25 THEN 'DE'
      WHEN rand_1 < 0.5 THEN 'AT'
      WHEN rand_1 < 0.75 THEN 'CH'
      ELSE 'IT'
    END AS country,
    CASE 
      WHEN rand_2 < 0.25 THEN 'SMALL'
      WHEN rand_2 < 0.5 THEN 'MEDIUM'
      WHEN rand_2 < 0.75 THEN 'BIG'
      ELSE 'MEGA'
    END AS city_size,
    CAST(RAND() * 1000000 AS INT64) as marketing_spend
  FROM
  data
),
###################
# END Dummy Data
###################

spend_buckets AS (
  SELECT
    country,
    city_size,
    CONCAT("row_", ROW_NUMBER() OVER()) AS identifier,
    #MD5(CONCAT(country, city_size)) AS identifier, (this works)
    SUM(marketing_spend) AS marketing_spend
  FROM 
    spend_with_categories
  GROUP BY 1,2
),

german_spend AS (
  SELECT
    country,
    ARRAY_AGG(identifier) AS identifier,
    SUM(marketing_spend) AS marketing_spend
  FROM 
    spend_buckets
  WHERE
    country = 'DE'
  GROUP BY
    country
),

german_identifiers AS (
  SELECT id AS identifier FROM german_spend, UNNEST(identifier) as id
),

non_german_spend AS (
  SELECT SUM(marketing_spend) AS marketing_spend FROM spend_buckets WHERE identifier NOT IN (SELECT identifier FROM german_identifiers)
)

(SELECT "german_spend" AS category, SUM(marketing_spend) AS marketing_spend FROM german_spend
UNION ALL
SELECT "non_german_spend" AS category, SUM(marketing_spend) AS marketing_spend FROM non_german_spend
UNION ALL
SELECT "total_spend" AS category, SUM(marketing_spend) AS marketing_spend FROM spend_buckets)

解決方案

我們實際上能夠通過使用密鑰的 hash 而不是ROW_NUMBER() OVER()標識符來解決問題,但出於好奇,我仍然很想了解導致這種情況的原因。

附加條款

  • 使用GENERATE_UUID() AS identifier而不是CONCAT("row_", ROW_NUMBER() OVER()) AS identifier導致幾乎 0 匹配。 即整個支出被歸類為非德國。

  • 將 spend_buckets 寫入表也可以解決問題,這讓我相信ROW_NUMBER() OVER()可能是延遲執行的?

  • 無論生成“唯一”ID 的方法如何,使用少量的虛擬數據也會產生不匹配的結果

Hash 函數是一種比生成每天都在變化的行號更好的標記行的方法。

CTE( with表)不是持久的,而是針對查詢中使用的每次計算。

在查詢中多次運行相同的 CTE 會產生不同的結果:

With test as (Select rand() as x)

Select * from test
union all Select * from test
union all Select * from test

一個好的解決方案是使用temp table 一種解決方法是使用搜索 CTE 表,它創建一個 row_number 或生成隨機數,並在以下多次使用。 這些 CTE 將重命名並在遞歸 CTE 中使用,然后使用后面的 CTE。 在您的示例中,它是spend_buckets

WITH recursive
...
spend_buckets_ as (
...),
spend_buckets as
(select * from spend_buckets_
union all select * from spend_buckets_
where false
),

然后值將匹配。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM