在SQL中计算2,3四分位数平均值

时间:2011-08-09 07:58:14

标签: mysql sql stored-procedures stored-functions

我想在SQL(MySQL)中编写一个存储过程来计算第二和第三四分位数的平均值。

换句话说,我有记录用于测量URL加载所需的时间。记录是(id,url,time),它们是每个URL的许多度量。我要做的是每个URL删除最低和最高25%(即较低和较高四分位数)并计算剩余25%-75%加载时间的平均值。并将其存储在另一张表中。

我在MS SQL中看到了一些这样的例子,看起来相对简单。但我必须使用MySQL:

  • LIMIT子句不支持百分比(无类似选择前25%)
  • LIMIT子句不支持其参数为变量(仅限常量)
  • 函数不支持动态SQL(例如PREPARE和EXECUTE)

我到目前为止:

create procedure G(
  IN val VARCHAR(10)
)
Begin
  select @cnt:=count(*) from test where a=val;
  select  @of:= @cnt /4;
  SELECT @len:= @cnt/2; 
  Prepare stmt from 'select * from test where a="a" LIMIT ?,?';
  execute stmt using @of, @len;
END;

我可以用PHP编写,但在SQL中考虑它会有更好的整体性能。我非常感谢你的帮助。

5 个答案:

答案 0 :(得分:2)

this question中查看@Richard aka cyberkiwi的回答和评论:

Select *
from
(
    SELECT tbl.*, @counter := @counter +1 counter
    FROM (select @counter:=0) initvar, tbl
    ORDER BY ordcolumn
) X
where counter >= (25/100 * @counter) and counter <= (75/100 * @counter);
ORDER BY ordcolumn

答案 1 :(得分:1)

如果在错误的四分位数中,您可以使用IF将它们设置为零来创建四分位数值:

我们假设,原始数据表是由

创建的
DROP TABLE IF EXISTS `rawdata`;
CREATE TABLE `rawdata` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `url` varchar(250) NOT NULL DEFAULT '',
  `time` int(11) NOT NULL,
  PRIMARY KEY (`id`),
  KEY `time` (`time`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

(当然是人口稠密的)。

我们还假设四分位表数据是由

创建的
DROP TABLE IF EXISTS `quartiles`;
CREATE TABLE `quartiles` (
  `url` varchar(250) NOT NULL,
  `Q1` float DEFAULT '0',
  `Q2` float DEFAULT '0',
  `Q3` float DEFAULT '0',
  `Q4` float DEFAULT '0',
  PRIMARY KEY (`url`),
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

(并留空)。

然后从rawdata填充四分位的过程看起来像

DELIMITER ;;

CREATE PROCEDURE `ComputeQuartiles`()
    READS SQL DATA
BEGIN
    DECLARE numrows int DEFAULT 0;
    DECLARE qrows int DEFAULT 0;
    DECLARE rownum int DEFAULT 0;
    DECLARE done int DEFAULT 0;
    DECLARE currenturl VARCHAR(250) CHARACTER SET utf8;
    DECLARE Q1,Q2,Q3,Q4 float DEFAULT 0.0;
    DECLARE allurls CURSOR FOR SELECT DISTINCT url FROM rawdata;
    DECLARE CONTINUE HANDLER FOR NOT FOUND SET currenturl='';

    OPEN allurls;
    FETCH allurls INTO currenturl;
    WHILE currenturl<>'' DO
        SELECT COUNT(*) INTO numrows FROM rawdata WHERE url=currenturl;
        SET qrows=FLOOR(numrows/4);
        if qrows>0 THEN
            -- Only session parameters can be recalculated inside a query,
            -- so @rownum:=@rownum+1 will work, but rownum:=rownum+1 will not.
            SET @rownum=0;
            SELECT
                SUM(IFNULL(QA,0))/qrows, 
                SUM(IFNULL(QB,0))/qrows, 
                SUM(IFNULL(QC,0))/qrows, 
                SUM(IFNULL(QD,0))/qrows
            FROM (
                SELECT 
                    if(@rownum<qrows,time,0) AS QA,
                    if(@rownum>=qrows AND @rownum<2*qrows,time,0) AS QB,
                    -- the middle 0-3 rows are left out 
                    if(@rownum>=(numrows-2*qrows) AND @rownum<(numrows-qrows),time,0) AS QC,
                    if(@rownum>=(numrows-qrows),time,0) AS QD,
                    @rownum:=@rownum+1 AS dummy
                FROM rawdata
                WHERE url=currenturl ORDER BY time
            ) AS baseview
            INTO Q1,Q2,Q3,Q4
            ;
            REPLACE INTO quartiles values (currenturl,Q1,Q2,Q3,Q4);
        END IF;

        FETCH allurls INTO currenturl;
    END WHILE;
    CLOSE allurls;

END ;;

DELIMITER ;

主要观点是:

  • 使用游标循环URL(或调整样本以接受URL作为参数)
  • 对于每个网址,找到总行数
  • 如果(rowcount % 4) != 0
  • ,请做一些简单的数学计算,以省略中间行
  • 选择URL的所有原始行,将time的值分配给QA-QD之一,具体取决于行号,为其他Qx分配值0
  • 将此查询用作另一个查询的子查询,该查询汇总并规范化值
  • 使用此超级查询的结果更新四分位表

我在8x1.9GHz机器上用18432个原始行url=concat('http://.../',floor(rand()*10)), time=round(rand()*10000)测试了它,并且它在0.50-0.54秒内始终如一地完成

答案 2 :(得分:0)

怎么样?

prepare stmt from select concat('select * from test where a="a" LIMIT ',@of,@len);
execute stmt;

答案 3 :(得分:0)

看看这个用MySQL计算百分位数的优秀例子。我在一些非常大的数据集上使用了这个非常成功。

http://planet.mysql.com/entry/?id=13588

请注意与group_concat_max_len相关的部分 - 这非常重要。将此值设置为最大允许值 - 这是您设置的最大数据包大小,将确保如果它构建的字符串太大,您将得到正确的错误,而不仅仅是“截断字段”警告。

SET @@group_concat_max_len := @@max_allowed_packet;

我要做的是使用此函数计算第25和第75百分位数(可以在单个查询中完成),然后通过对数据运行第二个查询来计算剩余数据的平均值。

<?php
$lowVal = /* result of query getting the 25%ile value */;
$highVal = /* result of query getting the 75%ile value */;

$strSQL = "SELECT AVG(`field`) AS myAvg 
             FROM `table` 
             WHERE { your_existing_criteria_goes_here }
                AND `filter_field` BETWEEN '{$lowVal}' AND '{$highVal}';"
/* Run the query and extract your data */
?>

希望一切都有意义,并帮助解决您的问题:)

答案 4 :(得分:0)

为什么不以这种方式使用一个查询:

select url, avg(time)
from mytable A
where time >
       (select min(B.time) + ((max(B.time)-min(B.time))/100*25)
          from mytable B where B.url = A.url)
and time <
       (select max(B.time) - ((max(B.time)-min(B.time))/100*25)
          from mytable B where B.url = A.url)
group by url;