Conditional Row_Number() for min and maximum date










0















I ve got a table with data which looks like this:



Table T1



+----+------------+------------+

| ID | Udate | last_code |
+----+------------+------------+
| 1 | 05/11/2018 | ATTEMPT |
| 1 | 03/11/2018 | ATTEMPT |
| 1 | 01/11/2017 | INFO |
| 1 | 25/10/2016 | ARRIVED |
| 1 | 22/9/2016 | ARRIVED |
| 1 | 14/9/2016 | SENT |
| 1 | 1/9/2016 | SENT |
+----+------------+------------+
| 2 | 26/10/2016 | RECEIVED |
| 2 | 19/10/2016 | ARRIVED |
| 2 | 18/10/2016 | ARRIVED |
| 2 | 14/10/2016 | ANNOUNCED |
| 2 | 23/9/2016 | INFO |
| 2 | 14/9/2016 | DAMAGE |
| 2 | 2/9/2016 | SCHEDULED |
+----+------------+------------+


Each id has multiple codes at different dates and there is no pattern for them.



Overall I m trying to get the last date and code, but if there is an "ATTEMPT" code, I need to get the first date and that code for each individual ID. Based on the table above, I would get:



+----+------------+------------+

| ID | Udate | last_code |
| 1 | 03/11/2018 | ATTEMPT |
| 2 | 26/10/2016 | RECEIVED |
+----+------------+------------+


I ve been trying



ROW_NUMBER() OVER (PARTITION BY ID
ORDER BY
(CASE WHEN code = 'ATTEMPT' THEN u_date END) ASC,
(CASE WHEN code_key <> 'ATTEMPT' THEN u_date END) DESC
) as RN


And at the moment I ve been stuck after I use ROW_NUMBER() twice, but can t think of a way to bring them all in the same table.



,ROW_NUMBER() OVER (PARTITION BY id, code order by udate asc) as RN1
,ROW_NUMBER() OVER (PARTITION BY id order by udate desc) AS RN2


I m not very familiar with CTEs and I think it s one of those queries which requires one perhaps..



Thanks.










share|improve this question






















  • I'm wasn't sure where ROW_NUMBER() is coming into play with what you are attempting. If needed and you clarify I can tweak my answer below if that's not getting you what you need.

    – Tim Mylott
    Nov 14 '18 at 22:27











  • I was using ROW_NUMBER () becuase at first I didn t realize there might be multiple 'ATTEMPT' codes. And it was way quicker than inner join for max and min date.

    – madlicksxxx
    Nov 15 '18 at 9:01















0















I ve got a table with data which looks like this:



Table T1



+----+------------+------------+

| ID | Udate | last_code |
+----+------------+------------+
| 1 | 05/11/2018 | ATTEMPT |
| 1 | 03/11/2018 | ATTEMPT |
| 1 | 01/11/2017 | INFO |
| 1 | 25/10/2016 | ARRIVED |
| 1 | 22/9/2016 | ARRIVED |
| 1 | 14/9/2016 | SENT |
| 1 | 1/9/2016 | SENT |
+----+------------+------------+
| 2 | 26/10/2016 | RECEIVED |
| 2 | 19/10/2016 | ARRIVED |
| 2 | 18/10/2016 | ARRIVED |
| 2 | 14/10/2016 | ANNOUNCED |
| 2 | 23/9/2016 | INFO |
| 2 | 14/9/2016 | DAMAGE |
| 2 | 2/9/2016 | SCHEDULED |
+----+------------+------------+


Each id has multiple codes at different dates and there is no pattern for them.



Overall I m trying to get the last date and code, but if there is an "ATTEMPT" code, I need to get the first date and that code for each individual ID. Based on the table above, I would get:



+----+------------+------------+

| ID | Udate | last_code |
| 1 | 03/11/2018 | ATTEMPT |
| 2 | 26/10/2016 | RECEIVED |
+----+------------+------------+


I ve been trying



ROW_NUMBER() OVER (PARTITION BY ID
ORDER BY
(CASE WHEN code = 'ATTEMPT' THEN u_date END) ASC,
(CASE WHEN code_key <> 'ATTEMPT' THEN u_date END) DESC
) as RN


And at the moment I ve been stuck after I use ROW_NUMBER() twice, but can t think of a way to bring them all in the same table.



,ROW_NUMBER() OVER (PARTITION BY id, code order by udate asc) as RN1
,ROW_NUMBER() OVER (PARTITION BY id order by udate desc) AS RN2


I m not very familiar with CTEs and I think it s one of those queries which requires one perhaps..



Thanks.










share|improve this question






















  • I'm wasn't sure where ROW_NUMBER() is coming into play with what you are attempting. If needed and you clarify I can tweak my answer below if that's not getting you what you need.

    – Tim Mylott
    Nov 14 '18 at 22:27











  • I was using ROW_NUMBER () becuase at first I didn t realize there might be multiple 'ATTEMPT' codes. And it was way quicker than inner join for max and min date.

    – madlicksxxx
    Nov 15 '18 at 9:01













0












0








0








I ve got a table with data which looks like this:



Table T1



+----+------------+------------+

| ID | Udate | last_code |
+----+------------+------------+
| 1 | 05/11/2018 | ATTEMPT |
| 1 | 03/11/2018 | ATTEMPT |
| 1 | 01/11/2017 | INFO |
| 1 | 25/10/2016 | ARRIVED |
| 1 | 22/9/2016 | ARRIVED |
| 1 | 14/9/2016 | SENT |
| 1 | 1/9/2016 | SENT |
+----+------------+------------+
| 2 | 26/10/2016 | RECEIVED |
| 2 | 19/10/2016 | ARRIVED |
| 2 | 18/10/2016 | ARRIVED |
| 2 | 14/10/2016 | ANNOUNCED |
| 2 | 23/9/2016 | INFO |
| 2 | 14/9/2016 | DAMAGE |
| 2 | 2/9/2016 | SCHEDULED |
+----+------------+------------+


Each id has multiple codes at different dates and there is no pattern for them.



Overall I m trying to get the last date and code, but if there is an "ATTEMPT" code, I need to get the first date and that code for each individual ID. Based on the table above, I would get:



