从多个来源高效地更新 table

Efficiently updating a table from multiple sources

我正在努力改进 Oracle 中现有 ETL 层的一部分。

  1. 文件已加载到临时 table。
  2. 执行许多 MERGE 语句来解析代理键。
  3. 应用了其他一些业务逻辑(需要那些代理键)。
  4. 结果已合并 进入 table (同时具有代理键和业务逻辑 结果)

我想改进的是第 2 步,似乎不太理想分几步来做。

MERGE INTO temp t
USING dimension_1 d1 ON (d1.natural_key = t.d1_natural_key)
WHEN MATCHED THEN UPDATE t.d1_id = d1.id

MERGE INTO temp t
USING dimension_2 d2 ON (d2.natural_key = t.d2_natural_key)
WHEN MATCHED THEN UPDATE t.d2_id = d2.id

MERGE INTO temp t
USING dimension_3 d3 ON (d3.natural_key = t.d3_natural_key)
WHEN MATCHED THEN UPDATE t.d3_id = d3.id

如果我在 SQL 服务器上写这个,我会做如下的事情:

UPDATE
    t
SET
    d1_id = COALESCE(d1.id, -1),
    d2_id = COALESCE(d2.id, -1),
    d3_id = COALESCE(d3.id, -1)
FROM
    temp  t
LEFT JOIN
    dimension_1   d1
        ON d1.natural_key = t.d1_natural_key
LEFT JOIN
    dimension_2   d2
        ON d2.natural_key = t.d2_natural_key
LEFT JOIN
    dimension_3   d3
        ON d3.natural_key = t.d3_natural_key

我这辈子都找不到 Oracle 中看似合理的选项。我能解决的最好的办法是使用 UPDATE (而我周围的每个人都在尖叫我 'must' 使用 MERGE) 和相关的子查询;像...

UPDATE
    temp t
SET
    d1_id = COALESCE((SELECT id FROM dimension_1 d1 WHERE d1.natural_key = t.d1_natural_key), -1),
    d2_id = COALESCE((SELECT id FROM dimension_2 d2 WHERE d2.natural_key = t.d2_natural_key), -1),
    d3_id = COALESCE((SELECT id FROM dimension_3 d3 WHERE d3.natural_key = t.d3_natural_key), -1)

有没有更好的选择?还是相关子查询方法在 Oracle 中实际有效?

我认为您的 SQL 服务器更新相当于:

UPDATE
    temp t1
SET
    (d1_id, d2_id, d3_id) = (
SELECT
    COALESCE(d1.id, -1),
    COALESCE(d2.id, -1),
    COALESCE(d3.id, -1)
FROM
    temp  t2
LEFT JOIN
    dimension_1   d1
        ON d1.natural_key = t2.d1_natural_key
LEFT JOIN
    dimension_2   d2
        ON d2.natural_key = t2.d2_natural_key
LEFT JOIN
    dimension_3   d3
        ON d3.natural_key = t2.d3_natural_key
WHERE
    t2.id = t1.id 
)

还是相关更新;加入发生在子查询中,因为 Oracle 不允许您加入作为更新本身的一部分。通常您不需要(或不想)在子查询中再次引用目标外部 table,但是您需要针对此处 outer-join 的内容。

您还可以将 left-join 方法与合并结合起来,将本质上相同的子查询放入 using 子句中:

MERGE INTO temp t
USING (
  SELECT t.id,
    COALESCE(d1.id, -1) AS d1_id,
    COALESCE(d2.id, -1) AS d2_id,
    COALESCE(d3.id, -1) AS d3_id
  FROM
    temp  t
  LEFT JOIN
    dimension_1   d1
      ON d1.natural_key = t.d1_natural_key
  LEFT JOIN
    dimension_2   d2
      ON d2.natural_key = t.d2_natural_key
  LEFT JOIN
    dimension_3   d3
      ON d3.natural_key = t.d3_natural_key
) d
ON (d.id = t.id)
WHEN MATCHED THEN UPDATE SET
  t.d1_id = d.d1_id,
  t.d2_id = d.d2_id,
  t.d3_id = d.d3_id

虽然在这种情况下我没有看到使用合并而不是更新有任何真正的好处。

两者都将覆盖您的三个 ID 列中的所有现有值,但听起来您并不希望有任何值。

我相信这可能比亚历克斯的答案更有效——只需要一次访问 temp table,而不是两次。在我对一百万行的快速测试中,性能大致相同,但计划更好,因为没有第二次访问 temp table。在您的数据集上可能值得一试。

UPDATE
 ( SELECT      d1.id s_d1_id,
               d2.id s_d2_id,
               d3.id s_d3_id,
               mt.d1_id,
               mt.d2_id,
               mt.d3_id
        FROM   temp mt
               LEFT JOIN dimension_1 d1 ON d1.natural_key = mt.d1_natural_key
               LEFT JOIN dimension_2 d2 ON d2.natural_key = mt.d2_natural_key
               LEFT JOIN dimension_3 d3 ON d3.natural_key = mt.d3_natural_key )
SET d1_id =    COALESCE (s_d1_id, -1), d2_id = COALESCE (s_d2_id, -1), d3_id = COALESCE (s_d3_id, -1);

需要注意的是,每个维度 table 中的 natural_key 列需要 UNIQUE 约束。有了这些约束,Oracle 知道 temp 在您正在更新的视图中是 key-preserved,这就是使上述语法正常的原因。

另一个警告:我曾经遇到过 SELECT 视图中的行与 table 中的行顺序不同的情况。结果是性能 下降 ,因为更新必须多次重新访问每个块。 SELECT 视图中的 ORDER BY temp.rowid 可以解决这个问题。