如何对 BigQuery 中的两个表进行条件连接?

Posted

技术标签:

【中文标题】如何对 BigQuery 中的两个表进行条件连接?【英文标题】:How to do conditional join on two tables in BigQuery? 【发布时间】:2020-07-09 19:37:19 【问题描述】:

我想加入两个表,如果列不匹配,则应应用另一个条件来加入表。表应基于两个产品和级别列进行连接。如果产品匹配但级别不匹配,则匹配顺序应为低 > 中 > 高。例如,在场景 1 中,表 2 应与表 1 的 Level Med(第 2 行)匹配。如果没有“Med”级别,则将与“High”级别匹配。例如,在场景 2 中,表 2 应与表 1 的 Level High(第 1 行)匹配。如果产品不匹配或 level 为 NA,则 Value 应为 0。

这是我的问题的不同场景。表 1 为主表,表 2 可以有不同的场景。

表 1

ID  Product_1    Product_2    Level    Value
1   C            D            High     10
2   A            B            Med      11
3   A            B            High     12
4   B            C            Med      13
5   B            C            High     9

表2的不同场景

场景 1,表 2

Product_1    Product_2    Level
A            B            Low

预期输出

Product_1    Product_2    Level    Value
A            B            Med      11

场景 2,表 2

Product_1    Product_2    Level
C            D            Low

预期输出

Product_1    Product_2    Level    Value
C            D            High      10

场景 3,表 2

Product_1    Product_2    Level
A            B            Med

预期输出

Product_1    Product_2    Level    Value
A            B            Med      11

场景 4,表 2

Product_1    Product_2    Level
M            N            High

预期输出

Product_1    Product_2    Level    Value
M            N            High      0

场景 5,表 2

Product_1    Product_2    Level
A            B            NA

预期输出

Product_1    Product_2    Level    Value
A            B            NA       0

我试过的代码,基本匹配,如果不匹配则返回“0”

WITH `project.dataset.Table1` AS (
 SELECT 1 ID, 'C' Product_1, 'D' Product_2, 'High' Level, 10 Value UNION ALL
 SELECT 2, 'A', 'B', 'Med', 11 UNION ALL
 SELECT 3, 'A', 'B', 'High', 12 UNION ALL
 SELECT 4, 'B', 'C', 'Med', 13 UNION ALL
 SELECT 5, 'B', 'C', 'High', 9 
),

`project.dataset.Table2` AS (
 SELECT 2 ID, 'A' Product_1, 'B' Product_2, 'Low' Level 
 ),
 
-- The Above table idea is taken from Mikhail's solution

get_values as (
select 
     t1.ID
    ,t2.Product_1
    ,t2.Product_2
    ,t2.Level      
    ,t1.Value
    
FROM `project.dataset.Table1` AS t1
join `project.dataset.Table2` t2 using(Product_1,Product_2,Level)
)

select t2.ID
    ,t2.Product_1
    ,t2.Product_2
    ,t2.Level
    ,IFNULL(Value, 0) as Value

from `project.dataset.Table2` t2
left join get_values gv on t2.ID = gv.ID

谢谢

【问题讨论】:

