Home > Net >  Mysql get next two dates grouped by ID
Mysql get next two dates grouped by ID

Time:10-29

I have a table that has all eventIDs and event dates. I need to get the next two dates, beyond the current date, for a specific subset of eventIDs. I have tried many iterations and looked at many posts that return the top N results by GROUP - but I can't get any of those examples to work as those examples aren't using dates like I need to. (ex: Using LIMIT within GROUP BY to get N results per group?)

 ------------ ----- 
| eDate      | ID  |
 ------------ ----- 
| 2021-10-27 | 1   |
 ------------ ----- 
| 2021-11-03 | 1   |
 ------------ ----- 
| 2021-11-10 | 1   |
 ------------ ----- 
| 2021-11-17 | 1   |
 ------------ ----- 
| 2021-11-24 | 1   |
 ------------ ----- 
| 2021-12-01 | 1   |
 ------------ ----- 
| 2021-12-08 | 1   |
 ------------ ----- 
| 2021-10-27 | 2   |
 ------------ ----- 
| 2021-11-03 | 2   |
 ------------ ----- 
| 2021-11-10 | 2   |
 ------------ ----- 
| 2021-11-17 | 2   |
 ------------ ----- 
| 2021-11-24 | 2   |
 ------------ ----- 
| 2021-12-01 | 2   |
 ------------ ----- 
| 2021-12-08 | 2   |
 ------------ ----- 
| 2021-10-27 | 3   |
 ------------ ----- 
| 2021-11-03 | 3   |
 ------------ ----- 
| 2021-11-10 | 3   |
 ------------ ----- 
| 2021-11-17 | 3   |
 ------------ ----- 
| 2021-11-24 | 3   |
 ------------ ----- 
| 2021-12-01 | 3   |
 ------------ ----- 
| 2021-12-08 | 3   |
 ------------ ----- 

SELECT eDate,ID,COUNT(*) as eCount FROM myTable WHERE ID in (1,3) AND eDate >=CURDATE() GROUP BY ID,eDate HAVING eCount < 3 ;

This query doesn't work because the GROUP BY ID,eDate creates a unique pair, so its returning ALL entries for IDs 1 and 3 - all with a eCount = 1

Technically I don't actually need the eCount but it illustrates one of my many attempts to get the correct results. If today was 2020-10-28, my desired results would be following.

 ------------ ----- -------- 
| eDate      | ID  | eCount |
 ------------ ----- -------- 
| 2021-11-03 | 1   | 1      |
 ------------ ----- -------- 
| 2021-11-10 | 1   | 2      |
 ------------ ----- -------- 
| 2021-11-03 | 3   | 1      | 
 ------------ ----- -------- 
| 2021-11-10 | 3   | 2      |
 ------------ ----- -------- 

CodePudding user response:

I think I figured out a solution...I don't know how well it scales or if there are other caveats I need to think about, but it occurred to me I don't need to use GROUP BY because I am already looking for ID in (1,3). All I need to do count the number of IDs I am looking for and multiple it by 2 to set as the LIMIT

IE: 1,3 = LIMIT 4
SELECT * FROM myTable WHERE ID IN (1,3) AND eDate>=CURDATE() ORDER BY eDate LIMIT 4 ;

IE: 1,3,11, 15 = LIMIT 8
SELECT * FROM myTable WHERE ID IN (1,3,11,15) AND eDate>=CURDATE() ORDER BY eDate LIMIT 4 ;

This will grab ALL dates for those IDs, order them by the date...so the top 4 or top 8 will be all the records I need.

CodePudding user response:

You didn't specify the type of eDate, so if the eDate type is DATE (only date without time) I wish this can help you.

SELECT eDate, ID, COUNT(ID) AS eCount
FROM myTable
WHERE ID IN (1,3) -- This condition return just two ids (1 and 3)
        AND eDate BETWEEN CONVERT(DATE, GETDATE()) AND CONVERT(DATE, DATEADD(DAY, 2, GETDATE())) -- This condition return the next two dates, beyond the current date ( current date = GETDATE() )
GROUP BY ID, eDate 
HAVING eCount < 3 -- This condition ensure select rows just have count loser than 3;

But if the eDate type is DATETIME, you must convert it first. CONVERT(DATE, eDate)

CodePudding user response:

Your solution works only if you have 2 or more dates after current date. Observe this example, if one of you id only have 1 date appear after current date, the LIMIT 4 in this query:

SELECT * FROM myTable WHERE ID IN (1,3) AND eDate>=CURDATE() ORDER BY eDate LIMIT 4 ;

.. will populate another row with ID=1. The result might be this instead:

 ------------ ---- 
|   eDate    | ID |
 ------------ ---- 
| 2021-11-03 | 3  |
| 2021-11-03 | 1  |
| 2021-11-10 | 1  |
| 2021-11-17 | 1  |
 ------------ ---- 

I'm guessing that's not what you want.

Following Akina's comment of "Use ROW_NUMBER() in CTE", if you're on MySQL version that supports window function, this could be the query you need:

WITH cte AS (
SELECT eDate, ID, ROW_NUMBER() OVER (PARTITION BY ID ORDER BY eDate) eCount
  FROM myTable WHERE eDate>=CURDATE()
  ORDER BY ID, eDate) 
SELECT * FROM cte 
WHERE eCount <=2;

You'll get all ID's date records that appear after current date with the flexibility to define specific ID or not and without the need to use LIMIT. For older MySQL versions that doesn't support window function, you probably can emulate the same idea of using row number like this:

SELECT eDate, ID, rownum
FROM
   ( SELECT *,
           @rn:=CASE WHEN @i = ID AND eDate >= CURDATE()
                   THEN @rn   1
                   ELSE 0 END AS rownum,
            @i := ID
        FROM myTable m  
         CROSS JOIN (SELECT @i:=0, @rn:=0)  r
       ORDER BY ID, eDate) v
 WHERE rownum > 0 AND rownum <= 2 
ORDER BY ID, eDate;

Demo fiddle

  • Related