2011-08-09 37 views
13

Tôi muốn viết một proc được lưu trữ trong SQL (MySQL) để tính trung bình của các phần tư thứ hai và thứ ba.Tính trung bình 2,3 phần tư trong SQL

Nói cách khác, tôi có bản ghi để đo lường thời gian tải URL. Hồ sơ là (id, url, thời gian) và chúng là nhiều phép đo cho mỗi URL. Những gì tôi đang cố gắng làm là cho mỗi URL xóa 25% thấp nhất và trên cùng (tức là phần tư dưới và trên) và tính trung bình 25% -75% thời gian tải còn lại. Và lưu trữ cái này vào một cái bàn khác.

Tôi đã xem một số ví dụ về điều này cho MS SQL và dường như tương đối dễ dàng. Nhưng tôi phải sử dụng MySQL nơi:

  • LIMIT khoản không hỗ trợ phần trăm (không tương tự để chọn top 25%)
  • LIMIT khoản không hỗ trợ đối số của nó là các biến (chỉ hằng)
  • chức năng không hỗ trợ SQL động (ví dụ PREPARE và EXECUTE)

và tôi đã như xa như ở đây:

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; 

Tôi có thể viết nó bằng PHP nhưng suy nghĩ trong SQL nó sẽ có hiệu suất tổng thể tốt hơn nhiều. Tôi sẽ đánh giá cao một số trợ giúp rất nhiều.

+2

'SQL For Smarties' của Joe Celko có một chương thống kê (chế độ, trung bình, phương sai, v.v.) Nó đáng giá. –

Trả lời

2

Nhìn vào câu trả lời và bình luận bởi @ Richard aka cyberkiwi trong 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 
0

thế nào về điều này?

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

1. Đây vẫn là SQL động và không khác gì những gì tôi viết 2. Dòng này tạo ra lỗi cú pháp mặc dù nó có vẻ ổn với tôi – munch

0

Hãy xem ví dụ tuyệt vời này về cách tính phần trăm với MySQL. Tôi đã sử dụng điều này với thành công lớn trên một số bộ dữ liệu khá lớn.

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

Hãy chú ý của bộ phận liên quan đến group_concat_max_len - đây là thực sự quan trọng. Đặt giá trị này thành giá trị tối đa cho phép - đó là cài đặt của bạn cho kích thước gói tối đa, sẽ đảm bảo rằng nếu chuỗi được tạo quá lớn, bạn sẽ nhận được lỗi thích hợp thay vì chỉ cảnh báo 'trường cắt ngắn'.

SET @@group_concat_max_len := @@max_allowed_packet; 

gì tôi sẽ làm là sử dụng chức năng này để tính toán ngày 25 và percentiles 75 (có thể được thực hiện trong một truy vấn duy nhất), và sau đó tính toán giá trị trung bình của dữ liệu còn lại của bạn bằng cách chạy một truy vấn thứ hai chống lại các dữ liệu .

<?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 */ 
?> 

Hy vọng rằng tất cả làm cho tinh thần, và giúp đỡ về vấn đề của bạn :)

+1

Tôi biết nó không phải là một thủ tục được lưu trữ và được thực hiện trong PHP, và tôi chắc chắn một người nào đó có thể viết lại nó như là một thủ tục lưu trữ có thể nhận được 25% ile và 75% ile giá trị trong một truy vấn và sau đó áp dụng chúng vào truy vấn được lọc chính, nhưng ở trên là cách tôi sẽ làm nó. Sau đó, nó cho phép bạn sử dụng các giá trị ile 25% và 75% ở các khu vực khác trong mã của bạn, ví dụ: khi tạo một số tiêu đề hoặc thông tin chú giải cho bảng dữ liệu hoặc biểu đồ mà bạn đang xây dựng;) –

0

Tại sao bạn không chỉ cần sử dụng một truy vấn theo cách này:

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; 
+0

Điều đó không nhất thiết phải giúp bạn có phần tư thứ 2 và thứ 3. Nếu thời gian được phân phối đồng đều thì nó sẽ đóng lại nhưng vì nó không làm những gì được yêu cầu. – Dason

1

Bạn có thể tạo các giá trị tứ phân vị bởi sử dụng IF để đặt chúng thành 0 nếu nằm trong phần tư sai:

Giả sử, bảng dữ liệu thô được tạo bởi

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; 

(và ofcourse dân cư).

hãy thử nghĩ xem các dữ liệu bảng tứ phân vị được tạo ra bởi

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; 

(và bỏ trống).

Sau đó, một thủ tục để cư tứ phân vị từ rawdata sẽ trông như thế

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:[email protected]+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:[email protected]+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 ; 

Những điểm chính là:

  • Sử dụng một con trỏ đến chu kỳ các URL (hoặc điều chỉnh mẫu để chấp nhận các URL như tham số)
  • Đối với mỗi URL, hãy tìm tổng số hàng
  • Thực hiện một số phép toán tầm thường để loại bỏ các hàng ở giữa, nếu (rowcount % 4) != 0
  • chọn tất cả các hàng thô cho URL, gán giá trị time cho một trong số QA-QD, tùy thuộc vào số hàng, gán giá trị Qx khác 0
  • Sử dụng truy vấn này dưới dạng truy vấn phụ tổng kết và bình thường hóa các giá trị
  • Sử dụng các kết quả của superquery này để cập nhật bảng tứ phân vị

tôi thử nghiệm này với 18.432 hàng thô, url=concat('http://.../',floor(rand()*10)), time=round(rand()*10000) trên một máy 8x1.9GHz và nó đã hoàn thành một cách nhất quán trong 0.50-0.54sec

Các vấn đề liên quan