请use text, not images/links, for text--including tables & ERDs。仅将图像用于无法表达为文本或增强文本的内容。在图像中包含图例/键和说明。请在代码问题中给出minimal reproducible example--cut & paste & runnable code,包括最小的代表性示例输入作为代码;期望和实际输出(包括逐字错误消息);标签和版本;明确的规范和解释。对于包含 DBMS 和 DDL(包括约束和索引)和输入为格式化为表的代码的 SQL。 How to Ask 请显示您可以执行的部分/相关查询并与您的目标相关。在暂停该目标时询问错误。请不要要求我们编写您的代码。 PS 以下是如何描述 DB 表的特征(这样您就可以对它们进行推理和交流):当给出业务关系(船舶)/关联或表(基本或查询结果)时,说出其中的一行说明了什么就其列值而言的业务情况。 PS 关系查询不使用“条件连接”。 CROSS/INNER JOIN 首先给出每个表中输入行的所有组合。 ON/WHERE 保留一些。你要哪个? 我只是一个想帮助你的用户。我看不到您是如何检查我的 cmets 或其链接的。例如,我们无法从您的帖子中剪切和粘贴并运行您或我们的尝试。例如,您没有说出您认为您的查询做了什么以及这与您的目标有何关系;你只是转储错误的代码。 (另外,我用粗体表示了一些东西,并说它对帮助很重要,但你还没有采取行动。我问你想要哪几行交叉连接而你没有采取行动。这些不是强制性的,我是只是向您展示如何摆脱困境。)如果您不理解某些内容并想要,请发表评论。 当我试图根据您的评论尽可能首先改进问题时,我没有检查虚拟数据上的虚拟代码,因此代码中有错误。但我提到了我的查询做了什么,这应该给出这个想法。我不明白为什么我需要在粗体线中说与列值的业务关系!或者我只是不明白你的意思。无论如何..我得到了解决方案。感谢您的 cmets 改进我的问题。 我刚刚告诉过你,你不需要遵循粗体字(“不是强制性的”),但它是基本的并且会帮助你。 (因为它直接导致相应的 SQL 代码。Is there any rule of thumb to construct SQL query from a human-readable description?)“应该给出的想法”是一个不合理的信念。 PS 请当您提供任何输入表时,请以表格形式将它们初始化代码,可以剪切和粘贴,而不仅仅是人们阅读。祝你好运。 【参考方案1】:

以下是 BigQuery 标准 SQL

#standardSQL
SELECT Scenario, Product_1, Product_2, candidates.Level, candidates.Value 
FROM (
  SELECT Scenario, Product_1, Product_2, t2.Level, 
    ARRAY_AGG(
      STRUCT(IF(t2.Level = 'NA', 'NA', IFNULL(t1.Level, t2.Level)) AS Level, IF(Value IS NULL OR t2.Level = 'NA', 0, Value) AS Value)
      ORDER BY CASE t2.Level
        WHEN 'Low' THEN CASE t1.Level WHEN 'Low' THEN 1 WHEN 'Med' THEN 2 WHEN 'High' THEN 3 END
        WHEN 'Med' THEN CASE t1.Level WHEN 'Low' THEN 77 WHEN 'Med' THEN 1 WHEN 'High' THEN 2 END
        WHEN 'High' THEN CASE t1.Level WHEN 'Low' THEN 77 WHEN 'Med' THEN 66 WHEN 'High' THEN 1 END
        ELSE 0
      END      
    )[OFFSET(0)] candidates
  FROM `project.dataset.table2` t2
  LEFT JOIN `project.dataset.table1` t1
  USING(Product_1, Product_2)
  GROUP BY Scenario, Product_1, Product_2, Level  
)  

如果应用到您的问题中的示例数据,如下例所示

#standardSQL
WITH `project.dataset.table1` AS (
  SELECT 'C' Product_1, 'D' Product_2, 'High' Level, 10 Value UNION ALL
  SELECT 'A', 'B', 'Med', 11 UNION ALL
  SELECT 'A', 'B', 'High', 12 UNION ALL
  SELECT 'B', 'C', 'Med', 13 UNION ALL
  SELECT 'B', 'C', 'High', 9 
),`project.dataset.table2` AS (
  SELECT 1 Scenario, 'A' Product_1, 'B' Product_2, 'Low' Level UNION ALL
  SELECT 2, 'C', 'D', 'Low' UNION ALL
  SELECT 3, 'A', 'B', 'Med' UNION ALL
  SELECT 4, 'M', 'N', 'High' UNION ALL
  SELECT 5, 'A', 'B', 'NA' 
)
SELECT Scenario, Product_1, Product_2, candidates.Level, candidates.Value 
FROM (
  SELECT Scenario, Product_1, Product_2, t2.Level, 
    ARRAY_AGG(
      STRUCT(IF(t2.Level = 'NA', 'NA', IFNULL(t1.Level, t2.Level)) AS Level, IF(Value IS NULL OR t2.Level = 'NA', 0, Value) AS Value)
      ORDER BY CASE t2.Level
        WHEN 'Low' THEN CASE t1.Level WHEN 'Low' THEN 1 WHEN 'Med' THEN 2 WHEN 'High' THEN 3 END
        WHEN 'Med' THEN CASE t1.Level WHEN 'Low' THEN 77 WHEN 'Med' THEN 1 WHEN 'High' THEN 2 END
        WHEN 'High' THEN CASE t1.Level WHEN 'Low' THEN 77 WHEN 'Med' THEN 66 WHEN 'High' THEN 1 END
        ELSE 0
      END      
    )[OFFSET(0)] candidates
  FROM `project.dataset.table2` t2
  LEFT JOIN `project.dataset.table1` t1
  USING(Product_1, Product_2)
  GROUP BY Scenario, Product_1, Product_2, Level  
)

