Оператор выбора SQL для вычисления столбца скользящего среднего - PullRequest
7 голосов
/ 26 мая 2009

Я пытаюсь получить столбец скользящего среднего в инструкции SELECT на основе столбца из n предыдущих строк в той же инструкции SELECT. Среднее значение, которое мне нужно, основано на n предыдущих строках в наборе результатов.

Позвольте мне объяснить

Id        Number       Average
 1             1          NULL
 2             3          NULL
 3             2          NULL
 4             4             2 <----- Average of (1, 3, 2),Numbers from previous 3 rows
 5             6             3 <----- Average of (3, 2, 4),Numbers from previous 3 rows
 .             .             .
 .             .             .

Первые 3 строки столбца Average являются нулевыми, поскольку предыдущих строк нет. Строка 4 в столбце Average показывает среднее значение столбца Number из предыдущих 3 строк.

Мне нужна помощь в попытке создать оператор SQL Select, который сделает это.

Ответы [ 7 ]

11 голосов
/ 26 мая 2009

Это должно сделать это:

--Test Data
CREATE TABLE    RowsToAverage
    (
    ID int NOT NULL,
    Number int NOT NULL
    )

INSERT  RowsToAverage(ID, Number)
SELECT  1, 1
UNION ALL
SELECT  2, 3
UNION ALL
SELECT  3, 2
UNION ALL
SELECT  4, 4
UNION ALL
SELECT  5, 6
UNION ALL
SELECT  6, 8
UNION ALL
SELECT  7, 10

--The query
;WITH   NumberedRows
AS
(
SELECT  rta.*, row_number() OVER (ORDER BY rta.ID ASC) AS RowNumber
FROM    RowsToAverage rta
)

SELECT  nr.ID, nr.Number,
        CASE
            WHEN nr.RowNumber <=3 THEN NULL
            ELSE (  SELECT  avg(Number) 
                    FROM    NumberedRows 
                    WHERE   RowNumber < nr.RowNumber
                    AND     RowNumber >= nr.RowNumber - 3
                )
        END AS MovingAverage
FROM    NumberedRows nr
8 голосов
/ 26 мая 2009

Предполагая, что столбец Id является последовательным, вот упрощенный запрос для таблицы с именем «MyTable»:

SELECT 
    b.Id,
    b.Number,
    (
      SELECT 
       AVG(a.Number) 
      FROM 
       MyTable a 
     WHERE 
       a.id >= (b.Id - 3) 
       AND a.id < b.Id
       AND b.Id > 3 
     ) as Average
FROM 
    MyTable b;
2 голосов
/ 27 мая 2009

Казалось бы, простое самостоятельное соединение работает намного лучше, чем подзапрос, ссылающийся на строку

Генерация 10 тыс. Строк тестовых данных:

drop table test10k
create table test10k (Id int, Number int, constraint test10k_cpk primary key clustered (id))

;WITH digits AS (
    SELECT 0 as Number
    UNION SELECT 1
    UNION SELECT 2
    UNION SELECT 3
    UNION SELECT 4
    UNION SELECT 5
    UNION SELECT 6
    UNION SELECT 7
    UNION SELECT 8
    UNION SELECT 9
)
,numbers as (
    SELECT 
        (thousands.Number * 1000) 
        + (hundreds.Number * 100) 
        + (tens.Number * 10) 
        + ones.Number AS Number
    FROM digits AS ones 
    CROSS JOIN digits AS tens
    CROSS JOIN digits AS hundreds
    CROSS JOIN digits AS thousands
)
insert test10k (Id, Number)
select Number, Number
from numbers 

Я бы вытащил особый случай первых 3 строк из основного запроса, вы можете ОБЪЕДИНИТЬ ВСЕ те обратно, если вы действительно хотите его в наборе строк. Самостоятельный запрос на присоединение:

;WITH   NumberedRows
AS
(
    SELECT  rta.*, row_number() OVER (ORDER BY rta.ID ASC) AS RowNumber
    FROM    test10k rta
)

SELECT  nr.ID, nr.Number,
    avg(trailing.Number) as MovingAverage
FROM    NumberedRows nr
    join NumberedRows as trailing on trailing.RowNumber between nr.RowNumber-3 and nr.RowNumber-1
where nr.Number > 3
group by nr.id, nr.Number

На моей машине это занимает около 10 секунд, подход подзапроса, продемонстрированный Аароном Алтоном, занимает около 45 секунд (после того, как я изменил его, чтобы отразить мою исходную таблицу тестирования):

;WITH   NumberedRows
AS
(
    SELECT  rta.*, row_number() OVER (ORDER BY rta.ID ASC) AS RowNumber
    FROM    test10k rta
)
SELECT  nr.ID, nr.Number,
    CASE
            WHEN nr.RowNumber <=3 THEN NULL
            ELSE (  SELECT  avg(Number) 
                            FROM    NumberedRows 
                            WHERE   RowNumber < nr.RowNumber
                            AND             RowNumber >= nr.RowNumber - 3
                    )
    END AS MovingAverage
FROM    NumberedRows nr

Если вы включите параметр SET STATISTICS PROFILE ON, вы увидите, что самообъединение имеет 10 000 выполнений в спуле таблицы. Подзапрос имеет 10 000 выполнений на фильтрах, агрегате и других этапах.

1 голос
/ 26 мая 2009

Редактировать: я упустил момент, что он должен усреднить три предыдущие записи ...

Для общего скользящего среднего, я думаю, что-то вроде этого будет работать:

SELECT
    id, number, 
    SUM(number) OVER (ORDER BY ID) / 
       ROW_NUMBER() OVER (ORDER BY ID) AS [RunningAverage]
FROM myTable
ORDER BY ID
0 голосов
/ 26 мая 2009

В качестве альтернативы вы можете денормализовать и сохранить предварительно рассчитанные рабочие значения. Описано здесь:

http://sqlblog.com/blogs/alexander_kuznetsov/archive/2009/01/23/denormalizing-to-enforce-business-rules-running-totals.aspx

Производительность выбора так же быстро, как и идет. Конечно, изменения происходят медленнее.

0 голосов
/ 26 мая 2009

Если вы хотите, чтобы это было действительно производительно, и не боялись копаться в редко используемой области SQL Server, вам следует заняться написанием пользовательской функции агрегирования. SQL Server 2005 и 2008 принесли в таблицу интеграцию CLR, включая возможность написания пользовательских агрегатных функций. Настраиваемая итоговая совокупная совокупность была бы наиболее эффективным способом для вычисления скользящей средней, подобной этой, на сегодняшний день.

0 голосов
/ 26 мая 2009

Ознакомьтесь с некоторыми решениями здесь . Я уверен, что вы могли бы достаточно легко адаптировать один из них.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...