Неизвестный столбец в подзапросе mysql

Я пытаюсь получить avg элемента, поэтому я использую подзапрос.

обновление: Я должен был быть яснее изначально, но я хочу, чтобы avg был только для последних 5 пунктов

сначала я начал с

SELECT 
y.id
FROM (
    SELECT *
        FROM (
                SELECT *
                FROM products
                WHERE itemid=1
        ) x  
    ORDER BY id DESC
    LIMIT 15 
) y;

который работает, но довольно бесполезен, поскольку он просто показывает мне идентификаторы.

затем я добавил в ниже

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;

когда я это делаю, я получаю ошибку неизвестный столбец ' y.id 'в предложении where' по дальнейшее чтение здесь я считаю, что это потому, что когда запросы спускаются на следующий уровень, к ним нужно присоединиться?

поэтому я попробовал ниже * * удалить un needed 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;

но я стол '.y ' не существует. Я на правильном пути? Что мне нужно изменить, чтобы получить то, что мне нужно?

пример можно найти здесь в 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);

учитывая мои данные в этом примере, мой ожидаемый результат ниже, где есть столбец рядом с каждым идентификатором, который имеет avg предыдущих 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

8 ответов


Я не эксперт MySQL (в MS SQL это может быть сделано проще), и ваш вопрос выглядит немного неясным для меня, но похоже, что вы пытаетесь получить среднее значение предыдущих 5 элементов.

если у вас Id без пробелов, это просто:

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 уровня вложенности подзапросов. Но вы можете получить 5 предыдущих пунктов с offset, например:

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


Это мое решение. Легко понять, как это работает, но в то же время он не может быть оптимизирован, так как я использую некоторые строковые функции, и это далеко от стандартного 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

пожалуйста, смотрите fiddle здесь. Я не рекомендую использовать этот трюк для больших таблиц, но для небольших наборов данных это хорошо.


это, возможно, не самое простое решение, но оно выполняет свою работу и является интересным вариантом и, на мой взгляд, прозрачным. Я моделирую аналитические функции, которые я знаю от Oracle.

как мы не предполагаем id чтобы быть последовательным, подсчет строк моделируется путем увеличения @rn каждой строки. Следующая таблица продуктов включая rownum соединена с собой и только строки 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 получить позицию строки в порядке


похоже, вы просто хотите:

SELECT
  id,
  (SELECT AVG(deposit)
   FROM (
        SELECT deposit
        FROM products
        ORDER BY id DESC
        LIMIT 5) last5
   ) avgdeposit
FROM products 

внутренний запрос получает последние 5 строк, добавленных в продукт, запрос, который обертывает, который получает среднее значение для своих депозитов.


Я собираюсь немного упростить ваш запрос, чтобы я мог это объяснить.

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;

вот один из способов сделать это в 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 или функции windowing. Наличие обоих значительно упростило бы запрос до следующего:

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.


2 возможных решения здесь

во-первых, используя пользовательские переменные, чтобы добавить порядковый номер. Сделайте это дважды и присоедините второй набор к первому, где порядковый номер находится между 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

второй более грубый и использует коррелированный суб-запрос (который, вероятно, будет работать плохо по мере увеличения объема данных). Обычный выбор, но для последнего столбца он имеет подзапрос, который ссылается на идентификатор в основном выберите.

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

это то, что вы после?

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); во-вторых, идентификатор не может быть последовательным/без пробелов в 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
;