输出是

Row Scenario    Product_1   Product_2   Level   Value    
1   1           A           B           Med     11   
2   2           C           D           High    10   
3   3           A           B           Med     11   
4   4           M           N           High    0    
5   5           A           B           NA      0      

我认为,以上内容主要为您提供所需的内容,但可能需要一些调整,我希望您能够做到

【讨论】:

嗨 Mikhail Berlyant,感谢您的快速回复。我会尽快检查并回复您。【参考方案2】:

基本上我尝试通过使用临时表来解决上述问题。

1.(src) 我们正在为每个级别分配一个分数,这将有助于在没有相同级别的情况下获取 table1 中可用的最低级别。此外,我们正在使用完全外连接,这将有助于查找 table1 中缺少的产品 2. (final) 这个表格将给我们所有产品的产品以及它们在两张表格之间的级别匹配。 3. (min_score) 它将给出每组product_1和product_2的最低分数

with src as
(
 select t1.product1 as t1p1, t1.product_2 as t1p2, t2.product_1 as t2p1, t2.product_2 as t2p2, t1.level as t1_level, t2.level as t2_level,case when t1.level='Low' then 1 
  when t1.level='Med' then 2
  when t1.level='High' then 3
  end as level_score from table1 t1 full outer join table2 t2 on(t1.product_1=t2.product_1 and t1.product_2=t2.product_2)
),
final as
(select t1p1,t1p2,t2p1,t2p2,case when t1_level=t2_level then t1_level else
 '#NA#' as level from src join table1 on(product_1=t1p1 and product2=t1p2) where t1p1 is not null and t1p2 is not null
),
min_score as
(
select t1p1,t1p2,min(level_score) as level_score from src group by t1p1,t1p2 
)
SELECT t2p1,t2p2,t2_level,0 as value FROM src WHERE (t1p1 IS NULL AND t1p2 IS NULL) OR (t2_level='NA')
UNION ALL
SELECT t1p1,t1p2,t1_level,value from final f join table1 tab1 on ( t1p1=product_1 and t1p2=product2 and t1_level=level) where level !='#NA#'
UNION ALL
SELECT t1p1,t2p2, CASE WHEN level_score=1 then 'Low' WHEN level_score=2 THEN 'Med' ELSE 'High' END as final_level,value   from final f join table1 on(product_1=t1p1 and product2=t1p2) where f.level='#NA#' and table1.level= final_level
 ;

我使用了 UNION ALL,因为它将包括每一行(如果有的话,也会重复)。 3 个选择查询的输出将是-

    第一个选择查询将给出 value=0 的所有产品,即 table1 中缺少产品或 table2 中的 level=NA。(场景 4,场景 5) 第二个查询将提供所有产品及其价值,其中产品及其级别存在于两个表中(场景 3) 如果我们在两个表中都有产品但它们​​的级别不匹配,它将为我提供价值。为此,我找到了 table1 中 product_1 和 product_2 组合的最低级别,然后通过比较级别得到了值。

【讨论】:

您好 Prakhar,感谢您的快速回复。

以上是关于如何对 BigQuery 中的两个表进行条件连接?的主要内容,如果未能解决你的问题,请参考以下文章

bigquery 中的条件连接

如何对 BigQuery 表进行分片?

如何在 BigQuery 中使用连接修剪分区?

BigQuery - 使用子查询和 OR 语句连接多个条件

使用 bigquery 对数据存储键进行连接

如何对 BigQuery 中的重复字段进行分组