+----+------------+------------+

| ID | Udate | last_code |
| 1 | 03/11/2018 | ATTEMPT |
| 2 | 26/10/2016 | RECEIVED |
+----+------------+------------+


I ve been trying



ROW_NUMBER() OVER (PARTITION BY ID
ORDER BY
(CASE WHEN code = 'ATTEMPT' THEN u_date END) ASC,
(CASE WHEN code_key <> 'ATTEMPT' THEN u_date END) DESC
) as RN


And at the moment I ve been stuck after I use ROW_NUMBER() twice, but can t think of a way to bring them all in the same table.



,ROW_NUMBER() OVER (PARTITION BY id, code order by udate asc) as RN1
,ROW_NUMBER() OVER (PARTITION BY id order by udate desc) AS RN2


I m not very familiar with CTEs and I think it s one of those queries which requires one perhaps..



Thanks.










share|improve this question














I ve got a table with data which looks like this:



Table T1



+----+------------+------------+

| ID | Udate | last_code |
+----+------------+------------+
| 1 | 05/11/2018 | ATTEMPT |
| 1 | 03/11/2018 | ATTEMPT |
| 1 | 01/11/2017 | INFO |
| 1 | 25/10/2016 | ARRIVED |
| 1 | 22/9/2016 | ARRIVED |
| 1 | 14/9/2016 | SENT |
| 1 | 1/9/2016 | SENT |
+----+------------+------------+
| 2 | 26/10/2016 | RECEIVED |
| 2 | 19/10/2016 | ARRIVED |
| 2 | 18/10/2016 | ARRIVED |
| 2 | 14/10/2016 | ANNOUNCED |
| 2 | 23/9/2016 | INFO |
| 2 | 14/9/2016 | DAMAGE |
| 2 | 2/9/2016 | SCHEDULED |
+----+------------+------------+


Each id has multiple codes at different dates and there is no pattern for them.



Overall I m trying to get the last date and code, but if there is an "ATTEMPT" code, I need to get the first date and that code for each individual ID. Based on the table above, I would get:



+----+------------+------------+

| ID | Udate | last_code |
| 1 | 03/11/2018 | ATTEMPT |
| 2 | 26/10/2016 | RECEIVED |
+----+------------+------------+


I ve been trying



ROW_NUMBER() OVER (PARTITION BY ID
ORDER BY
(CASE WHEN code = 'ATTEMPT' THEN u_date END) ASC,
(CASE WHEN code_key <> 'ATTEMPT' THEN u_date END) DESC
) as RN


And at the moment I ve been stuck after I use ROW_NUMBER() twice, but can t think of a way to bring them all in the same table.



,ROW_NUMBER() OVER (PARTITION BY id, code order by udate asc) as RN1
,ROW_NUMBER() OVER (PARTITION BY id order by udate desc) AS RN2


I m not very familiar with CTEs and I think it s one of those queries which requires one perhaps..



Thanks.







tsql date conditional common-table-expression row-number






share|improve this question













share|improve this question











share|improve this question




share|improve this question










asked Nov 14 '18 at 21:29









madlicksxxxmadlicksxxx

196




196












  • I'm wasn't sure where ROW_NUMBER() is coming into play with what you are attempting. If needed and you clarify I can tweak my answer below if that's not getting you what you need.

    – Tim Mylott
    Nov 14 '18 at 22:27











  • I was using ROW_NUMBER () becuase at first I didn t realize there might be multiple 'ATTEMPT' codes. And it was way quicker than inner join for max and min date.

    – madlicksxxx
    Nov 15 '18 at 9:01

















  • I'm wasn't sure where ROW_NUMBER() is coming into play with what you are attempting. If needed and you clarify I can tweak my answer below if that's not getting you what you need.

    – Tim Mylott
    Nov 14 '18 at 22:27











  • I was using ROW_NUMBER () becuase at first I didn t realize there might be multiple 'ATTEMPT' codes. And it was way quicker than inner join for max and min date.

    – madlicksxxx
    Nov 15 '18 at 9:01
















I'm wasn't sure where ROW_NUMBER() is coming into play with what you are attempting. If needed and you clarify I can tweak my answer below if that's not getting you what you need.

– Tim Mylott
Nov 14 '18 at 22:27





I'm wasn't sure where ROW_NUMBER() is coming into play with what you are attempting. If needed and you clarify I can tweak my answer below if that's not getting you what you need.

– Tim Mylott
Nov 14 '18 at 22:27













I was using ROW_NUMBER () becuase at first I didn t realize there might be multiple 'ATTEMPT' codes. And it was way quicker than inner join for max and min date.

– madlicksxxx
Nov 15 '18 at 9:01





I was using ROW_NUMBER () becuase at first I didn t realize there might be multiple 'ATTEMPT' codes. And it was way quicker than inner join for max and min date.

– madlicksxxx
Nov 15 '18 at 9:01












1 Answer
1






active

oldest

votes


















1














I think you have a couple of options before attempting a CTE.



Give these a try, examples below:



DECLARE @TestData TABLE
(
[ID] INT
, [Udate] DATE
, [last_code] NVARCHAR(100)
);

INSERT INTO @TestData (
[ID]
, [Udate]
, [last_code]
)
VALUES ( 1, '11/05/2018', 'ATTEMPT ' )
, ( 1, '11/03/2018', 'ATTEMPT' )
, ( 1, '11/01/2017', 'INFO' )
, ( 1, '10/25/2016', 'ARRIVED' )
, ( 1, '9/22/2016 ', 'ARRIVED' )
, ( 1, '9/14/2016 ', 'SENT' )
, ( 1, '9/1/2016 ', 'SENT' )
, ( 2, '10/26/2016', 'RECEIVED' )
, ( 2, '10/19/2016', 'ARRIVED' )
, ( 2, '10/18/2016', 'ARRIVED' )
, ( 2, '10/14/2016', 'ANNOUNCED' )
, ( 2, '9/23/2016 ', 'INFO' )
, ( 2, '9/14/2016 ', 'DAMAGE' )
, ( 2, '9/2/2016 ', 'SCHEDULED' );

