【问题标题】:SQL Full outer join or alternative solutionSQL 完全外连接或替代解决方案
【发布时间】:2013-05-10 16:45:23
【问题描述】:

我正在尝试使用full outer join 将多个表连接在一起,它接近正确的结果,但由于连接子句存在一些重复的行。我有几个表,其中包含 id、date、value 列。我正在寻找一个表,其中每个 id、日期对都有一行,其中包含每个表中的所有值。

Here is a SQLFiddle if you want to play with it.

这是我目前得到的:


SELECT
  COALESCE(T1.ID, T2.ID, T3.ID, t4.id) AS ID,
  COALESCE(T1.event_dt, T2.event_dt, T3.event_dt, t4.event_dt) AS DATE,
  T1.AMT1, T2.AMT2, T3.AMT3, t4.AMT4
FROM T1
FULL OUTER JOIN T2
ON
  T2.id = T1.id
  AND T2.event_dt = T1.event_dt
FULL OUTER JOIN T3
ON
  T3.id = T1.id
  AND T3.event_dt = T1.event_dt
FULL OUTER JOIN T4
ON
  T4.id = T1.id
  AND T4.event_dt = T1.event_dt
ORDER BY ID, DATE

这几乎可行,但是当例如 T4 有一个不在 T1 中的 ID,event_dt 对时,我会得到一些重复的行(正如预期的那样,因为这就是我要加入的)。例如,我会得到类似的东西:


1   April, 06 2012 00:00:00+0000    (null)  2   (null)  (null)
1   April, 06 2012 00:00:00+0000    (null)  (null)  (null)  4
1   April, 06 2012 00:00:00+0000    (null)  (null)  3   (null)

当我想要得到:

2012 年 4 月 6 日 00:00:00+0000(空)2 3 4

有没有办法将这些行展平/合并在一起,或者有没有更好的方法来解决这个问题?

【问题讨论】:

  • 分组的条件是什么?因为在您的示例中您只输入了一个值(2、3 或 4),但在您的小提琴中您有多个值,例如对于 AMT1,你得到 40 和 1。是 Date 字段吗?那么如果你得到相同的日期但有多个记录会发生什么?您是否考虑过规范化您的表格?
  • 对它们进行分组的条件是按 ID、event_dt 对。因此,对于每个 ID、事件日期,我都想要 T1、T2、T3、T4 中的所有值。我输入的值只是虚拟值。
  • (id,event_dt)是所有表的主键吗?
  • 是的,(id, event_dt) 是pk
  • 就“更好的方式”而言,拥有四个具有看似相同谓词的表会在 me 处发出危险信号。我不知道你的语义,但这很可疑。

标签: sql postgresql join


【解决方案1】:

我认为你加入-citeria 根本不是你真正想要的。这个应该可以解决问题:

SELECT
  COALESCE(T1.ID, T2.ID, T3.ID, t4.id) AS ID,
  COALESCE(T1.event_dt, T2.event_dt, T3.event_dt, t4.event_dt) AS DATE,
  T1.AMT1, T2.AMT2, T3.AMT3, t4.AMT4
FROM T1
FULL OUTER JOIN T2
ON
  T2.id = T1.id
  AND T2.event_dt = T1.event_dt
FULL OUTER JOIN T3
ON
  T3.id = coalesce(T1.id, T2.id)
  AND T3.event_dt = coalesce(T1.event_dt, T2.event_dt)
FULL OUTER JOIN T4
ON
  T4.id = coalesce(T1.id, T2.id, T3.id)
  AND T4.event_dt = coalesce(T1.event_dt, T2.event_dt, T3.event_dt)
ORDER BY ID, DATE

SQL-Fiddle here 为您提供 2012-04-06 所需的输出。

