简体   繁体   中英

Selecting max record based on a condition in a different column

I have a table with something like this:

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

An open account is one with a null CloseDate. I want to retrieve the ProfileId of the most recently opened account (ie MAX(OpenDate) where CloseDate IS NULL ) , and I want the maximum ProfileValue for those open accounts. In the above example, this means I want to return a row with ProfileId 3 and ProfileValue 5. So ideally:

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

However, the trouble I am having is for the case when there is no open accounts, I want to return the most recently opened account (regardless of when it was closed) and whatever the max ProfileValue is, and I am not sure how to condition this.

For my example, the query I have so far looks like this:

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?

This will return

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

And I can then join this table back to the Profiles table to get that the ProfileId, although this isn't particularly efficient either.

How can I fix my HAVING clause to include the scenario when there are no open accounts? I tried to do something like

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

to try and make NULL CloseDate be considered the max value, but this returns too many rows. And is there a better way to return ProfileId in my query so that I don't have to join the resulting table to itself again?

Edit:

Adapting @Gordon Linoff's answer:

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

This will return:

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

So if I modify the subquery to return TOP 1 , it will provide the correct ProfileId, but I need to also get the MAX(ProfileValue) of the open accounts (which is 5), and in the case that there are no open accounts, simply return the max of the all the accounts. I'm trying various group by clauses, but they seem to shuffle the ProfileId order. My back up plan is to simply retrieve the max ProfileValue in a separate query, but this is inefficient. Any ideas on how I can fix this query?

This seems like a good situation to use lateral joins:

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;

Your query mentions other tables. I don't understand what they are being used for, because they are not part of the question.

I adapted this from Gordon Linoff's answer. I used his suggestion to get the most recently opened ProfileId, and I used my old idea to get the ProfileValue, and I joined the two tables together. This can't possibly be the best way to do this, but I've been testing it and it appears to work.

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM