2014-07-15 36 views
0

我想分析羣組數據的異常值a。可以說我有數據:羣組數據的異常值

+--------+---------+-------+ 
| fruit | country | price | 
+--------+---------+-------+ 
| apple | UK  | 1 | 
| apple | USA  | 3 | 
| apple | LT  | 2 | 
| apple | LV  | 5 | 
| apple | EE  | 4 | 
| pear | SW  | 6 | 
| pear | NO  | 2 | 
| pear | FI  | 3 | 
| pear | PL  | 7 | 
+--------+---------+-------+ 

讓我們帶梨吧。如果我發現異常的方法是採取25%梨的最高價格和最低的25%,梨的異常會

+--------+---------+-------+ 
| pear | NO  | 2 | 
| pear | PL  | 7 | 
+--------+---------+-------+ 

至於蘋果:

+--------+---------+-------+ 
| apple | UK  | 1 | 
| apple | LV  | 5 | 
+--------+---------+-------+ 

那我想是創建一個視圖,這將顯示所有水果異常值工會的表格。如果我有這個觀點,我可以只分析尾巴,也可以與主表相交,以獲得沒有異常值的表 - 這是我的目標。解決這將是:

(SELECT * FROM fruits f WHERE f.fruit = 'pear' ORDER BY f.price ASC 
LIMIT (SELECT ROUND(COUNT(*) * 0.25,0) 
     FROM fruits f2 
     WHERE f2.fruit = 'pear') 
) 
union all 
(SELECT * FROM fruits f WHERE f.fruit = 'pear' ORDER BY f.price DESC 
LIMIT (SELECT ROUND(COUNT(*) * 0.25,0) 
     FROM fruits f2 
     WHERE f2.fruit = 'pear') 
) 
union all 
(SELECT * FROM fruits f WHERE f.fruit = 'apple' ORDER BY f.price ASC 
LIMIT (SELECT ROUND(COUNT(*) * 0.25,0) 
     FROM fruits f2 
     WHERE f2.fruit = 'apple') 
) 
union all 
(SELECT * FROM fruits f WHERE f.fruit = 'apple' ORDER BY f.price DESC 
LIMIT (SELECT ROUND(COUNT(*) * 0.25,0) 
     FROM fruits f2 
     WHERE f2.fruit = 'apple') 
) 

這會給我的表我想要的,但是代碼LIMIT後似乎並不正確......另一個問題是組數。在這個例子中,只有兩組(梨,蘋果),但在我的實際數據中有大約100組。因此,'union all'應該以某種方式自動通過所有獨特的水果,而不必爲每個獨特的水果編寫代碼,查找每個獨特水果的異常值數量,僅取這些數量的行並將其顯示在另一個表格(視圖)中。

+0

所以這是一個關於百分點的問題? – Strawberry

+0

這是有點麻煩,不能使用'ROW_NUMBER()'(MySQL不支持) – wvdz

+0

您似乎選擇最高和最低值,而不是頂部和底部25%的值!? ! – Strawberry

回答

0

在我知道的任何RDBMS中,您不能爲LIMIT提供來自子查詢的值。有些dbs甚至不允許在它們的子句版本中使用主變量/參數(我正在考慮iSeries DB2)。

這實質上是一個的問題。所有其他RDBMS中的類似查詢都可以使用所謂的窗口函數來解決 - 實質上,您正在查看可移動的數據選擇。我們不得不僞造它。查詢的實際機制取決於您需要的實際數據,因此我只能說出您在此嘗試的內容。這些技術應該普遍適用,但可能需要比其他更多的創造力。

首先你需要一個函數,它會返回一個數字,表明它的位置 - 我假設重複價格應該被賦予相同的等級(關係),並且這樣做不會在數量上產生差距。這本質上是DENSE_RANK()窗口函數。我們可以通過做得到這些結果如下:

SELECT fruit, country, price, 
     @Rnk := IF(@last_fruit <> fruit, 1, 
       IF(@last_price = price, @Rnk, @Rnk + 1)) AS Rnk, 
     @last_fruit := fruit, 
     @last_price := price 
FROM Fruits 
JOIN (SELECT @Rnk := 0) n 
ORDER BY fruit, price 

Example Fiddle

...產生該'apple'組的下列:

fruit country price rank 
============================= 
apple UK  1  1 
apple LT  2  2 
apple USA  3  3 
apple EE  4  4 
apple LV  5  5 

現在,你想獲得頂部/底部25%的行。在這種情況下,你需要不同價格的計數:

SELECT fruit, COUNT(DISTINCT price) 
FROM Fruits 
GROUP BY fruit 

...而現在我們只需要加入這個以前的語句來限制頂部/底部:

SELECT RankedFruit.fruit, RankedFruit.country, RankedFruit.price 
FROM (SELECT fruit, COUNT(DISTINCT price) AS priceCount 
     FROM Fruits 
     GROUP BY fruit) CountedFruit 
JOIN (SELECT fruit, country, price, 
      @Rnk := IF(@last_fruit <> fruit, 1, 
         IF(@last_price = price, @Rnk, @Rnk + 1)) AS rnk, 
      @last_fruit := fruit, 
      @last_price := price 
     FROM Fruits 
     JOIN (SELECT @Rnk := 0) n 
     ORDER BY fruit, price) RankedFruit 
    ON RankedFruit.fruit = CountedFruit.fruit 
    AND (RankedFruit.rnk > ROUND(CountedFruit.priceCount * .75) 
      OR RankedFruit.rnk <= ROUND(CountedFruit.priceCount * .25)) 

SQL Fiddle Example

...這將產生如下:

fruit country price 
======================= 
apple UK  1 
apple LV  5 
pear NN  2 
pear NO  2 
pear PL  7 

(我複製了pear行以顯示「並列」價格。)

0

輪不需要2/3的參數嗎?即你不需要輸入什麼小數位,你想四捨五入嗎?

so 
... 
LIMIT (SELECT ROUND(COUNT(*) * 0.25) 
     FROM #fruits f2 
     WHERE f2.fruit = 'apple') 

becomes 
... 
LIMIT (SELECT ROUND(COUNT(*) * 0.25,2) 
     FROM #fruits f2 
     WHERE f2.fruit = 'apple') 

此外,只是快速查看午餐,但它看起來像只是期望最小值/最大值。你不能只是使用這些功能嗎?

+0

爲什麼有2位小數? – wvdz

+0

@popovitsj,2僅僅是語法的一個例子。 – KevHun

+0

我只是好奇你如何將結果限制在1.33行。 – wvdz