2

Right now I have a problem because importing rows to my database from an external source happens so quickly.

If more than one row is created with the exact same publish date, it is impossible to sort by date and scroll through posts sequentially.

Let's say five rows are all generated at exactly 11:22:04 AM:

  • Row 1 - 11:22:04 AM
  • Row 2 - 11:22:04 AM
  • Row 3 - 11:22:04 AM
  • Row 4 - 11:22:04 AM
  • Row 5 - 11:22:04 AM

If a visitor is viewing Row 3 and wants "the next row" I am going to ask the database to give me the next row published later than or equal to 11:22:04 AM, which is going to be Row 1 no matter what I do.

  • If I don't say "or equal to" that would just mean that the visitor would never get to see Row 4 or Row 5, which is just as bad as always getting Row 1.
  • Adding the row ID to the order by clause doesn't help because -- again -- it's just going to always give me Row 1 (if I'm currently looking at Row 3).
  • Adding a greater than or less than to the where clause doesn't help because -- for example -- if I'm looking at Row 3 and I want "the next" row but say the ID needs to be greater than 3, I'm then never going to get Row 1.

I can cheat the system by updating every row in the database published at the exact same time with the row's ID as the seconds, which would turn the above records into:

  • Row 1 - 11:22:01 AM
  • Row 2 - 11:22:02 AM
  • Row 3 - 11:22:03 AM
  • Row 4 - 11:22:04 AM
  • Row 5 - 11:22:05 AM

That actually works very well. The problem is that new rows are added every time an administrator imports data, and I can't be constantly updating the database to correct this.

My queries to get next and previous look like this:

// next row
select  t.*
from    table t
where   t.postdate  >= '{$current_date}'
and     t.postdate  < now()
and     t.id        <> {$current_id}
order   by t.postdate
limit   1

// previous row
select  t.*
from    table t
where   t.postdate  <= '{$current_date}'
and     t.postdate  < now()
and     t.id        <> {$current_id}
order   by t.postdate desc
limit   1

(Yes, I have googled this extensively and reviewed several similar questions on Stackoverflow!)

  • Did you look at commands for skipping rows with sql? OFFSET is common command i think, though never used it. These maybe relevant too though: http://stackoverflow.com/questions/16568/how-to-select-the-nth-row-in-a-sql-database-table http://stackoverflow.com/questions/758186/how-to-get-n-rows-starting-from-row-m-from-sorted-table-in-t-sql. Maybe add second ORDER field and can use that – Daniel Brose Aug 12 '15 at 04:45
  • do you have an auto_inc int, if not can you add one, sorry if i missed it above – Drew Aug 12 '15 at 04:46
  • @DanielBrose I can't see how skipping rows and / or using OFFSET would help resolve the problem in any way. I've tried multiple different ORDER BY clauses but keep running into the same problem. – David Vincent Gagne Aug 12 '15 at 04:56
  • @Drew Yes, each row has an auto_increment field. Using it does not help because it simply means I'd always get -- for example -- Row 1 as the "next" row regardless of whether I'm currently on Row 3 or Row 4. – David Vincent Gagne Aug 12 '15 at 04:58
  • @DavidVincentGagne - I assume your ID is an autoincrement. So if your ORDER BY has 2 fields, postdate THEN id, then having "and t.id > {$current_id}" should sort it. The OFFSET maybe not that useful, as would assume knowledge of total rows and ect, but you CAN always assume that the ID would be greater if postdate is the same – Daniel Brose Aug 12 '15 at 04:59
  • I don't see it as a problem. perhaps all the data comes in exactly in order, so the ID is golden ... from an ordering perspective. You have the ID back in PHP in an array. You know exactly which version of the **same datetime** you are processing by ID – Drew Aug 12 '15 at 04:59

2 Answers2

3

Could you try something like this:

// next row
select  t.*
from    table t
where   CONCAT(t.postdate, t.id)  >= '{$current_date}{$current_id}'
and     t.postdate  < now()
and     t.id        <> {$current_id}
order   by t.postdate
limit   1

// previous row
select  t.*
from    table t
where   CONCAT(t.postdate, t.id)  <= '{$current_date}{$current_id}'
and     t.postdate  < now()
and     t.id        <> {$current_id}
order   by t.postdate desc
limit   1
JasonJensenDev
  • 2,377
  • 21
  • 30
  • I'm trying this and it's worked for several test cases. I was wary of concatenating a `DATETIME` and an `INT(11)` but it seems to be okay. Until or unless I run into a problem -- I'll be testing quite a bit for the next few weeks! -- I am going to take this as the best answer. Thanks! – David Vincent Gagne Aug 12 '15 at 05:14
0

Assumptions

Main assumption is that ID field is auto_increment.

Make use of it

// next row
select  t.*
from    table t
where   (( t.postdate  = '{$current_date}' AND  t.id > {$current_id})
OR t.postdate  > '{$current_date}')
and     t.postdate  < now()
order   by t.postdate, t.id
limit   1

// previous row
select  t.*
from    table t
where   (( t.postdate  = '{$current_date}' AND  t.id < {$current_id})
OR t.postdate  < '{$current_date}')
and     t.postdate  < now()
order   by t.postdate desc, t.id DESC
limit   1

Its as simple as that.

Maybe play with the exact syntax a little to get your desired result (order by asc or desc, > or <) but it will never select same row.

Bit rusty with sql, so confirm the syntax too, but the explicit id check will always make sure that if same postdate to further distincts by id.

Update

Added ACS or DESC to the ORDER BY t.id to help ensure all rows selectable, as noted in comments

Update

sqlfiddle proving this concept: http://sqlfiddle.com/#!9/70e01/18

my first sqlfiddle, so excuse if mistakes, but works fine to me.

Daniel Brose
  • 1,394
  • 10
  • 24
  • No, sorry. You're going to run into the same problem I explained in my original question doing it this way. Let's say you land on Row 3. If you say you want the "next" post (where the date is the same but the ID is greater) then yes, you will get Row 4. But if you want the "previous" post (where the date is the same but the ID is lower) then you are only ever going to get Row 1 and never Row 2 (because you have to sort first on date and second on ID, because not **all** the dates are the same). – David Vincent Gagne Aug 12 '15 at 05:08
  • @DavidVincentGagne - i did read your comments carefully, i did have to edit typo sorry but the scoping should allow a proper EITHER OR type check. Your ID can be greater or lesser, OR the postdate different. ROW 1 stays selectable that way. – Daniel Brose Aug 12 '15 at 05:10
  • Sorry, I hit [return] on the previous comment before I finished explaining. It's updated now to (hopefully) explain why using the `auto_increment` field doesn't help. – David Vincent Gagne Aug 12 '15 at 05:15
  • @DavidVincentGagne - thanks for that, i have bad habits of posting then editing and editing myself ;) I cant test the syntax, but see my edit and that adding ACS or DESC to t.id sorting would solve this – Daniel Brose Aug 12 '15 at 05:19
  • Sorry. I appreciate your effort, but you're still going to get the same result. Your "next" example is only going to return Row 1 every time. – David Vincent Gagne Aug 13 '15 at 15:55
  • @DavidVincentGagne - no it doesnt, check the sqlfiddle i just posted. This approach is at least in my mind more desirable than string concatenation for the numerous reasons string concatenation is at times risky. As a side note, be increadibly careful to escape input (such as post_id) even if you think the user cant affect it, cause if your just grabbing from $_POST or $_GET and directly planting there, then that code is highly vulnerable to injection. – Daniel Brose Aug 14 '15 at 01:05