【问题标题】:Select N random records per group每组选择 N 个随机记录
【发布时间】:2011-03-16 07:54:51
【问题描述】:

大家好,周日好。 我需要从每组中随机选择 N 条记录。

从Quassnoi的查询开始

http://explainextended.com/2009/03/01/selecting-random-rows/

为了选择X个随机记录我写了这个存储过程

delimiter //
drop procedure if exists casualiPerGruppo //
create procedure casualiPerGruppo(in tabella varchar(50),in campo varchar(50),in numPerGruppo int)
comment 'Selezione di N record casuali per gruppo'
begin
declare elenco_campi varchar(255);
declare valore int;
declare finite int default 0;
declare query1 varchar(250);
declare query2 varchar(250);
declare query3 varchar(250);
declare query4 varchar(250);
declare cur_gruppi cursor for select gruppo from tmp_view;
declare continue handler for not found set finite = 1;

drop table if exists tmp_casuali;
set @query1 = concat('create temporary table tmp_casuali like ', tabella);
prepare stmt from @query1;
execute stmt;
deallocate prepare stmt;

set @query2 = concat('create or replace view tmp_view as select ',campo,' as gruppo from ',tabella,' group by ',campo);
prepare stmt from @query2;
execute stmt;
deallocate prepare stmt;

open cur_gruppi;
mio_loop:loop
fetch cur_gruppi into valore;
    if finite = 1 then
        leave mio_loop;
    end if;

