mysql子查询中的未知列
Posted
技术标签:
【中文标题】mysql子查询中的未知列【英文标题】:Unknown column in mysql subquery 【发布时间】:2013-10-20 20:24:42 【问题描述】:我正在尝试获取项目的平均值,因此我使用了子查询。
更新:我一开始应该更清楚,但我希望平均只适用于最后 5 项
首先我开始
SELECT
y.id
FROM (
SELECT *
FROM (
SELECT *
FROM products
WHERE itemid=1
) x
ORDER BY id DESC
LIMIT 15
) y;
它运行但相当无用,因为它只是向我显示 ID。
然后我在下面添加了
SELECT
y.id,
(SELECT AVG(deposit) FROM (SELECT deposit FROM products WHERE id < y.id ORDER BY id DESC LIMIT 5)z) AVGDEPOSIT
FROM (
SELECT *
FROM (
SELECT *
FROM products
WHERE itemid=1
) x
ORDER BY id DESC
LIMIT 15
) y;
当我这样做时,我得到错误 Unknown column 'y.id' in 'where clause',在进一步阅读这里我相信这是因为当查询进入下一个级别时,他们需要加入吗?
所以我尝试了下面的**删除了不需要的 suquery
SELECT
y.id,
(SELECT AVG(deposit) FROM (
SELECT deposit
FROM products
INNER JOIN y as yy ON products.id = yy.id
WHERE id < yy.id
ORDER BY id DESC
LIMIT 5)z
) AVGDEPOSIT
FROM (
SELECT *
FROM products
WHERE itemid=1
ORDER BY id DESC
LIMIT 15
) y;
但我得到表'test.y'不存在。我在正确的轨道上吗?我需要改变什么才能得到我想要的东西?
例子可以在here in sqlfiddle找到。
CREATE TABLE products
(`id` int, `itemid` int, `deposit` int);
INSERT INTO products
(`id`, `itemid`, `deposit`)
VALUES
(1, 1, 50),
(2, 1, 75),
(3, 1, 90),
(4, 1, 80),
(5, 1, 100),
(6, 1, 75),
(7, 1, 75),
(8, 1, 90),
(9, 1, 90),
(10, 1, 100);
根据本示例中的数据,我的预期结果如下,每个 ID 旁边有一列包含前 5 次存款的平均值。
id | AVGDEPOSIT
10 | 86 (deposit value of (id9+id8+id7+id6+id5)/5) to get the AVG
9 | 84
8 | 84
7 | 84
6 | 79
5 | 73.75
【问题讨论】:
你能发布你的表架构吗? @SajunaFernando 我已经编辑了问题以显示这一点并添加到 sqlfiddle 中。 您能否补充一下您的问题,您希望插入小提琴中的数据的预期结果是什么? 请用英文准确描述你想要做什么。 【参考方案1】:我将稍微简化您的查询,以便我可以解释它。
SELECT
y.id,
(
SELECT AVG(deposit) FROM
(
SELECT deposit
FROM products
LIMIT 5
) z
) AVGDEPOSIT
FROM
(
SELECT *
FROM
(
SELECT *
FROM products
) x
LIMIT 15
) y;
我的猜测是您只需要在其中插入一些 AS
关键字。我相信其他人会想出更优雅的东西,但现在你可以尝试一下。
SELECT
y.id,
(
SELECT AVG(deposit) FROM
(
SELECT deposit
FROM products
LIMIT 5
) z
) AS AVGDEPOSIT
FROM
(
SELECT *
FROM
(
SELECT *
FROM products
) AS x
LIMIT 15
) y;
【讨论】:
嵌套SELECT * FROM products
的目的是什么?
我只是为了使AS
关键字的语法更易于查看而进行了简化。他@ak85 可能想保留原来的 select 语句。【参考方案2】:
我不是 mysql 专家(在 MS SQL 中可以更轻松地完成),您的问题对我来说似乎有点不清楚,但看起来您正在尝试获取前 5 项的平均值。
如果你有Id without gaps,这很容易:
select
p.id,
(
select avg(t.deposit)
from products as t
where t.itemid = 1 and t.id >= p.id - 5 and t.id < p.id
) as avgdeposit
from products as p
where p.itemid = 1
order by p.id desc
limit 15
如果不是,那么我已经尝试过这样的查询
select
p.id,
(
select avg(t.deposit)
from (
select tt.deposit
from products as tt
where tt.itemid = 1 and tt.id < p.id
order by tt.id desc
limit 5
) as t
) as avgdeposit
from products as p
where p.itemid = 1
order by p.id desc
limit 15
但我有例外Unknown column 'p.id' in 'where clause'
。看起来 MySQL 无法处理 2 级嵌套的子查询。
但是您可以使用offset
获得 5 个以前的项目,如下所示:
select
p.id,
(
select avg(t.deposit)
from products as t
where t.itemid = 1 and t.id > coalesce(p.prev_id, -1) and t.id < p.id
) as avgdeposit
from
(
select
p.id,
(
select tt.id
from products as tt
where tt.itemid = 1 and tt.id <= p.id
order by tt.id desc
limit 1 offset 6
) as prev_id
from products as p
where p.itemid = 1
order by p.id desc
limit 15
) as p
sql fiddle demo
【讨论】:
这带回了我在感谢之后的结果。我会继续做一些测试,如果我发现这里有什么需要改变的地方,我会告诉你,但到目前为止它看起来很棒!【参考方案3】:看起来你只是想要:
SELECT
id,
(SELECT AVG(deposit)
FROM (
SELECT deposit
FROM products
ORDER BY id DESC
LIMIT 5) last5
) avgdeposit
FROM products
内部查询获取添加到产品的最后 5 行,包装查询获取其存款的平均值。
【讨论】:
我提供了更多信息,您的答案是正确的,但我希望该 AVG 成为最后 5 个结果。 好的,现在试试。我添加了一个内部查询来获取最新的 5 个产品。 谢谢我正在寻找每个项目的平均值,就像它在迄今为止的最佳答案中所做的那样。 啊,您想要 preceding 5 种产品(基于 id)的平均存款,其中 itemid 为 1?尽管您的示例数据没有显示,但 itemid 可能不是 1?如果它不是 1,它是否算作“最新 5”但不计入总数,或者我应该找到 itemid 1 的 5 行,无论多远? 是的,很抱歉造成混淆,其中 itemid = 当前 id 的 itemid(在我的小提琴中,这始终是 1,但最好不要在那里硬编码 1)【参考方案4】:这是我的解决方案。它的工作原理很容易理解,但同时由于我使用了一些字符串函数,因此无法对其进行太多优化,并且与标准 SQL 相差甚远。如果只需要返回几条记录,也可以。
此查询将为每个 ID 返回一个逗号分隔的前一个 ID 列表,按升序排列:
SELECT p1.id, p1.itemid, GROUP_CONCAT(p2.id ORDER BY p2.id DESC) previous_ids
FROM
products p1 LEFT JOIN products p2
ON p1.itemid=p2.itemid AND p1.id>p2.id
GROUP BY
p1.id, p1.itemid
ORDER BY
p1.itemid ASC, p1.id DESC
它会返回如下内容:
| ID | ITEMID | PREVIOUS_IDS |
|----|--------|-------------------|
| 10 | 1 | 9,8,7,6,5,4,3,2,1 |
| 9 | 1 | 8,7,6,5,4,3,2,1 |
| 8 | 1 | 7,6,5,4,3,2,1 |
| 7 | 1 | 6,5,4,3,2,1 |
| 6 | 1 | 5,4,3,2,1 |
| 5 | 1 | 4,3,2,1 |
| 4 | 1 | 3,2,1 |
| 3 | 1 | 2,1 |
| 2 | 1 | 1 |
| 1 | 1 | (null) |
然后我们可以将这个查询的结果与 products 表本身连接起来,在连接条件上我们可以使用 FIND_IN_SET(src, csvalues) 来返回 src 字符串在逗号分隔值中的位置:
ON FIND_IN_SET(id, previous_ids) BETWEEN 1 AND 5
最终查询如下所示:
SELECT
list_previous.id,
AVG(products.deposit)
FROM (
SELECT p1.id, p1.itemid, GROUP_CONCAT(p2.id ORDER BY p2.id DESC) previous_ids
FROM
products p1 INNER JOIN products p2
ON p1.itemid=p2.itemid AND p1.id>p2.id
GROUP BY
p1.id, p1.itemid
) list_previous LEFT JOIN products
ON list_previous.itemid=products.itemid
AND FIND_IN_SET(products.id, previous_ids) BETWEEN 1 AND 5
GROUP BY
list_previous.id
ORDER BY
id DESC
请看小提琴here。我不建议将这个技巧用于大表,但对于小数据集就可以了。
【讨论】:
【参考方案5】:这是在 MySQL 中执行此操作的一种方法:
SELECT p.id
, ( SELECT AVG(deposit)
FROM ( SELECT @rownum:=@rownum+1 rn, deposit, id
FROM ( SELECT @rownum:=0 ) r
, products
ORDER BY id ) t
WHERE rn BETWEEN p.rn-5 AND p.rn-1 ) avgdeposit
FROM ( SELECT @rownum1:=@rownum1+1 rn, id
FROM ( SELECT @rownum1:=0 ) r
, products
ORDER BY id ) p
WHERE p.rn >= 5
ORDER BY p.rn DESC;
遗憾的是 MySQL 不支持 WITH 子句或窗口函数。两者都将大大简化查询到以下内容:
WITH tbl AS (
SELECT id, deposit, ROW_NUMBER() OVER(ORDER BY id) rn
FROM products
)
SELECT id
, ( SELECT AVG(deposit)
FROM tbl
WHERE rn BETWEEN t.rn-5 AND t.rn-1 )
FROM tbl t
WHERE rn >= 5
ORDER BY rn DESC;
后一个查询在 Postgres 中运行良好。
【讨论】:
【参考方案6】:这里有两种可能的解决方案
首先使用用户变量添加一个序列号。这样做两次,然后将第二组连接到第一组,其中序列号在 id - 1 和 id - 5 之间。然后只需使用 AVG。没有相关的子查询。
SELECT Sub3.id, Sub3.itemid, Sub3.deposit, AVG(Sub4.deposit)
FROM
(
SELECT Sub1.id, Sub1.itemid, Sub1.deposit, @Seq:=@Seq+1 AS Sequence
FROM
(
SELECT id, itemid, deposit
FROM products
ORDER BY id DESC
) Sub1
CROSS JOIN
(
SELECT @Seq:=0
) Sub2
) Sub3
LEFT OUTER JOIN
(
SELECT Sub1.id, Sub1.itemid, Sub1.deposit, @Seq1:=@Seq1+1 AS Sequence
FROM
(
SELECT id, itemid, deposit
FROM products
ORDER BY id DESC
) Sub1
CROSS JOIN
(
SELECT @Seq1:=0
) Sub2
) Sub4
ON Sub4.Sequence BETWEEN Sub3.Sequence + 1 AND Sub3.Sequence + 5
GROUP BY Sub3.id, Sub3.itemid, Sub3.deposit
ORDER BY Sub3.id DESC
第二个比较粗略,使用关联子查询(随着数据量的增加,可能会表现不佳)。进行普通选择,但对于最后一列,它有一个子查询,该查询引用主选择中的 id。
SELECT id, itemid, deposit, (SELECT AVG(P2.deposit) FROM products P2 WHERE P2.id BETWEEN P1.id - 5 AND p1.id - 1 ORDER BY id DESC LIMIT 5)
FROM products P1
ORDER BY id DESC
【讨论】:
【参考方案7】:这可能不是最简单的解决方案,但它确实可以完成工作,并且是一个有趣的变体,并且在我看来是透明的。我模拟了我从 Oracle 知道的分析函数。
由于我们不假设id
是连续的,因此通过增加@rn 每行来模拟行的计数。包含 rownum 的 Next products 表与自身相连,仅使用第 2-6 行来构建平均值。
select p2id, avg(deposit), group_concat(p1id order by p1id desc), group_concat(deposit order by p1id desc)
from ( select p2.id p2id, p1.rn p1rn, p1.deposit, p2.rn p2rn, p1.id p1id
from (select p.*,@rn1:=@rn1+1 as rn from products p,(select @rn1 := 0) r) p1
, (select p.*,@rn2:=@rn2+1 as rn from products p,(select @rn2 := 0) r) p2 ) r
where p2rn-p1rn between 1 and 5
group by p2id
order by p2id desc
;
结果:
+------+--------------+---------------------------------------+------------------------------------------+
| p2id | avg(deposit) | group_concat(p1id order by p1id desc) | group_concat(deposit order by p1id desc) |
+------+--------------+---------------------------------------+------------------------------------------+
| 10 | 86.0000 | 9,8,7,6,5 | 90,90,75,75,100 |
| 9 | 84.0000 | 8,7,6,5,4 | 90,75,75,100,80 |
| 8 | 84.0000 | 7,6,5,4,3 | 75,75,100,80,90 |
| 7 | 84.0000 | 6,5,4,3,2 | 75,100,80,90,75 |
| 6 | 79.0000 | 5,4,3,2,1 | 100,80,90,75,50 |
| 5 | 73.7500 | 4,3,2,1 | 80,90,75,50 |
| 4 | 71.6667 | 3,2,1 | 90,75,50 |
| 3 | 62.5000 | 2,1 | 75,50 |
| 2 | 50.0000 | 1 | 50 |
+------+--------------+---------------------------------------+------------------------------------------+
SQL 小提琴演示:http://sqlfiddle.com/#!2/c13bc/129
感谢这个关于如何在mysql中模拟解析函数的回答:MySQL get row position in ORDER BY
【讨论】:
【参考方案8】:这就是你所追求的吗?
SELECT m.id
, AVG(d.deposit)
FROM products m
, products d
WHERE d.id < m.id
AND d.id >= m.id - 5
GROUP BY m.id
ORDER BY m.id DESC
;
但不可能这么简单。首先,该表不能只包含一个 itemid(因此您的 WHERE 子句);其次,id 在 itemid 中不能是连续的/没有间隙的。第三,您可能想要生成跨越 itemid 而不是一次运行一个 itemid 的东西。就是这样。
SELECT itemid
, m_id as id
, AVG(d.deposit) as deposit
FROM (
SELECT itemid
, m_id
, d_id
, d.deposit
, @seq := (CASE WHEN m_id = d_id THEN 0 ELSE @seq + 1 END) seq
FROM (
SELECT m.itemid
, m.id m_id
, d.id d_id
, d.deposit
FROM products m
, products d
WHERE m.itemid = d.itemid
AND d.id <= m.id
ORDER BY m.id DESC
, d.id DESC) d
, (SELECT @seq := 0) s
) d
WHERE seq BETWEEN 1 AND 5
GROUP BY itemid
, m_id
ORDER BY itemid
, m_id DESC
;
【讨论】:
以上是关于mysql子查询中的未知列的主要内容,如果未能解决你的问题,请参考以下文章