--option 1
--couple of outer apply
--1 - to get the min date for attempt
--2 - to get the max date regardless of the the code
--where clause, using coalesce will pick what date. Use the date if I have one for code ='ATTEMPT', if not use the max date.
SELECT [a].*
FROM @TestData [a]
OUTER APPLY (
SELECT [b].[ID]
, MIN([b].[Udate]) AS [AttemptUdate]
FROM @TestData [b]
WHERE [b].[ID] = [a].[ID]
AND [b].[last_code] = 'ATTEMPT'
GROUP BY [b].[ID]
) AS [aa]
OUTER APPLY (
SELECT [c].[ID]
, MAX([c].[Udate]) AS [MaxUdate]
FROM @TestData [c]
WHERE [c].[ID] = [a].[ID]
GROUP BY [c].[ID]
) AS [cc]
WHERE [a].[ID] = COALESCE([aa].[ID], [cc].[ID])
AND [a].[Udate] = COALESCE([aa].[AttemptUdate], [cc].[MaxUdate]);


--use window functions
--Similiar in that we are finding the max Udate and also min Udate when last_code='ATTEMPT'
--Then using COALESCE in the where clause to evaluate which one to use.
--Maybe a little cleaner
SELECT [td].[ID]
, [td].[Udate]
, [td].[last_code]
FROM (
SELECT [ID]
, [last_code]
, [Udate]
, MAX([Udate]) OVER ( PARTITION BY [ID] ) AS [MaxUdate]
, MIN( CASE WHEN [last_code] = 'ATTEMPT' THEN [Udate]
ELSE NULL
END
) OVER ( PARTITION BY [ID] ) AS [AttemptUdate]
FROM @TestData
) AS [td]
WHERE [td].[Udate] = COALESCE([td].[AttemptUdate], [td].[MaxUdate]);


To explain how I got there a little bit, it was primarily base on your requirement:




Overall I m trying to get the last date and code, but if there is an
"ATTEMPT" code, I need to get the first date and that code for each
individual ID.




So for each ID I needed a way to get:



  • Minimum Udate for last_code = 'ATTEMPT' per ID - if there was no ATTEMPT we'll get a null

  • Maximum Udate for all records per ID

If I could determine the above for each record based on ID then my final result set are basically those where the Udate equals my Maximum Udate if the Minimum was null. If the Minimum wasn't null use that instead.



The first option, using 2 outer applies is doing each of the points above.



Minimum Udate for last_code = 'ATTEMPT' per ID - if there was no ATTEMPT we'll get a null:



OUTER APPLY (
SELECT [b].[ID]
, MIN([b].[Udate]) AS [AttemptUdate]
FROM @TestData [b]
WHERE [b].[ID] = [a].[ID]
AND [b].[last_code] = 'ATTEMPT'
GROUP BY [b].[ID]
) AS [aa]


Outer Apply as I might not have an ATTEMPT record for a given ID, so in those situations it returns NULL.



Maximum Udate for all records per ID:



OUTER APPLY (
SELECT [c].[ID]
, MAX([c].[Udate]) AS [MaxUdate]
FROM @TestData [c]
WHERE [c].[ID] = [a].[ID]
GROUP BY [c].[ID]
) AS [cc]


Then the where clause compares what was returned by those to return only the records I want:



 [a].[Udate] = COALESCE([aa].[AttemptUdate], [cc].[MaxUdate]);


I'm using COALESCE to handled and evaluate NULLs. COALESCE will evaluate the fields from left to right and use/return the first non NULL value.



So using this with Udate we can evaluate which Udate value I should use in my filter to satisfy the requirement.



Because if I had an ATTEMPT record field AttemptUdate would have a value and be used in the filter first. If I didn't have an ATTEMPT record AttemptUdate would be NULL so then MaxUdate would be used.



For option 2, similar just going after it a little different.



Minimum Udate for last_code = 'ATTEMPT' per ID - if there was no ATTEMPT we'll get a null:



 MIN( CASE WHEN [last_code] = 'ATTEMPT' THEN [Udate]
ELSE NULL
END
) OVER ( PARTITION BY [ID] ) AS [AttemptUdate]


Min on Udate, but I use a case statement to evaluate if that records is an ATTEMPT or not. using OVER PARTITION will do that based on how I tell it to partition the data, by ID.



Maximum Udate for all records per ID:



MAX([Udate]) OVER ( PARTITION BY [ID] ) AS [MaxUdate]


Go get me the maximum Udate based on ID, since that's how I told it to partition it.



I do all that in a sub-query to make the where clause easier to work with. Then it's the same as before when filtering:



[td].[Udate] = COALESCE([td].[AttemptUdate], [td].[MaxUdate]);


Using COALESCE to determine which date I should be using and only return the records I want.



With the second option, go a little deeper, If you run just the sub query, you'll see you get for each individual record the 2 main driving points of the requirement:



  • What's the Max Udate per ID

  • What's the mint Udate of last_code=ATTEMPT per ID

enter image description here



From there I can just filter on those records satisfying what I was originally looking for, using a COALESCE to simplify my filter.



[td].[Udate] = COALESCE([td].[AttemptUdate], [td].[MaxUdate]);


Use AttemptUdate unless it's NULL then use MaxUdate.






share|improve this answer

























  • It works great !!! I ve got some duplicates, but it s my data, not your query. Thanks +1.

    – madlicksxxx
    Nov 15 '18 at 9:18











  • Also, I would be grateful if you could briefly explain the thought process for this, I wanna see what I was missing. Thanks.

    – madlicksxxx
    Nov 15 '18 at 9:19











  • @madlicksxxx you bet! I updated the answer and added my thought process and how I worked through it. Hope it helps.

    – Tim Mylott
    Nov 15 '18 at 21:48











  • Legend, thank you!

    – madlicksxxx
    Nov 16 '18 at 8:08










Your Answer






StackExchange.ifUsing("editor", function ()
StackExchange.using("externalEditor", function ()
StackExchange.using("snippets", function ()
StackExchange.snippets.init();
);
);
, "code-snippets");

StackExchange.ready(function()
var channelOptions =
tags: "".split(" "),
id: "1"
;
initTagRenderer("".split(" "), "".split(" "), channelOptions);

StackExchange.using("externalEditor", function()
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled)
StackExchange.using("snippets", function()
createEditor();
);

else
createEditor();

);

