【问题标题】:JOIN only one row from second table and if no rows exist return null仅从第二个表中加入一行,如果不存在行,则返回 null
【发布时间】:2018-04-24 04:00:26
【问题描述】:

在这个查询中,我需要显示左表中的所有记录,并且只显示右表中结果为最高日期的记录。

当前查询:

SELECT  a.*, c.*
FROM users a 
INNER JOIN payments c
    ON a.id = c.user_ID
INNER JOIN
(
    SELECT user_ID, MAX(date) maxDate
    FROM payments
    GROUP BY user_ID
) b ON c.user_ID = b.user_ID AND
        c.date = b.maxDate
WHERE a.package = 1

这将返回连接有效的所有记录,但我需要显示所有用户,如果他们尚未付款,付款表中的字段应为空。

我可以使用联合来显示其他行:

SELECT  a.*, c.*
FROM users a 
INNER JOIN payments c
    ON a.id = c.user_ID
INNER JOIN
(
    SELECT user_ID, MAX(date) maxDate
    FROM payments
    GROUP BY user_ID
) b ON c.user_ID = b.user_ID AND
        c.date = b.maxDate
WHERE a.package = 1
union
SELECT  a.*, c.*
FROM users a 
--here I would need to join with payments table to get the columns from the payments table, 
but where the user doesn't have a payment yet
WHERE a.package = 1

使用联合的选项似乎不是一个好的解决方案,但这是我尝试过的。

【问题讨论】:

    标签: sql oracle join inner-join


    【解决方案1】:

    使用row_number() 的通用方法对于“最高日期”或“最近”或类似条件非常有用:

    SELECT
          *
    FROM users a
    LEFT OUTER JOIN (
          -- determine the row corresponding to "most recent"
          SELECT
                payments.*
              , ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY date DESC) is_recent
          FROM payments
    ) b ON a.ID = b.user_ID
          AND b.is_recent = 1
    

    (在 over 子句中反转 ORDER BY 也可以启用“最旧”)

    【讨论】:

    • 你能解释一下“最古老的”吗? +1
    • 查看over ( partition by ... order by ...) 上的内容在一个“分区”内,order by 控制哪一行被赋予值一。如果你颠倒顺序,你会颠倒得到一个的那一行。 IE。您可以找到“最早的”或反转它并找到“最古老的”。我建议您稍微使用一下,直到它变得有意义为止。
    【解决方案2】:

    我喜欢@Matthew McPeak 的回答,但 OUTER APPLY 是 12c 或更高版本,而且从历史上看,它并不是非常惯用的 Oracle。这是一个直接的 LEFT OUTER JOIN 版本:

    SELECT *
    FROM users a 
    LEFT OUTER JOIN
    (
        -- retrieve the list of payments for just those payments that are the maxdate per user
        SELECT payments.*
        FROM payments 
        JOIN (SELECT user_id, MAX(date) maxdate
                FROM payments
               GROUP BY user_id
              ) maxpayment_byuser
             ON maxpayment_byuser.maxdate = payments.date
                AND maxpayment_byuser.user_id = payments.user_id
    ) b ON a.ID = b.user_ID 
    

    如果性能是一个问题,您可能会发现以下性能更高,但为简单起见,您最终会得到一个额外的“maxdate”列。

    SELECT *
    FROM users a 
    LEFT OUTER JOIN
    (
        -- retrieve the list of payments for just those payments that are the maxdate per user
        SELECT *
        FROM (
               SELECT payments.*,
                      MAX(date) OVER (PARTITION BY user_id) maxdate
                 FROM payments
             ) max_payments
        WHERE date = maxdate
    ) b ON a.ID = b.user_ID 
    

    【讨论】:

    • 我有 12c,所以他的回答有效。但我喜欢你 +1 的方式
    【解决方案3】:

    因此,换句话说,您需要一个用户列表以及每个用户的最后一次付款。

    您可以使用OUTER APPLY 而不是INNER JOIN 来获取每个用户的最后一笔付款。性能可能会更好,并且对于没有付款的用户,它会按照您想要的方式工作。

    SELECT a.*, b.*
    FROM   users a
    OUTER APPLY ( SELECT * FROM payments c 
                  WHERE c.user_id = a.user_id 
                  ORDER BY c.date DESC 
                  FETCH FIRST ROW ONLY ) b
    WHERE a.package = 1;
    

    这是同一概念的通用版本,不需要您的表格(供其他读者使用)。它提供了一个数据库用户列表和每个用户最近修改的对象。您可以看到它正确地包含了没有对象的用户。

    SELECT a.*, b.*
    FROM  all_users a
    OUTER APPLY ( SELECT * FROM all_objects b 
                  WHERE b.owner = a.username 
                  ORDER BY b.last_ddl_time desc 
                  FETCH FIRST ROW ONLY ) b
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-02-05
      • 1970-01-01
      • 2012-10-09
      • 1970-01-01
      • 1970-01-01
      • 2021-07-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多