我想分析分组数据的异常值。假设我有数据:
+--------+---------+-------+
| 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)。
这本质上是一个greatest-n-per-group问题。大多数其他 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
...为 '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))
...产生以下结果:
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/