简体   繁体   中英

Getting next available date from database

I have a system that allows users to assign a specific file to a past or present date. The limitations are that they may only upload one file per day per user. When the user goes to upload a file the date field must default to the current date and when that date is not available it will show the first available date in the past in DESC order. Below is the relevent field names.

file_id (INT - INDEX - AUTO INCREMENT)
user_id (INT - may index this)
upload_date (INT - stores date as a unix timestamp)

The only solution I have really found would be to build them all into an array in DESC order by date and loop through until i found an empty slot. However, I feel this could really cause speed issues if the user had the past thousand days filled. I feel like I am overlooking a simple solution.

PLEASE NOTE: For one reason or another they Date is being stored as a Unix timestamp which I understand the downsides on and I am not concerned about correcting at this time.

To get the most recent date that has not been used:

select user_id, max(date) - 1
from (select ud.*,
             (select max(date) from upload_date ud2 where ud2.user_id = ud.user_id and ud2.date < ud.date
            ) as prevdate
      from upload_date ud
     ) ud
where date(from_unixtime(ud.prevdate)) <>  date(from_unixtime(ud.date)) - 1 or
      ud.prevdate is null
group by user_id

This query first gets the previous date for any given day using a correlated subquery. It then converts the time values to dates and selects any row where the previous date has a gap. The largest of the date minus one is the date you are looking for.

This SQL is untested, so it may have syntax errors.

One way to approach this is with a classic "return missing rows" query. Basically, to get a "missing" row returned from the database, you need a way to generate the "missing" rows.

To build such a query, we can start with:

SELECT MAX(t.upload_date)
  FROM mytable t
 WHERE t.upload_date <= NOW() 
   AND t.user = 'someuser'

That gets the initial date, that we are going to work backwards from.

For the "one per day" requirement, you probably want to truncate that upload_date to midnight, at least for this query. For now, we'll assume that the expression in the SELECT list is already truncated, to illustrate the approach, without bogging down in the details of dealing with a unix timestamp.

To generate a descending list of dates, starting with that initial date retrieved by the previous query...

SELECT s.upload_date - INTERVAL n.d DAY AS available_date
  FROM ( SELECT MAX(t.upload_date) AS upload_date
           FROM mytable t
          WHERE t.upload_date <= NOW() 
            AND t.user = 'someuser'
       ) s
 CROSS
  JOIN ( SELECT 0 AS d UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3
         UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 
         UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9 
       ) n
 ORDER BY n.d DESC

With that result, we can use an anti-join pattern to find which dates are not already used. This is a LEFT JOIN and a predicate that throws out matching rows:

SELECT s.upload_date - INTERVAL n.d DAY AS available_date
  FROM ( SELECT MAX(t.upload_date) AS upload_date
           FROM mytable t
          WHERE t.upload_date <= NOW() 
            AND t.user = 'someuser'
       ) s
 CROSS
  JOIN ( SELECT 0 AS d UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3
         UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 
         UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9 
       ) n
  LEFT
  JOIN mytable u
    ON u.upload_date = s.upload_date - INTERVAL n.d DAY
   AND u.user = 'someuser'
 WHERE u.upload_date IS NULL 
 ORDER BY n.d DESC
 LIMIT 1

That only looks back 9 days, to get it to look back more days, just extend the inline view aliased as n to return more consecutive integers. (There's some tricks we can play with cross joins to get a whole boatload of integers.)

All that remains is working on the "matching" criteria (which works with the MySQL DATE datatype):

    ON u.upload_date = s.upload_date - INTERVAL n.d DAY

into something like this:

    ON u.upload_date >= UNIX_TIMESTAMP(FROM_UNIXTIME(s.upload_date)-INTERVAL n.d+1 DAY)
   AND u.upload_date <  UNIX_TIMESTAMP(FROM_UNIXTIME(s.upload_date)-INTERVAL n.d DAY)

And futzing with the integer timestamp value to get a MySQL DATE out of it...

 SELECT DATE(FROM_UNIXTIME(s.upload_date)) - INTERVAL n.d DAY AS available_date

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