function createEditor()
StackExchange.prepareEditor(
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: true,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: 10,
bindNavPrevention: true,
postfix: "",
imageUploader:
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
,
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
);



);













draft saved

draft discarded


















StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53309007%2fconditional-row-number-for-min-and-maximum-date%23new-answer', 'question_page');

);

Post as a guest















Required, but never shown

























1 Answer
1






active

oldest

votes








1 Answer
1






active

oldest

votes









active

oldest

votes






active

oldest

votes









1














I think you have a couple of options before attempting a CTE.



Give these a try, examples below:



DECLARE @TestData TABLE
(
[ID] INT
, [Udate] DATE
, [last_code] NVARCHAR(100)
);

INSERT INTO @TestData (
[ID]
, [Udate]
, [last_code]
)
VALUES ( 1, '11/05/2018', 'ATTEMPT ' )
, ( 1, '11/03/2018', 'ATTEMPT' )
, ( 1, '11/01/2017', 'INFO' )
, ( 1, '10/25/2016', 'ARRIVED' )
, ( 1, '9/22/2016 ', 'ARRIVED' )
, ( 1, '9/14/2016 ', 'SENT' )
, ( 1, '9/1/2016 ', 'SENT' )
, ( 2, '10/26/2016', 'RECEIVED' )
, ( 2, '10/19/2016', 'ARRIVED' )
, ( 2, '10/18/2016', 'ARRIVED' )
, ( 2, '10/14/2016', 'ANNOUNCED' )
, ( 2, '9/23/2016 ', 'INFO' )
, ( 2, '9/14/2016 ', 'DAMAGE' )
, ( 2, '9/2/2016 ', 'SCHEDULED' );

--option 1
--couple of outer apply
--1 - to get the min date for attempt
--2 - to get the max date regardless of the the code
--where clause, using coalesce will pick what date. Use the date if I have one for code ='ATTEMPT', if not use the max date.
SELECT [a].*
FROM @TestData [a]
OUTER APPLY (
SELECT [b].[ID]
, MIN([b].[Udate]) AS [AttemptUdate]
FROM @TestData [b]
WHERE [b].[ID] = [a].[ID]
AND [b].[last_code] = 'ATTEMPT'
GROUP BY [b].[ID]
) AS [aa]
OUTER APPLY (
SELECT [c].[ID]
, MAX([c].[Udate]) AS [MaxUdate]
FROM @TestData [c]
WHERE [c].[ID] = [a].[ID]
GROUP BY [c].[ID]
) AS [cc]
WHERE [a].[ID] = COALESCE([aa].[ID], [cc].[ID])
AND [a].[Udate] = COALESCE([aa].[AttemptUdate], [cc].[MaxUdate]);


--use window functions
--Similiar in that we are finding the max Udate and also min Udate when last_code='ATTEMPT'
--Then using COALESCE in the where clause to evaluate which one to use.
--Maybe a little cleaner
SELECT [td].[ID]
, [td].[Udate]
, [td].[last_code]
FROM (
SELECT [ID]
, [last_code]
, [Udate]
, MAX([Udate]) OVER ( PARTITION BY [ID] ) AS [MaxUdate]
, MIN( CASE WHEN [last_code] = 'ATTEMPT' THEN [Udate]
ELSE NULL
END
) OVER ( PARTITION BY [ID] ) AS [AttemptUdate]
FROM @TestData
) AS [td]
WHERE [td].[Udate] = COALESCE([td].[AttemptUdate], [td].[MaxUdate]);


To explain how I got there a little bit, it was primarily base on your requirement:




Overall I m trying to get the last date and code, but if there is an
"ATTEMPT" code, I need to get the first date and that code for each
individual ID.




So for each ID I needed a way to get:



  • Minimum Udate for last_code = 'ATTEMPT' per ID - if there was no ATTEMPT we'll get a null

  • Maximum Udate for all records per ID

If I could determine the above for each record based on ID then my final result set are basically those where the Udate equals my Maximum Udate if the Minimum was null. If the Minimum wasn't null use that instead.



The first option, using 2 outer applies is doing each of the points above.



Minimum Udate for last_code = 'ATTEMPT' per ID - if there was no ATTEMPT we'll get a null:



OUTER APPLY (
SELECT [b].[ID]
, MIN([b].[Udate]) AS [AttemptUdate]
FROM @TestData [b]
WHERE [b].[ID] = [a].[ID]
AND [b].[last_code] = 'ATTEMPT'
GROUP BY [b].[ID]
) AS [aa]


Outer Apply as I might not have an ATTEMPT record for a given ID, so in those situations it returns NULL.



Maximum Udate for all records per ID:



OUTER APPLY (
SELECT [c].[ID]
, MAX([c].[Udate]) AS [MaxUdate]
FROM @TestData [c]
WHERE [c].[ID] = [a].[ID]
GROUP BY [c].[ID]
) AS [cc]


Then the where clause compares what was returned by those to return only the records I want:



 [a].[Udate] = COALESCE([aa].[AttemptUdate], [cc].[MaxUdate]);


I'm using COALESCE to handled and evaluate NULLs. COALESCE will evaluate the fields from left to right and use/return the first non NULL value.



So using this with Udate we can evaluate which Udate value I should use in my filter to satisfy the requirement.



Because if I had an ATTEMPT record field AttemptUdate would have a value and be used in the filter first. If I didn't have an ATTEMPT record AttemptUdate would be NULL so then MaxUdate would be used.



For option 2, similar just going after it a little different.



Minimum Udate for last_code = 'ATTEMPT' per ID - if there was no ATTEMPT we'll get a null:



 MIN( CASE WHEN [last_code] = 'ATTEMPT' THEN [Udate]
ELSE NULL
END
) OVER ( PARTITION BY [ID] ) AS [AttemptUdate]


Min on Udate, but I use a case statement to evaluate if that records is an ATTEMPT or not. using OVER PARTITION will do that based on how I tell it to partition the data, by ID.



Maximum Udate for all records per ID:



MAX([Udate]) OVER ( PARTITION BY [ID] ) AS [MaxUdate]