set @query3 = concat("select group_concat(column_name) into @elenco_campi
              from information_schema.columns
                      where table_name = '",tabella,"' and table_schema = database()");
prepare stmt from @query3;
execute stmt;
deallocate prepare stmt;

set @query4 = concat('insert into tmp_casuali select ',
             @elenco_campi,' from (
                     select  @cnt := count(*) + 1,
                     @lim :=', numPerGruppo,
                         ' from ',tabella,
                     ' where ',campo,' = ', valore,
                     ' ) vars
                     straight_join
                    (
                    select  r.*,
                    @lim := @lim - 1
                    from ', tabella, ' r
                    where   (@cnt := @cnt - 1)
                    and rand() < @lim / @cnt and ', campo, ' = ', valore ,
                    ') i');

prepare stmt from @query4;
execute stmt;
deallocate prepare stmt;

end loop;
close cur_gruppi;
select * from tmp_casuali;
end //
delimiter ;

我这样回忆是为了给你一个想法:

create table prova (
id int not null auto_increment primary key,
id_gruppo int,
altro varchar(10)
) engine = myisam;


insert into prova (id_gruppo,altro) values 
(1,'aaa'),(2,'bbb'),(3,'ccc'),(1,'ddd'),(1,'eee'),(2,'fff'),
(2,'ggg'),(2,'hhh'),(3,'iii'),(3,'jjj'),(3,'kkk'),(1,'lll'),(4,'mmm');

call casualiPerGruppo('prova','id_gruppo',2);

我的问题是 Quassnoi 查询,尽管性能非常好,但在大型 recorset 上甚至需要 1 秒。所以如果我在我的sp中应用它几次,总时间会增加很多。

你能建议我一个更好的方法来解决我的问题吗? 提前致谢

编辑。

create table `prova` (
  `id` int(11) not null auto_increment,
  `id_gruppo` int(11) default null,
  `prog` int(11) default null,
  primary key (`id`)
) engine=myisam charset=latin1;

delimiter //
drop procedure if exists inserisci //
create procedure inserisci(in quanti int)
begin
declare i int default 0;
while i < quanti do
insert into prova (id_gruppo,prog) values (
                        (floor(1 + (rand() * 100))),
                        (floor(1 + (rand() * 30)))
                       );
set i = i + 1;
end while;
end //

delimiter ;

call inserisci(1000000);

@Clodoaldo: 我的存储过程

call casualipergruppo('prova','id_gruppo',2);

给我 200 条记录,大约需要 23 秒。您的存储过程不断给我错误代码:1473 即使我将 varchar 值增加到 20000,选择的嵌套级别太高。我不知道查询中涉及的联合是否有任何限制。

【问题讨论】:

    标签: mysql random


    【解决方案1】:

    我从程序中删除了 tabella 和 campo 参数只是为了更容易理解。我相信你可以把它们带回来。

    delimiter //
    drop procedure if exists casualiPerGruppo //
    create procedure casualiPerGruppo(in numPerGruppo int)
    begin
    declare valore int;
    declare finite int default 0;
    declare query_part varchar(200);
    declare query_union varchar(2000);
    declare cur_gruppi cursor for select distinct id_gruppo from prova;
    declare continue handler for not found set finite = 1;
    
    create temporary table resultset (id int, id_gruppo int, altro varchar(10));
    
    set @query_part = 'select id, id_gruppo, altro from (select id, id_gruppo, altro from prova where id_gruppo = @id_gruppo order by rand() limit @numPerGruppo) ss@id_gruppo';
    set @query_part = replace(@query_part, '@numPerGruppo', numPerGruppo);
    set @query_union = '';
    
    open cur_gruppi;
    mio_loop:loop
    fetch cur_gruppi into valore;
        if finite = 1 then
            leave mio_loop;
        end if;
    
    set @query_union = concat(@query_union, concat(' union ', @query_part));
    set @query_union = replace(@query_union, '@id_gruppo', valore);
    
    end loop;
    close cur_gruppi;
    
    set @query_union = substr(@query_union, 8);
    set @query_union = concat('insert into resultset ', @query_union);
    
    prepare stmt from @query_union;
    execute stmt;
    deallocate prepare stmt;
    select * from resultset order by id_gruppo, altro;
    drop table resultset;
    
    end //
    delimiter ;
    

    【讨论】:

    • @nick 你试过这个吗?有什么问题吗?
    • 嗨克洛多阿尔多。首先感谢您为我付出的时间。 +1 只是为了它。抱歉我的回复延迟。我在一张有 100 万条记录和 100 个不同组的桌子上尝试你的 sp。 sp 给我这个错误“选择的嵌套级别太高”
    • 我的 sp 很慢(大约 25 秒),但至少返回了正确的结果。
    • 就像好奇一样,它是否适用于数据的子集?你有多少内存?同样对于 100 个组,查询字符串的长度至少为 15,000 个字符。所以这是必要的:声明 query_union varchar(20000)。
    • 嗨。我已经编辑了我的帖子。是的。如果我修改我的 sp“inserisci”以使用 10 个不同的 id_gruppo 而不是 100 填充我的表,那么您的 sp 可以正常工作并正确给我 20 条记录。它不适用于大量不同的组。
    【解决方案2】:

    哇。这是做一些非常简单的事情的复杂方法。试试这个:

    假设您有连续的 id(否则您可能没有行)。

    create view random_prova as
    select * from prova
    where id = (select min(id) from prova) + 
        floor(RAND(0) * (select max(id) - min(id) from prova));
    

    这将为您提供 1 个随机行。

    要获得多行,要么在存储过程或服务器程序中循环,直到获得足够的行,要么以编程方式创建一个使用联合的查询。 例如,这将为您提供 3 个随机行:

    select * from random_prova
    union
    select * from random_prova
    union
    select * from random_prova;
    

    请注意,使用 RAND(0) 而不是 RAND() 意味着每次调用都会获得不同的随机数。 RAND() 将为单个语句中的每个调用提供相同的值(因此将 RAND() 与 union 一起使用不会为您提供多行)。

    使用 union 有一些缺点 - 可能会偶然获得同一行两次。以编程方式调用它直到获得足够的行更安全。

    为了提供更好的性能,请使用 java 之类的东西来随机选择 id 进行简单查询,例如

    select * from prova where id in (...)
    

    并让 java(或 perl 或其他)使用随机 id 填充列表 - 您将避免每次都必须获取 id 范围的低效率。

    如果您的 id 不是连续的,请发布 - 有一种有效的方法,但我的解释很长。

    【讨论】:

    • 您可以简单地从 random_prova 的列表中排除选定的行。这样做的一种方法是将选定的值推送到数组中。排除该数组中的那些。但也可以使用其他方法。
    • 感谢您的回复。也许我错了,但在我看来,您并不认为我需要每个组的 N 条不同记录。可能会出现 id 不连续的情况。我正在寻找一个不涉及任何编程语言的 sql 解决方案。 @赛义德。我不知道如何有效地实施您的建议。
    猜你喜欢
    • 2016-05-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-12-02
    相关资源
    最近更新 更多