在日常开发中,我们通常需要在指定的一张表或多张表中随机的选择一条记录作为显示。如:广告展示(从多个广告商的广告中随机选择一个)、新闻推荐等等
常见方法如下:
1、随机排序后获取第一行
获取随机记录的常见SQL做法,就是对查询结果进行随机排序,然后获取第一行。如:
select * from bugs order by rand() limit 1;
这种方法在数据据量非常小的时候,性能不会有什么大的影响。如果数据量非常庞大时,这将是致命的(因为你需要遍历整个表进行排序)。如果采用随机函数的返回值进行排序,则排序就跟记录本身没有任何关系,则每次排序后的数据是不一样的,这样数据的排序就不能被复用。使用随机函数排序,则不能使用数据库的索引进行排序,索引排序是非常快的。不使用索引排序的后果就是不得不遍历整张表(这是非常慢的)。随机排序的另一个问题就是,好不容易对整个数据进行了排序,但是只用了一条数据,太浪费了。
2、在表格ID最大和最小值之间选择一个随机数
通过数据库或者程序中的随机函数,生成一个指定表ID最小值到最大值之间的随机数。然后将显示这个随机数指定的记录。如下:
select b1.* from bugs as b1 join ( select ceil(rand()*(select max(bug_id) from bugs)) as rand_id ) as b2 on (b1.bug_id = b2.rand_id);
注意:要求主键值必须从1开始到最大值,且是连续的。如果漏掉了某些值,则可能获取不到数据(缺点)。如Bugs表:
bug_id(Bug编号) | name(Bug名称) |
1 | BugName01 |
2 | BugName02 |
4 | BugName04 |
6 | BugName06 |
随机获取Bugs表中的一条记录:
select ceil(rand()*(select max(bug_id) from bugs));
有rand() * 6 ==>,如果rand()=0.9,则0.9*6=5.4,向下取整后等于5,从上面的表中可以看出bug_id=5的记录不存在。
3、随机找到的下一个有效值
与上面的方案类似,但解决了表中在最小值和最大值之间存在缝隙的情况,这个查询会返回它随机找到的第一个有效的值。如下:
select b1.* from bugs as b1 join ( select ceil(rand()*(select max(bug_id) from bugs)) as rand_id ) as b2 where b1.bug_id>=b2.bug_id order by b1.bug_id limit 1;
这个方法解决了没有随机数对应的主键值,即方法2的问题。但是在缝隙后面的第一列的选中几率会随着缝隙的增大而增大。如下:
bug_id(Bug编号) | name(Bug名称) |
1 | BugName01 |
2 | BugName02 |
100 | BugName001 |
在上面这章表中随机数字位于3到100之间的都将选中bug_id=100的这条记录,那么bug_id=100这条记录的选中概率将非常高。
注意:当表中数据向ID值之间的缝隙不大并且每个值要被等概率选中的重要性不高时可以考虑这种方案。
4、通过编程语言实现
你可以在程序中先获取所有的主键值,然后在主键值中随机选择一个,再用这个随机选中的主键值去检索信息。如:
String sql = select bug_id from bugs; // 获取所有的Bugs编号,且存放到List中 List<Integer> bugIdList = getBugsIdList(sql); // 从列表中中随机选择一个Bug编号 int bugId = bugIdList(random(bugIdList.size())); // 根据随机bugId值检索信息 String bugSQL = "select * from bugs where bug_id=" + bugId ;
存在的问题:
(1)获取所有的bugs表的主键值,可能超出内存处理极限(有非常多的记录)。
(2)查询必须进行两次数据库操作,一次获取所有key,另一次获取随机取出bugId的信息。如果查询太复杂,则将非常耗时。
5、通过下标来实现
前面几种解决方案中都存在这样那样的问题,那么这种解决方案就避免了上面几种方案中存在的问题。原理如下:计算总的数据行数,随机选择0到总行数之间的一个值,然后利用这个值作为位移来获取随机行(orcale数据库rownum、mysql数据库的limit可以实现)。如(Oracle):
select * from ( select a.*, rownum n_index from bugs a ) t where t.n_index=( select ceil(rand()*select count(*) from bugs );
使用这种方案,使用了非SQL标准的语法,如rownum这是orace独有的、MySQL的limit等。当你不能保证主键是连续的,并且需要每行数据有相同的选中概率时,可以用这个方案。