tags:

views:

1275

answers:

5

I have a MySQL table called bb_posts used by a bbPress forum. It has an autoincrement field called topid_id and another field called topic_poster.

I'm trying to write a function that finds the "next post by the same author". So, for instance, say the user is on a particular page that displays topic 123. If you do a SQL query:

SELECT *
FROM `bb_topics`
WHERE `topic_poster` = 5
ORDER BY `topic_id` ASC

This might return the following rows:

topic_id    topic_poster
6           5
50          5
123         5
199         5
2039        5

What I'd like to do is write a SQL query that returns these two rows:

topic_id    topic_poster
50          5
199         5

This would be the row PRIOR to the row with topic_id of 123, and the row AFTER that row.

If it's too hard to do this in one query, it's definitely OK to break this up into two queries...

I'd like to avoid doing the whole SQL query ("SELECT * FROM bb_topics WHERE topic_poster = 5") and looping through the results, because the result set is sometimes huge.

Is this possible? :-)

A: 

NOTE: Before downvoting, please read the comment and the first revision of question ;-)

By definition the data on a RDBMS table are not ordered. It means that when you run a query, if you don't specify an ORDER BY clause, the order can be different from one query to another. Furthermore, you have to consider that the next query is going to (or, at least, may) return a different order.

Suppose that may be another user on database inserts a new record and the RDBMS considers to put it between the rows you are considering. Or the DBA is moving the table to another in online basis (this is possible in Oracle, for instance), in this case the data can be changed on any matter.

For your question, you have to build a kind of query result cache to determine prior and next rows on the queries.

FerranB
I added an ORDER BY clause to clarify. Thanks!
bobbyh
I downvoted you because this is a simple query problem and has nothing to do with the sort order of records.
cdonner
@cdonner, the first revision of the question shows the query without the ORDER BY.
FerranB
@FerranB, I know, but it is irrelevant. He wants to get the next/previous row for a given row, regardless of the sort order of the row. Of course you can do that in the result set in memory, then you are right - it would matter. But he asked for the SQL.
cdonner
@cdonner In the case of non ORDER BY that is impossible with SQL on a standard RDMBS because ALL the result sets on a query are not determinist. Without the ORDER BY there are no way to do it.
FerranB
@FerranB, aggregate functions like min() and max() always return the min and max values, regardless of the sort order.
cdonner
cdonner: this is a definition issue. Next and Previous are meaningless concepts unless you have an ordered set. You can't say regardless of the of the sort order: what do you put in the max(???) unless you know the order? max(foo) orders by foo. max(bar) orders by bar.
jmucchiello
@joe_mucchiello, you are splitting hairs. There is an implicit order (of increasing topic ids). I think he explained this sufficiently in the question.
cdonner
+2  A: 

Next one:

SELECT * FROM `bb_topics` 
      WHERE `topic_id` = 
      (select min(`topic_id`) FROM `bb_topics` where `topic_id` > 123
         and `topic_poster` = 5)

Previous one:

SELECT * FROM `bb_topics` 
      WHERE `topic_id` = 
      (select max(`topic_id`) FROM `bb_topics` where `topic_id` < 123
         and `topic_poster` = 5)

Both:

SELECT * FROM `bb_topics` 
      WHERE `topic_id` = 
      (select min(`topic_id`) FROM `bb_topics` where `topic_id` > 123
                     and `topic_poster` = 5)
      or `topic_id` = 
      (select max(`topic_id`) FROM `bb_topics` where `topic_id` < 123
                        and `topic_poster` = 5)
cdonner
This is promising! Sorry for the newb question, but where do I put the "123" (the topic_id) that I know? Also, Is it SELECT * FROM `bb_topics` *AS* t?
bobbyh
yes, AS is probably right. I am more familiar with SQL Server syntax. Let me edit this one more time for you.
cdonner
This will do if you already have the current topic id and the poster id. As le dorfier has shown, you can do the same thing in a single query with self-joins as well (it will give you the next and prevous id for every row in your result set in a single query - much more elegant).
cdonner
except that his query won't work - it will give you ALL the higher and lower topic ids for this poster. You will still need subqueries with the min() and max() function in the join conditions.
cdonner
cdonner, this is awesome, thanks!I'm sure this works in SQL Server, but for MySQL, I think the syntax needs to be: SELECT * FROM `bb_topics` WHERE `topic_id` = (SELECT MIN(`topic_id`) FROM `bb_topics` WHERE `topic_id` > 123 AND `topic_poster` = 5)This works! :-)
bobbyh
it would not have worked, you need of the course the FROM clause in the subqueries. I added that. Glad I could help.
cdonner
A: 

Here's a subtle but efficient way to accomplish this, and it works on even old versions of MySQL which don't support subqueries --

SELECT *    
FROM bb_topics AS bb1    
LEFT JOIN bb_topics AS bb2    
    ON bb1.topic_poster = bb2.topic_poster    
    AND bb2.topic_id > bb1.topic_id    
LEFT JOIN bb_topics AS bb3  
    ON bb1.topic_poster = bb3.topic_poster    
    AND bb3.topic_id > bb1.topic_id    
    AND bb3.topic_id < bb2.topic_id  
WHERE bb1.topic_poster = 5  
    AND bb3.topic_id IS NULL

This gets you the next adjacent post. I can fill in the symmetric clauses for the next previous post if it's not self-evident.

EDIT - minor corrected alii.

le dorfier
this is not quite right, since the joins will give you much more than you want. There is no way around the subqueries.
cdonner
Then I didn't get the expression correctly. There should only be max 1 row with a higher topic_id and none between it and the target id. (The "topic_id IS NULL" says there is no match).
le dorfier
+1  A: 

Look at this older question as well.

My guess is that the UNION with LIMIT 1 performs better than the aggregates in my answer here.

cdonner
A: 

what will be sql statement for getting records from row no. 50 to 80

skiping 1- 49

You really should post this as a separate question... but my guess is, you're looking for SELECT * FROM table LIMIT 30 OFFSET 50; (I'm probably off by one somewhere in there).
kquinn