tags:

views:

42

answers:

3

Considering the following table:

someId INTEGER #PK
ageStart TINYINT(3)
ageEnd TINYINT(3)
dateBegin INTEGER
dateEnd INTEGER

Where dateBegin and dateEnd are dates represented as days since 1800-12-28...

And considering some sample data:

someId | ageStart | ageEnd | dateStart | dateEnd
------------------------------------------------
   203 |       16 |     25 |     76533 |   76539   \
   506 |       16 |     25 |     76540 |   76546    adjacent rows
   384 |       16 |     25 |     76547 |   76553   /
   342 |       16 |     25 |     76563 |   76569   \
   545 |       16 |     25 |     76570 |   76576    adjacent rows
   764 |       16 |     25 |     76577 |   76583   /

(There would be arbitrary rows mixed in off course, I just want to illustrate 2 relevant rowsets)

Is it possible to find adjacent rows for a given age category (ageStart to ageEnd) without a stored procedure? The criteria for adjacency is: dateStart is 1 day after dateEnd of the previous found row.

For instance, given the above sample data, if I were to query it with the following parameters:

ageStart = 16
ageEnd = 25
dateStart = 76533

I would like it to return me the rows 1, 2 and 3 of the sample data, since their dates are adjacent (dayStart is next day of previous row's dateEnd).

ageStart = 16
ageEnd = 25
dateStart = 76563

...would give me rows 4, 5 and 6 of the sample data

A: 

Well, you can generate a result-set ordered in a specific way and use LIMIT, to get only first record from it.

For example, get the next record by dateEnd in the list:

SELECT *
FROM `table`
WHERE `dateEnd` > '76546'
ORDER BY `dateEnd`
LIMIT 1

You will get:

384 |       16 |     25 |     76547 |   76553

For a previous row:

SELECT *
FROM `table`
WHERE `dateEnd` < '76546'
ORDER BY `dateEnd` DESC
LIMIT 1

You will get:

   203 |       16 |     25 |     76533 |   76539

I doubt that it can be done with just one query...

Silver Light
+1  A: 

You can consider your data to be in a parent-child relationship: a record is a child of a (parent) record if the child's startDate equals the parent's endDate + 1. For hierarchical data (with parent-child relationships), the nested sets model allows you to query the data without stored procedures. You can find a brief description of the nested sets model here:

http://dev.mysql.com/tech-resources/articles/hierarchical-data.html

The idea is to number your records in a clever way so that you can use simple queries instead of recursive stored procedures.

While it is very easy to query hierarchical data stored in this way, some care is required when adding new records. Adding new records in a nested sets model requires updates of existing records. This may or may not be acceptable in your use case.

titanoboa
Good idea, but I have no privileges to alter the database structure. If I'm correct you're proposing to add lft and rgt columns right? As a side note, I keep postponing diving into nested set modeling, because it feels so counter intuitive for me. I guess the time is still not here to start diving into it for me yet. ;-)
fireeyedboy
Exactly, you need additional columns to use the nested set model. If you can't alter your table structure, you can't use this approach.
titanoboa
Alright titanoboa, thanks for the suggestion though. Sounded like a good approach if I had the privileges. +1
fireeyedboy
+1  A: 

Probably not efficient if lots of data into your table but try this:

SELECT b.*
FROM
(SELECT @continue:=2) init,
(
 SELECT * 
 FROM ageTable 
 WHERE ageStart=16 AND 
       ageEnd=25   AND 
       dateStart=76533
) a 
  INNER JOIN (
   SELECT *
   FROM ageTable
   ORDER BY dateStart
  ) b ON (
    b.ageStart=a.ageStart AND
    b.ageEnd=a.ageEnd     AND
    b.dateStart>=a.dateStart
  ) 
  LEFT JOIN ageTable c ON (
    c.dateStart=b.dateEnd+1 AND
    c.ageStart=b.ageStart   AND 
    c.ageEnd=b.ageEnd
  )
WHERE 
 CASE
  WHEN @continue=2 THEN
   CASE
    WHEN c.someId IS NULL THEN
     @continue:=1
    ELSE
     @continue
   END
  WHEN @continue=1 THEN
   @continue:=0
  ELSE
   @continue
 END
Patrick
Holy moly! This is no child's play query. This is bloody briljant! It will probably take me a good hour to completely understand what exactly is going on in the query, but it looks like it is returning the data that I need. By the way: the data in the table is acceptable for this query I believe. There are about max 2000 rows at any given time in the table, so this shouldn't be a problem. Awesome query. Thank you.
fireeyedboy
The first query (init) is just to initialize the user variable @continue to 2. The second (a) is for insuring that there is at least one row for you condition, the third (b) is to get all the data sorted and starting from the startDate requested, the left join (c) insure that the last row not verify the condition will be return but with null values. The case is there to stop the query return row after the last adjacent row is found. You can see what is going on removing the where case, and doing a select a.*,b.*,c.* ;)
Patrick
Thanks for clarifying the query. This query has given me lot's of new tricks to play with. Especially the variable initialization. I always wondered how to use variables in non SP queries, but never got around to investigate it. If I could give you more upvotes, I would. Thanks again Patrick.
fireeyedboy
You 're welcome nice if it helps :)
Patrick