[英]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.