14

我想在 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 中它会有更好的整体性能。我将非常感谢一些帮助。

4

5 回答 5

2

在这个问题中查看@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
于 2011-08-09T08:37:51.883 回答
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 作为参数)
  • 对于每个 URL 查找总行数
  • 做一些简单的数学运算以省略中间行,如果(rowcount % 4) != 0
  • 选择 URL 的所有原始行time,根据行号将值分配给 QA-QD 之一,将值分配给另一个 Qx
  • 将此查询用作另一个查询的子查询,对值进行汇总和规范化
  • 使用这个超级查询的结果来更新四分位数表

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

于 2011-12-15T22:54:09.463 回答
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;
于 2011-11-14T17:24:37.003 回答
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 */
?>

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

于 2011-10-25T08:06:52.357 回答
0

这个怎么样 ?

prepare stmt from select concat('select * from test where a="a" LIMIT ',@of,@len);
execute stmt;
于 2011-08-09T09:06:47.047 回答