开发者

MySQL querying with a dynamic range?

Given the table snippet:

id | name | age

I am trying to form a query that will return 10 people within a certain age range. However, if there are not enough people in that range, I want to extend the range until I can find 10 people.

For instance, if I only find 5 people in a range of 30-40, I would find 5 others in a 25-45 range.

In addition, I would like the query to be able use order by RAND开发者_StackOverflow() or similar, in order to be able to get different results each time.

Is this going beyond what MySQL can handle? Will I have to put some of this logic in the application instead?


UPDATED for performance:

My original solution worked but requuired a table scan. Am's solution is a good one and doesn't require a table scan but its hard-coded ranges won't work when the only matches are far outliers. Plus it requires de-duping records. But combining both solutions can get you the best of both worlds, provided you have an index on age. (if you don't have an index on age, then all solutions will require a table scan).

The combined solution first picks only the rows which might qualify (the desired range, plus the 10 rows over and 10 rows under that range), and then uses my original logic to rank the results. Caveat: I don't have enough sample data present to verify that MySQL's optimizer is indeed smart enough to avoid a table scan here-- MySQL might not be smart enough to weave those three UNIONs together without a scan.

[just updated again to fix 2 embarrassing SQL typos: DESC where DESC shouldn't have been!]

SELECT * FROM
(
  SELECT id, name, age,
     CASE WHEN age BETWEEN 25 and 35 THEN RAND() ELSE ABS (age-30) END as distance
  FROM
  (
     SELECT * FROM (SELECT * FROM Person WHERE age > 35 ORDER BY age DESC LIMIT 10) u1
     UNION
     SELECT * FROM (SELECT * FROM Person WHERE age < 25 ORDER BY age LIMIT 10) u2
     UNION
     SELECT * FROM (SELECT * FROM Person WHERE age BETWEEN 25 and 35) u3
  ) p2
  ORDER BY distance
  LIMIT 10
) p ORDER BY RAND() ;

Original Solution:

I'd approach it this way:

  1. first, compute how far each record is from the center of the desired age range, and order the results by that distance. For all results inside the range, treat the distance as a random number between zero and one. This ensures that records inside the range will be selected in a random order, while records outside the range, if needed, will be selected in order closest to the desired range.
  2. trim the number of records in that distance-ordered resultset to 10 records
  3. randomize order of the resulting records

Like this:

CREATE TABLE Person (id int AUTO_INCREMENT PRIMARY KEY, name varchar(50) NOT NULL, age int NOT NULL);
INSERT INTO Person (name, age) VALUES ("Joe Smith", 26);
INSERT INTO Person (name, age) VALUES ("Frank Johnson", 32);
INSERT INTO Person (name, age) VALUES ("Sue Jones", 24);
INSERT INTO Person (name, age) VALUES ("Ella Frederick", 44);

SELECT * FROM
(
  SELECT id, name, age,
     CASE WHEN age BETWEEN 25 and 35 THEN RAND() ELSE ABS (age-30) END as distance
  FROM Person
  ORDER BY distance DESC
  LIMIT 10
) p ORDER BY RAND() ;

Note that I'm assuming that, if there are not enough records inside the range, the records you want to append are the ones closest to that range. If this assumption is incorrect, please add more details to the question.

re: performance, this requires a scan through the table, so won't be fast-- I'm working on a scan-less solution now...


I would do somthing like this:

select * from (
 SELECT * FROM (select * from ppl_table where age>30 and age<40 order by rand() limit 10) as Momo1
 union
 SELECT * FROM (select * from ppl_table where age>25 and age<40 order by rand() limit 20) as Momo2
) as FinalMomo
limit 10

basically selecting 10 users from the first group and then more from the second group. if the first group doesn't add up to 10, there will be more from the second group.

The reason we are selectong 20 from the second group is because UNION will remove the duplicated values, and you want to have at least 10 users in the final result.

Edit

I added the as aliases from the inner SELECT, and made a separate in the inner SELECTs since MySql doesn't like ORDER BY with UNION

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