1 | 2 | 3

11 - 14   [14]

Selecting Next And Previous

Also relates to PHP

Starting to get things sorted with the new look business site now, so time to catch up on some blogging. I am going to start off by looking at the common scenario of creating next and previous links for a data result set.

A typical application of the next and previous links is in a News Archive. These links offer intrinsic usability benefits presenting the user with less steps to view articles (eg they do not have to jump back to an index to find the link for the next article). However, selecting neighbouring items from a MySQL database is not that simple.

The obvious solution is to select the item with a unique key one less/more than the current record. But this has two drawbacks. Firstly it requires integers as unique keys, which is not always the case. It also assumes that there is a continuous sequence of unique keys with no breaks. This is rarely the case in a content management system, where some items might be deleted.

For this example I am going to assume that the unique key is not integer based, and instead focus on the timestamp at which each entry was added. So rather than adding or subtracting directly from a unique key, all that is required are a couple of comparative selection queries, having firstly established the timestamp for the current record.


SELECT @now := added 
       FROM table 
       WHERE uid = {x};
SELECT @prev := uid 
       FROM table 
       WHERE added < @now 
       ORDER by added DESC 
       LIMIT 0, 1;
SELECT @next := uid 
       FROM table 
       WHERE added > @now 
       ORDER by added ASC 
       LIMIT 0, 1;

This in itself is one solution, if, rather than creating SQL variables, the required data was just extracted directly in the second and third statements. But this will require two separate calls to the database abstraction layer from the API, with two sets of result data to manipulate. The code can be made more manageable at each layer if this is reduced to a single call. Having acquired the unique ID of the next and previous item, it is merely a case of collecting the data.


SELECT IF(uid = @prev,'p','n') as position, uid, title 
       FROM table 
       WHERE (uid = @prev) OR (uid = @next);

In this statement I have created an additional result field position which will be set to p or n for the previous and next records respectively. This is because I generally use PEAR::DB as my database abstraction with a set of custom functions. In this scenario, the first three statements can be executed in the standard way, $dbh->query($query), and the final statement called via $dbh->getAssoc($query). This will return an associative array of a maximum two items with keys of either n or p and the corresponding data.

Of course there is always more than one way of doing things, and I feel the logic of this alternative approach quite elegant, however, with limited permissions on my current servers, I can only test this on my local machine.


SELECT @row := 0;
DROP TABLE IF EXISTS temptable;
CREATE TEMPORARY TABLE temptable 
       SELECT @row := @row + 1 as rownum, table.* FROM table
       ORDER BY added DESC;
SELECT @current := rownum 
       FROM temptable 
       WHERE uid = {x};
SELECT IF(rownum < @current, 'n', 'p') as position, uid, title
       FROM temptable 
       WHERE (rownum = @current - 1) OR (rownum = @current + 1)

The code utilises the CREATE TEMPORARY TABLE statement to create a replica of the data that is being examined, and adding an additional field to the temporary table to act as a unique integer identifier (rownum). This resolves the problem outlined initially regarding the datatype of unique indexes, and sequence gaps.

There are no doubt copious other solutions to this problem. Of course an obvious choice is to do the work on the API layer. For example, the entire dataset could be collected and then PHP's array functions used to manipulate the dataset and extract the required information. However, I have found in the past this can get quite kludgey! Doing the work at the Database layer itself does not (appear to) harm the performance of the database, and helps keep all layers in the application that bit purer.

Posted on Jan 30, 2004 at 00:45:22. [Comments for Selecting Next And Previous- 4]

MySQL Summarizing

Also relates to Databases

Another little SQL snippet here for MySQL. This one is useful when summarizing <select> data from an HTML form (perhaps stored as an ENUM datatype).


SELECT @total := COUNT(*),
       @offices := COUNT(IF(type='offices',1,NULL)),
       @units := COUNT(IF(type='units',1,NULL))
       FROM `tblunits`;                   
