MySQL复杂的第n行选择

时间:2015-03-21 17:01:01

标签: mysql

我有两张桌子:

Types                   Data
+----+----------+       +-------+-------+
| id |   name   |       |  id   | type  |
+----+----------+       +-------+-------+
| 1  | name1    |       | 1     | 1     |
| 2  | name2    |       | 2     | 5     |
| 3  | name3    |       | 3     | 7     |
| 4  | name4    |       | 4     | 4     |
| 5  | name5    |       | 5     | 2     |
| 6  | name6    |       | 6     | 6     |
| 7  | name7    |       | 7     | 3     |
| .. | ..       |       | 8     | 5     |
+----+----------+       | 9     | 5     |
                        | 10    | 4     |
                        | 11    | 1     |
                        | 12    | 2     |
                        | 13    | 6     |
                        | 14    | 5     |
                        | 15    | 2     |
                        | ...   | ...   |
                        | 1...? | 1...? |
                        +-------+-------+

数据表非常大,它包含我需要选择1000行的数百万行,但结果必须来自整个表,所以每第n行选择一次。我使用How to select every nth row in mySQL starting at n的答案完成了这个,但是,我需要为它添加一些逻辑,我需要一个选择查询来选择所有类型的每一行。我想这听起来很复杂,所以我会试着描述一下我想要实现的目标:

假设有7种类型和数据表有7M行0.5M行用于类型1,2,3,1.5M行用于类型4,5,6,7(只是清晰的间隔现在可能等于所有的类型)。

我需要1000个包含相同数量类型的记录,所以如果我有7种类型,每种类型都可以出现在结果集ROUND(1000/7)中,这将等于每种类型142个记录,所以我需要从Data中选择142个类型表;

对于包含0.5M行的类型1,2,3,其将是ROUND(0.5M / 142),等于每个第3521行; 对于类型4,5,6,7,其中包含1.5M行,这些行将是ROUND(1.5M / 142),等于每个第10563行;

所以结果看起来像这样:

Result
+-------+------+
|  id   | type |
+-------+------+
| 1     |    1 |
| 3522  |    1 |
| 7043  |    1 |
| ..    |   .. | 
| ..    |    2 |
| ..    |    2 |
| ..    |   .. | 
| ..    |    3 |
| ..    |    3 |
| ..    |   .. |
| ..    |    4 |
| ..    |    4 |
| ..    |   .. |
| ..    |    5 |
| ..    |    5 |
| ..    |   .. |
| ..    |    6 |
| ..    |    6 |
| ..    |   .. |
| ..    |    7 |
| ..    |    7 |
| ..    |   .. |
+-------+------+

我可以简单地在任何编程语言中使用多个查询从Data表中返回每个类型的计数,然后在进行数学选择时只选择单个类型。

但我想纯粹在MySQL中这样做,使用尽可能少的查询。

修改

我会尝试更详细地解释一下我用实例来实现的目标。

我有1437823行的表。表模式如下所示:

+---------+----------+------+-----+---------+----------------+
| Field   | Type     | Null | Key | Default | Extra          |
+---------+----------+------+-----+---------+----------------+
| id      | int(11)  | NO   | PRI | NULL    | auto_increment |
| type    | int(11)  | NO   |     | NULL    |                |
| counter | int(11)  | NO   |     | NULL    |                |
| time    | datetime | NO   |     | NULL    |                |
+---------+----------+------+-----+---------+----------------+

该表类型统计信息为:

+------+-----------+
| Type | Row Count |
+------+-----------+
|    1 |    135160 |
|    2 |    291416 |
|    3 |    149863 |
|    4 |    296293 |
|    5 |    273459 |
|    6 |    275929 |
|    7 |     15703 |
+------+-----------+

(P.S。类型计数可以及时改变。)

假设我需要从时间间隔中选择样本数据,在问题的第一个版本中,我省略了时间,因为我认为它是无关紧要的,但现在我认为在订购提高性能时它可能有一些意义。

所以无论如何我需要选择大约1000行样本,其中每种类型都有相同的数据块,因此最终结果的统计结果如下所示: 我选择了7行类型的1000行,所以ROUND(1000/7)=每行143行;

+------+-----------+
| Type | Row Count |
+------+-----------+
|    1 |       143 |
|    2 |       143 |
|    3 |       143 |
|    4 |       143 |
|    5 |       143 |
|    6 |       143 |
|    7 |       143 |
+------+-----------+

所以现在我需要在时间间隔内的相等间隙中为每种类型选择143行​​。所以对于单一类型,它看起来像这样:

SET @start_date := '2014-04-06 22:20:21';
SET @end_date := '2015-02-20 16:20:58';
SET @nth := ROUND(
    (SELECT COUNT(*) FROM data WHERE type = 1 AND time BETWEEN @start_date AND @end_date) / ROUND(1000 / (SELECT COUNT(*) FROM types))
);

SELECT r.*
  FROM (SELECT * FROM data WHERE type = 1 AND time BETWEEN @start_date AND @end_date) r
 CROSS
  JOIN ( SELECT @i := 0 ) s
HAVING ( @i := @i + 1) MOD @nth = 1

统计:

+------+-----------+
| Type | Row Count |
+------+-----------+
|    1 |       144 |
+------+-----------+

这个查询会给我带来可接受性能的所需结果,但是我需要对每种类型进行查询,这会降低性能并且稍后需要将结果连接成单个数据集,因为这是我进一步处理所需的,所以我会喜欢在单个查询中执行此操作或至少获取单个结果集。

P.S。只要类型块相等,我就可以容忍结果集中的行计数偏差。

2 个答案:

答案 0 :(得分:0)

你想要的是一个分层的样本。获得分层样本的一个好方法是按类型对行进行排序并分配一个序号 - 编号不必为每种类型重新开始。

然后,您可以通过获取每个第n个值获得1000行:

select d.*
from (select d.*, (@rn := @rn + 1) as rn
      from data d cross join
           (select @rn := 0) vars
      order by type
     ) d
where mod(rn, floor( @rn / 1000 )) = 1;

注意:最后的比较是n行中的1行接近1000.根据值的数量,它可能会被一两个关闭。

编辑:

哎呀,上面做了一个分层样本,它与数据中类型的原始分布相匹配。要获得每个组的相同计数,请随机枚举它们并为每个组选择第一个“n”:

select d.*
from (select d.*,
             (@rn := if(@t = type, @rn + 1,
                        if(@t := type, 1, 1)
                       )
             ) as rn
      from data d cross join
           (select @rn := 0, @t := -1) vars
      order by type, rand()
     ) d cross join
     (select count(*) as numtypes from types) as t
where rn <= 1000 / numtypes;

答案 1 :(得分:0)

这应该做你想要的(在包含TYPE=1的100行,包含TYPE=2的200行,包含TYPE=3的300行,包含TYPE=4的400行的表格上进行测试; 10中的值_c / 10,我得到40行,每行10个)。请检查性能,因为我显然使用的样本表比你真正的样本表少。

select * from
    (select
        @n := @n + 1 _n,
        _c,
        data.*
    from
        (select
            type _t,
            count(*) _c
        from data
        group by type) _1
    inner join data on(_t = data.type)
    inner join (select @n := 0) _2 order by data.type) _2
where mod(_n, floor(_c / 10)) = 0
order by type, id;

虽然每个组的编号相同,但不能保证从每个组中获取完全相同的编号,因为floor(_c / 10)显然存在舍入不准确的情况。