Go get me the maximum Udate based on ID, since that's how I told it to partition it.



I do all that in a sub-query to make the where clause easier to work with. Then it's the same as before when filtering:



[td].[Udate] = COALESCE([td].[AttemptUdate], [td].[MaxUdate]);


Using COALESCE to determine which date I should be using and only return the records I want.



With the second option, go a little deeper, If you run just the sub query, you'll see you get for each individual record the 2 main driving points of the requirement:



  • What's the Max Udate per ID

  • What's the mint Udate of last_code=ATTEMPT per ID

enter image description here



From there I can just filter on those records satisfying what I was originally looking for, using a COALESCE to simplify my filter.



[td].[Udate] = COALESCE([td].[AttemptUdate], [td].[MaxUdate]);


Use AttemptUdate unless it's NULL then use MaxUdate.






share|improve this answer

























  • It works great !!! I ve got some duplicates, but it s my data, not your query. Thanks +1.

    – madlicksxxx
    Nov 15 '18 at 9:18











  • Also, I would be grateful if you could briefly explain the thought process for this, I wanna see what I was missing. Thanks.

    – madlicksxxx
    Nov 15 '18 at 9:19











  • @madlicksxxx you bet! I updated the answer and added my thought process and how I worked through it. Hope it helps.

    – Tim Mylott
    Nov 15 '18 at 21:48











  • Legend, thank you!

    – madlicksxxx
    Nov 16 '18 at 8:08















1














I think you have a couple of options before attempting a CTE.



Give these a try, examples below:



DECLARE @TestData TABLE
(
[ID] INT
, [Udate] DATE
, [last_code] NVARCHAR(100)
);

INSERT INTO @TestData (
[ID]
, [Udate]
, [last_code]
)
VALUES ( 1, '11/05/2018', 'ATTEMPT ' )
, ( 1, '11/03/2018', 'ATTEMPT' )
, ( 1, '11/01/2017', 'INFO' )
, ( 1, '10/25/2016', 'ARRIVED' )
, ( 1, '9/22/2016 ', 'ARRIVED' )
, ( 1, '9/14/2016 ', 'SENT' )
, ( 1, '9/1/2016 ', 'SENT' )
, ( 2, '10/26/2016', 'RECEIVED' )
, ( 2, '10/19/2016', 'ARRIVED' )
, ( 2, '10/18/2016', 'ARRIVED' )
, ( 2, '10/14/2016', 'ANNOUNCED' )
, ( 2, '9/23/2016 ', 'INFO' )
, ( 2, '9/14/2016 ', 'DAMAGE' )
, ( 2, '9/2/2016 ', 'SCHEDULED' );

--option 1
--couple of outer apply
--1 - to get the min date for attempt
--2 - to get the max date regardless of the the code
--where clause, using coalesce will pick what date. Use the date if I have one for code ='ATTEMPT', if not use the max date.
SELECT [a].*
FROM @TestData [a]
OUTER APPLY (
SELECT [b].[ID]
, MIN([b].[Udate]) AS [AttemptUdate]
FROM @TestData [b]
WHERE [b].[ID] = [a].[ID]
AND [b].[last_code] = 'ATTEMPT'
GROUP BY [b].[ID]
) AS [aa]
OUTER APPLY (
SELECT [c].[ID]
, MAX([c].[Udate]) AS [MaxUdate]
FROM @TestData [c]
WHERE [c].[ID] = [a].[ID]
GROUP BY [c].[ID]
) AS [cc]
WHERE [a].[ID] = COALESCE([aa].[ID], [cc].[ID])
AND [a].[Udate] = COALESCE([aa].[AttemptUdate], [cc].[MaxUdate]);


--use window functions
--Similiar in that we are finding the max Udate and also min Udate when last_code='ATTEMPT'
--Then using COALESCE in the where clause to evaluate which one to use.
--Maybe a little cleaner
SELECT [td].[ID]
, [td].[Udate]
, [td].[last_code]
FROM (
SELECT [ID]
, [last_code]
, [Udate]
, MAX([Udate]) OVER ( PARTITION BY [ID] ) AS [MaxUdate]
, MIN( CASE WHEN [last_code] = 'ATTEMPT' THEN [Udate]
ELSE NULL
END
) OVER ( PARTITION BY [ID] ) AS [AttemptUdate]
FROM @TestData
) AS [td]
WHERE [td].[Udate] = COALESCE([td].[AttemptUdate], [td].[MaxUdate]);


To explain how I got there a little bit, it was primarily base on your requirement:




Overall I m trying to get the last date and code, but if there is an
"ATTEMPT" code, I need to get the first date and that code for each
individual ID.




So for each ID I needed a way to get:



  • Minimum Udate for last_code = 'ATTEMPT' per ID - if there was no ATTEMPT we'll get a null

  • Maximum Udate for all records per ID

If I could determine the above for each record based on ID then my final result set are basically those where the Udate equals my Maximum Udate if the Minimum was null. If the Minimum wasn't null use that instead.



The first option, using 2 outer applies is doing each of the points above.



Minimum Udate for last_code = 'ATTEMPT' per ID - if there was no ATTEMPT we'll get a null:



OUTER APPLY (
SELECT [b].[ID]
, MIN([b].[Udate]) AS [AttemptUdate]
FROM @TestData [b]
WHERE [b].[ID] = [a].[ID]
AND [b].[last_code] = 'ATTEMPT'
GROUP BY [b].[ID]
) AS [aa]


Outer Apply as I might not have an ATTEMPT record for a given ID, so in those situations it returns NULL.



Maximum Udate for all records per ID:



OUTER APPLY (
SELECT [c].[ID]
, MAX([c].[Udate]) AS [MaxUdate]
FROM @TestData [c]
WHERE [c].[ID] = [a].[ID]
GROUP BY [c].[ID]
) AS [cc]


Then the where clause compares what was returned by those to return only the records I want:



 [a].[Udate] = COALESCE([aa].[AttemptUdate], [cc].[MaxUdate]);


I'm using COALESCE to handled and evaluate NULLs. COALESCE will evaluate the fields from left to right and use/return the first non NULL value.



