按组分组的数据异常值

时间:2014-07-15 11:57:22

标签: mysql sql

我想分析分组数据的异常值。让我们说我有数据:

+--------+---------+-------+
| 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组。所以'联合所有'应该以某种方式自动通过所有独特的水果而不为每个独特的水果编写代码,找到每个独特水果的异常数量,只取这些行数并在另一个表中显示(查看)。

2 个答案:

答案 0 :(得分: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')

另外,只是快速浏览午餐,但看起来你只是期待最小/最大值。你能不能只使用这些功能吗?

答案 1 :(得分:0)

您无法在我知道的任何RDBMS中为LIMIT提供来自子查询的值。有些dbs甚至不允许在它们的子句版本中使用主变量/参数(我正在思考iSeries DB2)。

这基本上是问题。大多数其他RDBMS中的类似查询通过所谓的窗口函数来解决 - 实际上,您正在查看可移动的数据选择。

MySQL没有这个功能,所以我们必须伪造它。查询的实际机制将取决于您需要的实际数据,因此我只能说出您在此处尝试的内容。这些技术通常应该具有适应性,但可能需要更多的创造力。

首先,你想要一个能够返回一个表示其位置的数字的函数 - 我假设重复的价格应该被赋予相同的等级(关系),并且这样做会赢得&#t; t在数字上创造一个空白。这基本上是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行以显示&#34;捆绑&#34;价格。)