根据不同列中的条件选择最大记录

时间:2018-10-25 16:20:46

标签: sql sql-server

我有一张桌子,上面是这样的:

User | ProfileId |      OpenDate       |      CloseDate      | ProfileValue
----------------------------------------------------------------------------
test |     1     | 2018-10-25 11:40:00 | 2018-10-25 11:40:00 |     10 
test |     3     | 2018-10-25 11:40:00 |        NULL         |     3 
test |     4     | 2018-10-25 11:45:00 | 2018-10-25 11:40:00 |     4 
test |     7     | 2018-10-18 10:00:00 |        NULL         |     5

一个打开的帐户是一个关闭日期为空的帐户。我想要检索最近打开的帐户(即MAX(OpenDate),其中CloseDate IS NULL)的ProfileId,并且我想要这些已打开帐户的最大ProfileValue。在上面的示例中,这意味着我想返回包含ProfileId 3和ProfileValue 5的行。因此理想情况下:

User | ProfileId | ProfileValue
--------------------------------
test |     3     |     5 

但是,我遇到的麻烦是没有打开的帐户时,我想返回最近打开的帐户(无论关闭时间如何)以及最大ProfileValue是多少,并且我不确定该如何处理。

对于我的示例,到目前为止,我所拥有的查询如下所示:

SELECT U.User,
       MAX(P.OpenDate) AS OpenDate,
       CASE WHEN MAX(CASE WHEN P.CloseDate IS NULL THEN 1 ELSE 0 END) = 0
        THEN MAX(P.CloseDate) END AS CloseDate, -- use null CloseDate field if available
       MAX(R.ProfileValue) AS ProfileValue
FROM #UserIds U
LEFT JOIN dbo.Profiles P
    ON U.User = P.User
INNER JOIN [dbo].[ReferenceTable] R
    ON P.ProfileId = R.ProfileId
GROUP BY U.User, P.CloseDate
HAVING P.CloseDate IS NULL -- TO DO?

这将返回

User |      OpenDate       |      CloseDate      | ProfileValue
----------------------------------------------------------------
test | 2018-10-25 11:40:00 |        NULL         |     5

然后我可以将此表重新连接到Profiles表以获取ProfileId,尽管这也不是特别有效。

在没有未结帐户的情况下,如何解决我的HAVING子句以包括方案?我试图做类似

HAVING ISNULL(P.CloseDate, '2079-06-06 23:59:00') = MAX(COALESCE(P.CloseDate, '2079-06-06 23:59:00'))

尝试将NULL的CloseDate视为最大值,但这将返回太多行。还有没有更好的方法可以在查询中返回ProfileId,这样我就不必再次将结果表联接到自身了?

编辑:

适应@Gordon Linoff的答案:

SELECT A.User, A.ProfileId, A.OpenDate, A.CloseDate, A.ProfileValue
FROM #UserIds U OUTER APPLY
     (SELECT TOP 5 P.User, P.ProfileId, P.OpenDate, P.CloseDate, R.ProfileValue
      FROM dbo.Profiles P
      INNER JOIN [dbo].[ReferenceTable] R
        ON P.ProfileId = R.ProfileId
      WHERE U.User = P.User
      ORDER BY (CASE WHEN p.CloseDate IS NULL THEN 1 ELSE 2 END),  -- put open accounts first
               P.OpenDate DESC  -- put most recent opened first
    ) A

这将返回:

User | ProfileId |      OpenDate       |      CloseDate      | ProfileValue
----------------------------------------------------------------------------
test |     3     | 2018-10-25 11:40:00 |        NULL         |     3
test |     7     | 2018-10-18 10:00:00 |        NULL         |     5
test |     4     | 2018-10-25 11:45:00 | 2018-10-25 11:40:00 |     4 
test |     1     | 2018-10-25 11:40:00 | 2018-10-25 11:40:00 |     10

因此,如果我修改子查询以返回TOP 1,它将提供正确的ProfileId,但是在这种情况下,我还需要获取未结帐户的MAX(ProfileValue)(为5)。没有开设的帐户,只需返回所有帐户的最大值即可。我正在尝试各种group by子句,但它们似乎会乱序ProfileId顺序。我的备份计划是仅在单独的查询中检索最大ProfileValue,但这效率低下。关于如何解决此查询的任何想法?

2 个答案:

答案 0 :(得分:1)

使用横向联接似乎是一个好情况:

SELECT p.*
FROM #UserIds U OUTER APPLY
     (SELECT p.*
      FROM dbo.Profiles P
      WHERE U.User = P.User
      ORDER BY (CASE WHEN p.CloseDate IS NULL THEN 1 ELSE 2 END),  -- put open accounts first
               o.OpenDate DESC  -- put most recent opened first
    ) p;

您的查询提到其他表。我不明白它们的用途,因为它们不是问题的一部分。

答案 1 :(得分:0)

我改编自戈登·利诺夫(Gordon Linoff)的回答。我使用他的建议来获取最近打开的ProfileId,并使用我的旧想法来获取ProfileValue,然后将两个表连接在一起。这可能不是执行此操作的最佳方法,但是我已经对其进行了测试,并且似乎可以正常工作。

SELECT D.User, D.ProfileId, D.ProfileValue
FROM
(
SELECT C.*, CASE WHEN (ROW_NUMBER() OVER
(PARTITION BY User ORDER BY User))=1 THEN 1 ELSE 0 END FirstTime -- We only want the first time the User appears in the result set
FROM
    (
    SELECT A.User, A.ProfileId, B.ProfileValue
    FROM #Users U OUTER APPLY
        (SELECT TOP 1 P.User, P.ProfileId, P.OpenDate, P.CloseDate, L.ProfileValue
              FROM [dbo].[Profiles] P
              INNER JOIN [dbo].[ReferenceTable] L
                ON P.ProfileId = L.ProfileId
              WHERE U.User = P.User
              ORDER BY (CASE WHEN p.CloseDate IS NULL THEN 1 ELSE 2 END),  -- put open accounts first
                       P.OpenDate DESC  -- put most recent opened first
        ) A
    LEFT OUTER JOIN
        (SELECT U.User,
                   MAX(P.OpenDate) AS OpenDate,
                   CASE WHEN MAX(CASE WHEN P.CloseDate IS NULL THEN 1 ELSE 0 END) = 0
                    THEN MAX(P.CloseDate) END AS CloseDate, -- use null CloseDate field if available
                   MAX(L.ProfileValue) AS ProfileValue
        FROM #Users U
            LEFT JOIN [dbo].[Profiles] P
                ON U.User = P.User
            INNER JOIN [dbo].[ReferenceTable] L
                ON P.ProfileId = L.ProfileId
            GROUP BY U.User, P.CloseDate
        ) B
    ON A.User = B.User
    AND A.OpenDate = B.OpenDate
    ) C
) D
WHERE D.FirstTime = 1