So using this with Udate we can evaluate which Udate value I should use in my filter to satisfy the requirement.



Because if I had an ATTEMPT record field AttemptUdate would have a value and be used in the filter first. If I didn't have an ATTEMPT record AttemptUdate would be NULL so then MaxUdate would be used.



For option 2, similar just going after it a little different.



Minimum Udate for last_code = 'ATTEMPT' per ID - if there was no ATTEMPT we'll get a null:



 MIN( CASE WHEN [last_code] = 'ATTEMPT' THEN [Udate]
ELSE NULL
END
) OVER ( PARTITION BY [ID] ) AS [AttemptUdate]


Min on Udate, but I use a case statement to evaluate if that records is an ATTEMPT or not. using OVER PARTITION will do that based on how I tell it to partition the data, by ID.



Maximum Udate for all records per ID:



MAX([Udate]) OVER ( PARTITION BY [ID] ) AS [MaxUdate]


Go get me the maximum Udate based on ID, since that's how I told it to partition it.



I do all that in a sub-query to make the where clause easier to work with. Then it's the same as before when filtering:



[td].[Udate] = COALESCE([td].[AttemptUdate], [td].[MaxUdate]);


Using COALESCE to determine which date I should be using and only return the records I want.



With the second option, go a little deeper, If you run just the sub query, you'll see you get for each individual record the 2 main driving points of the requirement:



  • What's the Max Udate per ID

  • What's the mint Udate of last_code=ATTEMPT per ID

enter image description here



From there I can just filter on those records satisfying what I was originally looking for, using a COALESCE to simplify my filter.



[td].[Udate] = COALESCE([td].[AttemptUdate], [td].[MaxUdate]);


Use AttemptUdate unless it's NULL then use MaxUdate.






share|improve this answer

























  • It works great !!! I ve got some duplicates, but it s my data, not your query. Thanks +1.

    – madlicksxxx
    Nov 15 '18 at 9:18











  • Also, I would be grateful if you could briefly explain the thought process for this, I wanna see what I was missing. Thanks.

    – madlicksxxx
    Nov 15 '18 at 9:19











  • @madlicksxxx you bet! I updated the answer and added my thought process and how I worked through it. Hope it helps.

    – Tim Mylott
    Nov 15 '18 at 21:48











  • Legend, thank you!

    – madlicksxxx
    Nov 16 '18 at 8:08













1












1








1







I think you have a couple of options before attempting a CTE.



Give these a try, examples below:



DECLARE @TestData TABLE
(
[ID] INT
, [Udate] DATE
, [last_code] NVARCHAR(100)
);

INSERT INTO @TestData (
[ID]
, [Udate]
, [last_code]
)
VALUES ( 1, '11/05/2018', 'ATTEMPT ' )
, ( 1, '11/03/2018', 'ATTEMPT' )
, ( 1, '11/01/2017', 'INFO' )
, ( 1, '10/25/2016', 'ARRIVED' )
, ( 1, '9/22/2016 ', 'ARRIVED' )
, ( 1, '9/14/2016 ', 'SENT' )
, ( 1, '9/1/2016 ', 'SENT' )
, ( 2, '10/26/2016', 'RECEIVED' )
, ( 2, '10/19/2016', 'ARRIVED' )
, ( 2, '10/18/2016', 'ARRIVED' )
, ( 2, '10/14/2016', 'ANNOUNCED' )
, ( 2, '9/23/2016 ', 'INFO' )
, ( 2, '9/14/2016 ', 'DAMAGE' )
, ( 2, '9/2/2016 ', 'SCHEDULED' );

--option 1
--couple of outer apply
--1 - to get the min date for attempt
--2 - to get the max date regardless of the the code
--where clause, using coalesce will pick what date. Use the date if I have one for code ='ATTEMPT', if not use the max date.
SELECT [a].*
FROM @TestData [a]
OUTER APPLY (
SELECT [b].[ID]
, MIN([b].[Udate]) AS [AttemptUdate]
FROM @TestData [b]
WHERE [b].[ID] = [a].[ID]
AND [b].[last_code] = 'ATTEMPT'
GROUP BY [b].[ID]
) AS [aa]
OUTER APPLY (
SELECT [c].[ID]
, MAX([c].[Udate]) AS [MaxUdate]
FROM @TestData [c]
WHERE [c].[ID] = [a].[ID]
GROUP BY [c].[ID]
) AS [cc]
WHERE [a].[ID] = COALESCE([aa].[ID], [cc].[ID])
AND [a].[Udate] = COALESCE([aa].[AttemptUdate], [cc].[MaxUdate]);


--use window functions
--Similiar in that we are finding the max Udate and also min Udate when last_code='ATTEMPT'
--Then using COALESCE in the where clause to evaluate which one to use.
--Maybe a little cleaner
SELECT [td].[ID]
, [td].[Udate]
, [td].[last_code]
FROM (
SELECT [ID]
, [last_code]
, [Udate]
, MAX([Udate]) OVER ( PARTITION BY [ID] ) AS [MaxUdate]
, MIN( CASE WHEN [last_code] = 'ATTEMPT' THEN [Udate]
ELSE NULL
END
) OVER ( PARTITION BY [ID] ) AS [AttemptUdate]
FROM @TestData
) AS [td]
WHERE [td].[Udate] = COALESCE([td].[AttemptUdate], [td].[MaxUdate]);


To explain how I got there a little bit, it was primarily base on your requirement:




Overall I m trying to get the last date and code, but if there is an
"ATTEMPT" code, I need to get the first date and that code for each
individual ID.




So for each ID I needed a way to get:



  • Minimum Udate for last_code = 'ATTEMPT' per ID - if there was no ATTEMPT we'll get a null

  • Maximum Udate for all records per ID

If I could determine the above for each record based on ID then my final result set are basically those where the Udate equals my Maximum Udate if the Minimum was null. If the Minimum wasn't null use that instead.



The first option, using 2 outer applies is doing each of the points above.



Minimum Udate for last_code = 'ATTEMPT' per ID - if there was no ATTEMPT we'll get a null:



