【问题标题】:Compute 2,3 quartile average in SQL在 SQL 中计算 2,3 四分位数平均值
【发布时间】:2011-10-23 00:17:30
【问题描述】:

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

【问题讨论】:

  • Joe Celko 的“聪明人的 SQL”有一章是关于统计数据(众数、中位数、方差等)的。值得购买。

标签: mysql sql stored-procedures stored-functions


【解决方案1】:

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

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

【讨论】:

    【解决方案2】:

    如果在错误的四分位数中,您可以使用 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 分配值 0
    • 将此查询用作另一个查询的子查询,对值进行汇总和规范化
    • 使用此超级查询的结果更新四分位数表

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

    【讨论】:

      【解决方案3】:

      这个怎么样?

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

      【讨论】:

      • 1.这仍然是动态 SQL,与我写的 2 没有什么不同。尽管在我看来它看起来不错,但这一行会产生语法错误
      【解决方案4】:

      看看这个用 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 */
      ?>
      

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

      【讨论】:

      • 我知道它不是一个存储过程,而是在 PHP 中完成的,而且我确信那里的某个人可以将它重写为能够获得 25%ile 和 75 的存储过程%ile 单个查询中的值,然后将它们应用于主过滤查询,但以上是我的做法。然后它允许您在代码的其他区域使用 25%ile 和 75%ile 值,例如为您正在构建的数据表或图表创建一些标题或图例信息时;)
      【解决方案5】:

      你为什么不这样使用一个查询:

      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;
      

      【讨论】:

      • 这并不一定能让你获得第二和第三四分位数。如果时间是均匀分布的,那么它会很接近,但因为它不符合要求。
      猜你喜欢
      • 1970-01-01
      • 2013-11-05
      • 1970-01-01
      • 2021-02-01
      • 2021-03-03
      • 2015-04-12
      • 1970-01-01
      • 2015-09-02
      • 1970-01-01
      相关资源
      最近更新 更多