我想在SQL(MySQL)中编写一个存储过程来计算第二和第三四分位数的平均值。
换句话说,我有记录用于测量URL加载所需的时间。记录是(id,url,time),它们是每个URL的许多度量。我要做的是每个URL删除最低和最高25%(即较低和较高四分位数)并计算剩余25%-75%加载时间的平均值。并将其存储在另一张表中。
我在MS SQL中看到了一些这样的例子,看起来相对简单。但我必须使用MySQL:
我到目前为止:
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中考虑它会有更好的整体性能。我非常感谢你的帮助。
答案 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 ;
主要观点是:
(rowcount % 4) != 0
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;