【讨论】:

    【解决方案2】:

    您始终可以在 amount 列周围使用聚合:

    SELECT
      COALESCE(T1.ID, T2.ID, T3.ID, t4.id) AS ID,
      COALESCE(T1.event_dt, T2.event_dt, T3.event_dt, t4.event_dt) AS DATE,
      max(coalesce(T1.AMT1, 0)) AMT1,  -- use coalesce to replace the null with zero
      max(coalesce(T2.AMT2, 0)) AMT2, 
      max(coalesce(T3.AMT3, 0)) AMT3, 
      max(coalesce(t4.AMT4, 0)) AMT4
    FROM T1
    FULL OUTER JOIN T2
      ON T2.id = T1.id
      AND T2.event_dt = T1.event_dt
    FULL OUTER JOIN T3
      ON T3.id = T1.id
      AND T3.event_dt = T1.event_dt
    FULL OUTER JOIN T4
      ON T4.id = T1.id
      AND T4.event_dt = T1.event_dt
    group by  COALESCE(T1.ID, T2.ID, T3.ID, t4.id), 
      COALESCE(T1.event_dt, T2.event_dt, T3.event_dt, t4.event_dt)
    ORDER BY ID, DATE;
    

    Demo

    【讨论】:

    • 如果在执行 MAX 之前不捕获 NULL,则不会分组。
    • @Declan_K 我添加了合并,但不需要看这个演示 -- sqlfiddle.com/#!12/7993e/14 -- 金额列没有分组,所以不会不正确
    • 你是对的。对此感到抱歉。 (这里的协议是什么,我可以删除我的愚蠢评论还是应该留在那里?)
    【解决方案3】:

    捕获 NULL,用零替换它们,然后在每列中找到 MAX 值。

    SELECT
      COALESCE(T1.ID, T2.ID, T3.ID, t4.id) AS ID,
      COALESCE(T1.event_dt, T2.event_dt, T3.event_dt, t4.event_dt) AS DATE,
      max( coalesce(T1.AMT1,0)) as amt1
    , max( coalesce(T2.AMT2,0)) as amt2
    , max( coalesce(T3.AMT3,0)) as amt3
    , max( coalesce(t4.AMT4,0)) as amt4
    FROM T1
    FULL OUTER JOIN T2
    ON
      T2.id = T1.id
      AND T2.event_dt = T1.event_dt
    FULL OUTER JOIN T3
    ON
      T3.id = T1.id
      AND T3.event_dt = T1.event_dt
    FULL OUTER JOIN T4
    ON
      T4.id = T1.id
      AND T4.event_dt = T1.event_dt
    group by   COALESCE(T1.ID, T2.ID, T3.ID, t4.id),
      COALESCE(T1.event_dt, T2.event_dt, T3.event_dt, t4.event_dt)
    ORDER BY ID, DATE
    

    这是Fiddle

    【讨论】:

      【解决方案4】:

      (假设 OP 想要一个完全对称的外部 4 连接)

      WITH four AS (
              SELECT id, event_dt FROM t1
              UNION
              SELECT id, event_dt FROM t2
              UNION
              SELECT id, event_dt FROM t3
              UNION
              SELECT id, event_dt FROM t4
              )
      SELECT f.id, f.event_dt
              , t1.amt1
              , t2.amt2
              , t3.amt3
              , t4.amt4
      FROM four f
      LEFT JOIN t1 ON t1.id = f.id AND t1.event_dt = f.event_dt
      LEFT JOIN t2 ON t2.id = f.id AND t2.event_dt = f.event_dt
      LEFT JOIN t3 ON t3.id = f.id AND t3.event_dt = f.event_dt
      LEFT JOIN t4 ON t4.id = f.id AND t4.event_dt = f.event_dt
      ORDER BY id, event_dt
              ;
      

      结果:

       id |  event_dt  | amt1 | amt2 | amt3 | amt4 
      ----+------------+------+------+------+------
        1 | 2012-04-01 |    1 |      |      |     
        1 | 2012-04-02 |    1 |      |    3 |     
        1 | 2012-04-03 |    1 |      |    3 |     
        1 | 2012-04-06 |      |    2 |    3 |    4
        1 | 2012-04-07 |      |    2 |      |     
        2 | 2012-04-01 |   40 |      |      |     
        2 | 2012-04-02 |      |      |    3 |     
        2 | 2012-04-03 |      |      |    3 |     
        2 | 2012-04-04 |   40 |      |      |     
      (9 rows)
      

      顺便说一句:在UNION 四之后,LEFT JOINs 将与此处的FULL JOINs 执行相同的操作(联合四已经拥有所有可能的 {id, event_dt} 对)

      【讨论】:

      • 所有这些都成功了,我选择了这个,因为它节省了我一些打字时间。
      • 这个解决方案看起来很漂亮,但是如果您关心性能,您应该使用完全外连接(就像@A.H. 提供的解决方案)对于我的数据,这个 WITH/UNION 查询需要 1070 毫秒......全外连接我在 87 毫秒内达到了相同的结果。
      • 如果性能是问题,请参阅上面@Mike Sherrill catcall 的评论:数据模型无论如何都已损坏。您无法通过“聪明”的查询来修复错误的数据模型。先修复模型。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2023-01-24
      • 2016-09-06
      • 1970-01-01
      • 1970-01-01
      • 2010-09-07
      • 2019-11-23
      • 1970-01-01
      相关资源
      最近更新 更多