OUTER APPLY (
SELECT [b].[ID]
, MIN([b].[Udate]) AS [AttemptUdate]
FROM @TestData [b]
WHERE [b].[ID] = [a].[ID]
AND [b].[last_code] = 'ATTEMPT'
GROUP BY [b].[ID]
) AS [aa]


Outer Apply as I might not have an ATTEMPT record for a given ID, so in those situations it returns NULL.



Maximum Udate for all records per ID:



OUTER APPLY (
SELECT [c].[ID]
, MAX([c].[Udate]) AS [MaxUdate]
FROM @TestData [c]
WHERE [c].[ID] = [a].[ID]
GROUP BY [c].[ID]
) AS [cc]


Then the where clause compares what was returned by those to return only the records I want:



 [a].[Udate] = COALESCE([aa].[AttemptUdate], [cc].[MaxUdate]);


I'm using COALESCE to handled and evaluate NULLs. COALESCE will evaluate the fields from left to right and use/return the first non NULL value.



So using this with Udate we can evaluate which Udate value I should use in my filter to satisfy the requirement.



Because if I had an ATTEMPT record field AttemptUdate would have a value and be used in the filter first. If I didn't have an ATTEMPT record AttemptUdate would be NULL so then MaxUdate would be used.



For option 2, similar just going after it a little different.



Minimum Udate for last_code = 'ATTEMPT' per ID - if there was no ATTEMPT we'll get a null:



 MIN( CASE WHEN [last_code] = 'ATTEMPT' THEN [Udate]
ELSE NULL
END
) OVER ( PARTITION BY [ID] ) AS [AttemptUdate]


Min on Udate, but I use a case statement to evaluate if that records is an ATTEMPT or not. using OVER PARTITION will do that based on how I tell it to partition the data, by ID.



Maximum Udate for all records per ID:



MAX([Udate]) OVER ( PARTITION BY [ID] ) AS [MaxUdate]


Go get me the maximum Udate based on ID, since that's how I told it to partition it.



I do all that in a sub-query to make the where clause easier to work with. Then it's the same as before when filtering:



[td].[Udate] = COALESCE([td].[AttemptUdate], [td].[MaxUdate]);


Using COALESCE to determine which date I should be using and only return the records I want.



With the second option, go a little deeper, If you run just the sub query, you'll see you get for each individual record the 2 main driving points of the requirement:



  • What's the Max Udate per ID

  • What's the mint Udate of last_code=ATTEMPT per ID

enter image description here



From there I can just filter on those records satisfying what I was originally looking for, using a COALESCE to simplify my filter.



[td].[Udate] = COALESCE([td].[AttemptUdate], [td].[MaxUdate]);


Use AttemptUdate unless it's NULL then use MaxUdate.






share|improve this answer















I think you have a couple of options before attempting a CTE.



Give these a try, examples below:



DECLARE @TestData TABLE
(
[ID] INT
, [Udate] DATE
, [last_code] NVARCHAR(100)
);

INSERT INTO @TestData (
[ID]
, [Udate]
, [last_code]
)
VALUES ( 1, '11/05/2018', 'ATTEMPT ' )
, ( 1, '11/03/2018', 'ATTEMPT' )
, ( 1, '11/01/2017', 'INFO' )
, ( 1, '10/25/2016', 'ARRIVED' )
, ( 1, '9/22/2016 ', 'ARRIVED' )
, ( 1, '9/14/2016 ', 'SENT' )
, ( 1, '9/1/2016 ', 'SENT' )
, ( 2, '10/26/2016', 'RECEIVED' )
, ( 2, '10/19/2016', 'ARRIVED' )
, ( 2, '10/18/2016', 'ARRIVED' )
, ( 2, '10/14/2016', 'ANNOUNCED' )
, ( 2, '9/23/2016 ', 'INFO' )
, ( 2, '9/14/2016 ', 'DAMAGE' )
, ( 2, '9/2/2016 ', 'SCHEDULED' );

--option 1
--couple of outer apply
--1 - to get the min date for attempt
--2 - to get the max date regardless of the the code
--where clause, using coalesce will pick what date. Use the date if I have one for code ='ATTEMPT', if not use the max date.
SELECT [a].*
FROM @TestData [a]
OUTER APPLY (
SELECT [b].[ID]
, MIN([b].[Udate]) AS [AttemptUdate]
FROM @TestData [b]
WHERE [b].[ID] = [a].[ID]
AND [b].[last_code] = 'ATTEMPT'
GROUP BY [b].[ID]
) AS [aa]
OUTER APPLY (
SELECT [c].[ID]
, MAX([c].[Udate]) AS [MaxUdate]
FROM @TestData [c]
WHERE [c].[ID] = [a].[ID]
GROUP BY [c].[ID]
) AS [cc]
WHERE [a].[ID] = COALESCE([aa].[ID], [cc].[ID])
AND [a].[Udate] = COALESCE([aa].[AttemptUdate], [cc].[MaxUdate]);


--use window functions
--Similiar in that we are finding the max Udate and also min Udate when last_code='ATTEMPT'
--Then using COALESCE in the where clause to evaluate which one to use.
--Maybe a little cleaner
SELECT [td].[ID]
, [td].[Udate]
, [td].[last_code]
FROM (
SELECT [ID]
, [last_code]
, [Udate]
, MAX([Udate]) OVER ( PARTITION BY [ID] ) AS [MaxUdate]
, MIN( CASE WHEN [last_code] = 'ATTEMPT' THEN [Udate]
ELSE NULL
END
) OVER ( PARTITION BY [ID] ) AS [AttemptUdate]
FROM @TestData
) AS [td]
WHERE [td].[Udate] = COALESCE([td].[AttemptUdate], [td].[MaxUdate]);


To explain how I got there a little bit, it was primarily base on your requirement:




Overall I m trying to get the last date and code, but if there is an
"ATTEMPT" code, I need to get the first date and that code for each
individual ID.




So for each ID I needed a way to get:



  • Minimum Udate for last_code = 'ATTEMPT' per ID - if there was no ATTEMPT we'll get a null

  • Maximum Udate for all records per ID

If I could determine the above for each record based on ID then my final result set are basically those where the Udate equals my Maximum Udate if the Minimum was null. If the Minimum wasn't null use that instead.



