【问题标题】:Hive / SQL - Left join with fallbackHive / SQL - 带回退的左连接
【发布时间】:2017-02-04 07:13:25
【问题描述】:

在 Apache Hive 中,我必须对表进行左连接,以保留左侧数据中的所有数据,并尽可能从右侧表中添加数据。 为此,我使用了两个连接,因为连接基于两个字段(material_id 和 location_id)。 这适用于两个传统的左连接:

SELECT 
   a.*, 
   b.*
FROM a
INNER JOIN (some more complex select) b
   ON a.material_id=b.material_id 
   AND a.location_id=b.location_id;

对于 location_id,数据库只包含两个不同的值,比如 1 和 2。

我们现在有一个要求,如果没有“完美匹配”,这意味着只有material_id可以被join,并且没有material_id和location_id的正确组合(例如material_id=100和location_id=1)用于join对于 b 表中的 location_id,连接应该“默认”或“回退”到 location_id 的其他可能值,例如material_id=001 和 location_id=2,反之亦然。这应该只适用于 location_id。

我们已经用 CASE 等研究了所有可能的答案,但没有占上风。像

这样的设置
...
ON a.material_id=b.material_id AND a.location_id=
CASE WHEN a.location_id = b.location_id THEN b.location_id ELSE ...;

我们尝试过或没有弄清楚如何在 hive 查询语言中真正做到这一点。

感谢您的帮助!也许有人有一个聪明的主意。

这是一些示例数据:

Table a
| material_id | location_id | other_column_a |
| 100         | 1           | 45            |
| 101         | 1           | 45            |
| 103         | 1           | 45            |
| 103         | 2           | 45            |



Table b
| material_id | location_id | other_column_b |
| 100         | 1           | 66            |
| 102         | 1           | 76            |
| 103         | 2           | 88            |


Left - Join Table
| material_id | location_id | other_column_a | other_column_b
| 100         | 1           | 45            | 66
| 101         | 1           | 45            | NULL (mat. not in b)
| 103         | 1           | 45            | DEFAULT TO where location_id=2 (88)
| 103         | 2           | 45            | 88

PS:如上所述here 存在等在子查询ON 中不起作用。

【问题讨论】:

    标签: sql hadoop join hive apache-hive


    【解决方案1】:

    解决方案是在没有a.location_id = b.location_id 的情况下进行左连接,并按优先顺序对所有行进行编号。然后按row_number 过滤。在下面的代码中,join 将首先复制行,因为所有匹配的 material_id 都将被连接,然后row_number() 函数将 1 分配给 a.location_id = b.location_id 的行和 2 分配给 a.location_id <> b.location_id 的行(如果存在)以及 a.location_id = b.location_id 和 1 的行如果不存在这样的。 b.location_id 添加到 row_number() 函数中的 order by 中,因此如果没有精确匹配,它将“优先”具有较低 b.location_id 的行。我希望你已经抓住了这个想法。

    select * from 
    (
    SELECT 
       a.*, 
       b.*,
       row_number() over(partition by material_id 
                         order by CASE WHEN a.location_id = b.location_id THEN 1 ELSE 2 END, b.location_id ) as rn
    FROM a
    LEFT JOIN (some more complex select) b
       ON a.material_id=b.material_id 
    )s 
    where rn=1
    ;
    

    【讨论】:

    • 您指定的方法确实有效,这就是为什么我将其标记为有用。但是,我们的要求现在已经改变,位置不被硬编码。我将发布我们当前的解决方案。
    • 您能解释一下这段代码中硬编码的内容吗? Location_id 值不是硬编码的,可以是任意的
    • 据我了解,THEN 1 ELSE 2 是硬编码的,这将如何与更多位置一起使用?但是是的,您的答案是我最初问题的答案,并且工作正常。非常感谢!
    • 然后我误解了这部分解决方案。在我们的案例中,它工作得非常好,我们将评估这两种方法的性能。再次感谢您!
    【解决方案2】:

    也许这对将来的某人有帮助:

    我们还想出了一种不同的方法。

    首先,我们创建另一个表,根据 material_id 计算表 b 中所有 (!) 位置的平均值。

    其次,在连接表中我们创建三列: c1 - material_id 和 location_id 匹配的值(表 a 与表 b 的左连接结果)。如果没有完美匹配,则此列为空。

    c2 - 表中的值,我们在其中写入该 material_id 的平均值(备用)表中的数字(无论位置如何)

    c3 - “实际值”列,我们使用 case 语句来决定第 1 列是否为 NULL(材料和位置不完全匹配),然后我们使用第 2 列的值(所有数据的平均值)材料的其他位置)以供进一步计算。

    【讨论】:

    • THEN 1 ELSE 2 - 这些不是位置 ID。这只是 ROW_NUMBER() 中 ORDER 的值来标记行。标有 1 的行会先排序,标有 2 的行会排在第二位,最后 filter 会过滤 rn=1。没有任何硬编码的列值。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多