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.