SELECT CONCAT(@total, ' [', 
              @offices, ' office', 
              IF(@offices != 1,'s',''), ', ', 
              @units, ' unit', 
              IF(@units != 1, 's',''), 
              ']');                    

Once again these two queries utilise variables, and make use of conditional testing within a COUNT function to calculate the summaries. The CONCAT could just as easily have been done in PHP, but I chose to use the PEAR::Package::DB getOne() call. Short and simple!

Posted on Nov 14, 2003 at 03:42:03. [Comments for MySQL Summarizing- 0]

Using MySQL Variables

Also relates to Databases

Here is a code snippet demonstrating the use of MySQL variables in a set of queries. The problem was to find the longest standing entry in a table for a custom CMS I am building. The date added is a timestamp datatype.


SELECT @min := MIN(added) FROM tbldirectory;
SELECT @interval := UNIX_TIMESTAMP(NOW()) - UNIX_TIMESTAMP(added),
       @name := name 
       FROM tbldirectory WHERE added = @min;
SELECT @weeks := MOD(FLOOR(@interval / (7 * 24 * 60 * 60)),52) as weeks,
       @years:= FLOOR(@interval / (7 * 24 * 60 * 60 * 52)) as years,
       CONCAT(@years, ' year', IF (@years!=1,'s',''), ' and ',
              @weeks, ' week', IF (@weeks!=1,'s','')) as duration,
       @name as name;

I wanted to display the length of time in years and weeks, so once a variable (@interval) was set to the duration in seconds, I used some basic arithmetic with the FLOOR and MOD functions to get the appropriate values. To keep the final select query readable I stored the week and year calculations in variables and then used the CONCAT function to build the required string.

Using the variables and functions in MySQL queries is a great way to reduce unnecessary formatting of the data within the API, and now the query result is ready to go straight into the template. The MySQL Control Centre has a useful SQL editor for building complex query strings step by step.

Posted on Nov 10, 2003 at 02:22:21. [Comments for Using MySQL Variables- 0]

MySQL Date Datatypes

Also relates to Databases

Here is a good tutorial on Date Arithmetic with MySQL. It demonstrates a small part of the depth of the Date API in MySQL which can dramtically cut down on uneccessary user defined date functions in PHP and the like. Simon Willison has also written a brief follow up to this on Storing Dates in MySQL and the MySQL Unix conversion functions, which allow a DateTime datatype to be readily converted to the native Unix timestamp format for PHP processing.

This has got me thinking briefly about the best choice for MySQL storage between Timestamp and DateTime. For this blog, I have used the Timestamp datatype in my schemas for all date fields. Both datatypes have access to the full set of date manipulation functions in the MySQL API, and there is no difference in memory use. So, example, for the dates on this blog I have the following call as part of a MySQL SELECT statements:

SELECT […] DATE_FORMAT(added, '%e %M %Y - %T') as create_time FROM […].

  • Timestamp can be a useful tool for write once data where a log time is required. The field can be excluded from a MySQL DML UPDATE statement, and will be autocompleted as the statement is executed.
  • Where a schema definition has both an added and amended field, placing the amended field first will ensure it is automatically updated each time a DML statement is executed. However, when the data is first entered, the added field must be set specifically using NOW() or a NULL value.
  • A Timestamp value can also be explicitly set in an INSERT or UPDATE statement.
  • For a schema with a predefined date range of historical significance, the Date or DateTime datatype is a more reliable choice - see this comment on Simon's blog.
  • A Date datatype is clearly a better choice for a fixed date, eg an appointment or anniversary.

Posted on Jul 13, 2003 at 23:54:59. [Comments for MySQL Date Datatypes- 2]

Breadcrumbs Trail

[ Home ] -> TW Blog -> MySQL
Site Map

The Severn Solutions website achieves the following standards:

[ XHTML 1.0 ] [ CSS 2 ] [ WAI AA ] [ Bobby AA ]

Page compiled in 0.008 seconds