2011-08-09 48 views
13

Chcę zapisać przechowywany proc w SQL (MySQL), aby obliczyć średnią drugiego i trzeciego kwartyl.Obliczyć średnią 2,3 kwartyl w SQL

Innymi słowy Mam zapisy pomiarów dla długości czasu ładowania adresu URL. Rekordy są (identyfikator, URL, czas) i są one wiele pomiarów dla każdego adresu URL. Co staram się zrobić, to dla każdego adresu URL usunąć najniższe i najwyższe 25% (tj. Dolny i górny kwartyl) i obliczyć średnią z pozostałych 25% -75% czasów ładowania. I przechowuj to w innym stole.

Widziałem kilka przykładów tego dla MS SQL i wydawało się stosunkowo łatwe. Ale muszę używać MySQL gdzie:

  • klauzula LIMIT nie obsługuje procentach (bez analogowych wybrać top 25%)
  • klauzula LIMIT nie obsługuje swoje argumenty być zmienne (tylko stałe)
  • funkcje nie obsługują dynamiczny SQL (np przygotowanie i przeprowadzenie)

i mam tak daleko, jak tutaj:

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; 

Mogę napisać to w PHP, ale myślę, że SQL będzie miał znacznie lepszą ogólną wydajność. Będę wdzięczny za pomoc.

+2

"SQL For Smarties" Joe Celko zawiera rozdział o statystyki (tryb, mediana, wariancja, itp.) Warto ceny zakupu. –

Odpowiedz

2

Spójrz na odpowiedź i komentarz przez @Richard aka cyberkiwi w 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

jak chodzi?

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

1. To nadal jest dynamiczny SQL i nie różni się od tego, co napisałem 2. Ta linia daje błąd składni, mimo że wygląda na to, że jest OK – munch

0

Spójrz na ten doskonały przykład obliczania percentyli za pomocą MySQL. Użyłem tego z wielkim sukcesem na niektórych dość dużych zestawach danych.

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

Zanotuj części dotyczącej group_concat_max_len - to jest bardzo ważne. Ustawienie tej wartości na maksymalną dopuszczalną wartość - czyli ustawienie maksymalnego rozmiaru pakietu, zapewni, że jeśli tworzony łańcuch stanie się zbyt duży, otrzymasz odpowiedni błąd, a nie tylko ostrzeżenie "obcinanego pola".

SET @@group_concat_max_len := @@max_allowed_packet; 

Co chciałbym zrobić, to skorzystać z tej funkcji, aby obliczyć 25. i 75. percentyla (co można zrobić w jednym zapytaniu), a następnie obliczyć średnie firmy pozostałych danych, uruchamiając drugą kwerendę przed danymi .

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

Nadzieja, że ​​wszystko ma sens, i pomóc z problemem :)

+1

Wiem, że to nie jest procedura składowana i odbywa się w PHP, i jestem pewien że ktoś tam może zapisać go ponownie jako procedurę przechowywaną, która jest w stanie uzyskać 25% ile i 75% ile wartości z pojedynczego zapytania, a następnie zastosować je do głównej filtrowanej kwerendy, ale powyższe jest, jak bym zrobił to. Następnie pozwala wykorzystać wartości 25% ile i 75% ile w innych obszarach kodu, np. podczas tworzenia niektórych nagłówków lub informacji o legendzie dla tabeli danych lub wykresu, który budujesz;) –

0

Dlaczego nie można po prostu użyć jednego zapytania w ten sposób:

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

To niekoniecznie spowoduje, że otrzymasz drugi i trzeci kwartyl. Jeśli czasy są równomiernie rozłożone, będzie blisko, ale to nie robi tego, o co prosi. – Dason

1

można utworzyć wartości kwartylu przez korzystając IF ustawić je do zera, jeśli w złym kwartyla:

Załóżmy, surowy tabela danych jest tworzona przez

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; 

(i oczywiście zaludniony).

Załóżmy również dane tabeli kwartyl jest tworzony przez

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; 

(i pozostawić puste).

Następnie procedura zapełnić kwartyle z rawdata wyglądałby

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 ; 

Głównymi punktami są:

  • użyć kursora do cyklu adresy URL (lub dostosować próbki zaakceptować URL jako parametr)
  • Dla każdego adresu URL znajdź łączną liczbę wierszy
  • Wykonaj trochę trywialnej matematyki, aby pominąć środkowe wiersze, jeśli (rowcount % 4) != 0
  • wybranie wszystkich surowców wierszy dla adresu URL, przypisując wartość time jednego z QA-QD, w zależności od liczby rzędów, przypisywanie innego Qx zostanie 0
  • używające tego zapytania jako podkwerendzie do innego, który podsumowuje i normalizuje wartości
  • wykorzystać wyniki tej superquery zaktualizować Ćwiartki stół

testowałem to z 18432 wierszy, url=concat('http://.../',floor(rand()*10)), time=round(rand()*10000) surowców na maszynie 8x1.9GHz i wykończone konsekwentnie w 0.50-0.54sec

Powiązane problemy