14

SQL (MySQL) でストアド プロシージャを記述して、2 番目と 3 番目の四分位数の平均を計算したいと考えています。

つまり、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別名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;

(そして空のまま)。

次に、生データから四分位数を入力する手順は次のようになります

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 の raw 行でテストしurl=concat('http://.../',floor(rand()*10)), time=round(rand()*10000)たところ、一貫して 0.50 ~ 0.54 秒で終了しました。

于 2011-12-15T22:54:09.463 に答える
0

MySQL でパーセンタイルを計算するこの優れた例を見てください。私はこれを使用して、かなり大きなデータセットで大成功を収めました。

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

に関連するセクションに注意してくださいgroup_concat_max_len- これは本当に重要です。この値を最大許容値 (最大パケット サイズの設定) に設定すると、構築される文字列が大きくなりすぎた場合に、単に「切り捨てられたフィールド」の警告ではなく、適切なエラーが発生するようになります。

SET @@group_concat_max_len := @@max_allowed_packet;

この関数を使用して 25 パーセンタイルと 75 パーセンタイルを計算し (これは 1 回のクエリで実行できます)、データに対して 2 番目のクエリを実行して残りのデータの平均を計算します。

<?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 に答える
0

このように 1 つのクエリを使用してみませんか。

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 に答える