mysql - 按组划分的数据异常值

标签 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    |
+--------+---------+-------+ 

我想要的是创建一个 View ,它将显示所有水果离群值联合表。如果我有这个 View ,我可以只分析尾部,也可以将 View 与主表相交以获得没有异常值的表 - 这就是我的目标。解决这个问题的方法是:

(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”应该以某种方式自动遍历所有独特的水果,而无需为每个独特的水果编写代码,找到每个独特水果的异常值数量,仅获取该行数并将其全部显示在另一个表( View )中。

最佳答案

在我所知的任何 RDBMS 中,您都无法为 LIMIT 提供子查询中的值。有些数据库甚至不允许在其子句版本中使用主机变量/参数(我正在考虑 iSeries DB2)。

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

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

首先,您需要一个函数,该函数将返回一个指示其位置的数字 - 我假设重复的价格应具有相同的排名(并列),并且这样做不会在数字中造成差距。这本质上是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 行来显示“捆绑”价格。)

关于mysql - 按组划分的数据异常值,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/24757644/

相关文章:

php - 循环解析包含某些不同文件的许多文本的txt文件

php - 为 @ID 字段插入 null 的 Doctrine

sql,大查询 : aggregate all entries between two strings in a variable

sql - 如何在 OPENJSON 查询的WITH子句中给出动态列名?

mysql - 根据日期获取记录(最近30天)

sql - 使用 unixODBC 找不到 hive odbc 连接器错误消息

php - 显示多个类别 codeigniter

mysql - MyBatis Spring 集成 - POJO 上未设置某些 ResultSet 值

SQL 日期选择

java - 错误无法在 JPA 中找到名为 [total_recods] 的参数