The first option, using 2 outer applies is doing each of the points above.



Minimum Udate for last_code = 'ATTEMPT' per ID - if there was no ATTEMPT we'll get a null:



OUTER APPLY (
SELECT [b].[ID]
, MIN([b].[Udate]) AS [AttemptUdate]
FROM @TestData [b]
WHERE [b].[ID] = [a].[ID]
AND [b].[last_code] = 'ATTEMPT'
GROUP BY [b].[ID]
) AS [aa]


Outer Apply as I might not have an ATTEMPT record for a given ID, so in those situations it returns NULL.



Maximum Udate for all records per ID:



OUTER APPLY (
SELECT [c].[ID]
, MAX([c].[Udate]) AS [MaxUdate]
FROM @TestData [c]
WHERE [c].[ID] = [a].[ID]
GROUP BY [c].[ID]
) AS [cc]


Then the where clause compares what was returned by those to return only the records I want:



 [a].[Udate] = COALESCE([aa].[AttemptUdate], [cc].[MaxUdate]);


I'm using COALESCE to handled and evaluate NULLs. COALESCE will evaluate the fields from left to right and use/return the first non NULL value.



So using this with Udate we can evaluate which Udate value I should use in my filter to satisfy the requirement.



Because if I had an ATTEMPT record field AttemptUdate would have a value and be used in the filter first. If I didn't have an ATTEMPT record AttemptUdate would be NULL so then MaxUdate would be used.



For option 2, similar just going after it a little different.



Minimum Udate for last_code = 'ATTEMPT' per ID - if there was no ATTEMPT we'll get a null:



 MIN( CASE WHEN [last_code] = 'ATTEMPT' THEN [Udate]
ELSE NULL
END
) OVER ( PARTITION BY [ID] ) AS [AttemptUdate]


Min on Udate, but I use a case statement to evaluate if that records is an ATTEMPT or not. using OVER PARTITION will do that based on how I tell it to partition the data, by ID.



Maximum Udate for all records per ID:



MAX([Udate]) OVER ( PARTITION BY [ID] ) AS [MaxUdate]


Go get me the maximum Udate based on ID, since that's how I told it to partition it.



I do all that in a sub-query to make the where clause easier to work with. Then it's the same as before when filtering:



[td].[Udate] = COALESCE([td].[AttemptUdate], [td].[MaxUdate]);


Using COALESCE to determine which date I should be using and only return the records I want.



With the second option, go a little deeper, If you run just the sub query, you'll see you get for each individual record the 2 main driving points of the requirement:



  • What's the Max Udate per ID

  • What's the mint Udate of last_code=ATTEMPT per ID

enter image description here



From there I can just filter on those records satisfying what I was originally looking for, using a COALESCE to simplify my filter.



[td].[Udate] = COALESCE([td].[AttemptUdate], [td].[MaxUdate]);


Use AttemptUdate unless it's NULL then use MaxUdate.







share|improve this answer














share|improve this answer



share|improve this answer








edited Nov 15 '18 at 21:47

























answered Nov 14 '18 at 22:15









Tim MylottTim Mylott

1,11318




1,11318












  • It works great !!! I ve got some duplicates, but it s my data, not your query. Thanks +1.

    – madlicksxxx
    Nov 15 '18 at 9:18











  • Also, I would be grateful if you could briefly explain the thought process for this, I wanna see what I was missing. Thanks.

    – madlicksxxx
    Nov 15 '18 at 9:19











  • @madlicksxxx you bet! I updated the answer and added my thought process and how I worked through it. Hope it helps.

    – Tim Mylott
    Nov 15 '18 at 21:48











  • Legend, thank you!

    – madlicksxxx
    Nov 16 '18 at 8:08

















  • It works great !!! I ve got some duplicates, but it s my data, not your query. Thanks +1.

    – madlicksxxx
    Nov 15 '18 at 9:18











  • Also, I would be grateful if you could briefly explain the thought process for this, I wanna see what I was missing. Thanks.

    – madlicksxxx
    Nov 15 '18 at 9:19











  • @madlicksxxx you bet! I updated the answer and added my thought process and how I worked through it. Hope it helps.

    – Tim Mylott
    Nov 15 '18 at 21:48











  • Legend, thank you!

    – madlicksxxx
    Nov 16 '18 at 8:08
















It works great !!! I ve got some duplicates, but it s my data, not your query. Thanks +1.

– madlicksxxx
Nov 15 '18 at 9:18





It works great !!! I ve got some duplicates, but it s my data, not your query. Thanks +1.

– madlicksxxx
Nov 15 '18 at 9:18













Also, I would be grateful if you could briefly explain the thought process for this, I wanna see what I was missing. Thanks.

– madlicksxxx
Nov 15 '18 at 9:19





Also, I would be grateful if you could briefly explain the thought process for this, I wanna see what I was missing. Thanks.

– madlicksxxx
Nov 15 '18 at 9:19













@madlicksxxx you bet! I updated the answer and added my thought process and how I worked through it. Hope it helps.

– Tim Mylott
Nov 15 '18 at 21:48





@madlicksxxx you bet! I updated the answer and added my thought process and how I worked through it. Hope it helps.

– Tim Mylott
Nov 15 '18 at 21:48













Legend, thank you!

– madlicksxxx
Nov 16 '18 at 8:08





Legend, thank you!

– madlicksxxx
Nov 16 '18 at 8:08



















draft saved

draft discarded
















































Thanks for contributing an answer to Stack Overflow!


  • Please be sure to answer the question. Provide details and share your research!

But avoid


  • Asking for help, clarification, or responding to other answers.

  • Making statements based on opinion; back them up with references or personal experience.

To learn more, see our tips on writing great answers.




draft saved


draft discarded














StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53309007%2fconditional-row-number-for-min-and-maximum-date%23new-answer', 'question_page');

);

Post as a guest















Required, but never shown





















































Required, but never shown














Required, but never shown












Required, but never shown







Required, but never shown

































Required, but never shown














Required, but never shown












Required, but never shown







Required, but never shown







這個網誌中的熱門文章

Barbados

How to read a connectionString WITH PROVIDER in .NET Core?

Node.js Script on GitHub Pages or Amazon S3