Как создать функцию стандартного отклонения относительного диапазона (stdev) в SQL Server (T-SQL) без использования циклов? - PullRequest
1 голос
/ 07 января 2011

Я пытаюсь найти способ использовать функцию относительного диапазона stdev в SQL Server 2005/8 без каких-либо циклов. Предполагая набор тестовых данных (test_data) с 10 записями и столбцами:

column_id identity(1,1), column_data, column_stdev

Мне нужно, чтобы мой stdev () работал следующим образом:

stdev (column_data, где id между i и i + 3) с i = column_id, автоинкремент.

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

declare @i int
set @i=1
while @i < 11
 begin
 update test_data
 set column_stdev = (select stdev(column_data) from test_data 
                     where column_id between @i and @i+2)
 set @i=@i+1
end

Не могли бы вы, пожалуйста, сообщить мне, если есть способ избежать зацикливания, чтобы задать для column_stdev получение stdev из последних 3 записей в column_data.

Ответы [ 2 ]

2 голосов
/ 07 января 2011

Одним очевидным улучшением было бы избавление от цикла WHILE и построчной обработки. Если вы не делаете это в явной транзакции, вам нужно будет дождаться завершения 10 отдельных коммитов, и общий объем требуемой работы в любом случае будет больше, как показано в статистике ниже.

NB. Однако я не утверждаю, что мой запрос не может быть улучшен. Возможно, вы сможете настроить подход «необычного обновления» для своих нужд и сделать это, например, с помощью одного сканирования данных.

create table t (
column_id int identity(1,1) primary key, 
column_data float, 
column_stdev float)

insert into t (column_data)
select top 10 CHECKSUM(newid()) from sys.objects

SET STATISTICS IO ON

UPDATE t 
SET  column_stdev = (SELECT stdev(t2.column_data)
                     FROM t t2 
                     WHERE t2.column_id BETWEEN t.column_id AND t.column_id + 2)

/*Table 't'. Scan count 11, logical reads 42, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Worktable'. Scan count 1, logical reads 23, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.*/

declare @i int
set @i=1
while @i < 11
 begin
 update t
 set column_stdev = (select stdev(column_data) from t 
                     where column_id between @i and @i+2)
 set @i=@i+1
end


/* (Aggregated the 10 results)
Table 't'. Scan count 20, logical reads 240, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Worktable'. Scan count 10, logical reads 230, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.*/
0 голосов
/ 08 января 2011

Этот ответ использует (недокументированный) подход " Quirky Update " отсюда. Я не мог найти способ сделать это с помощью одного сканирования, поэтому этот подход использует 2 сканирования.

Статистика намного лучше на Table 't'. Scan count 2, logical reads 4

Это требует некоторых изменений схемы. Дополнительный столбец и кластерный индекс должны быть отсортированы по убыванию. Я предположил, что ваш column_data - это int и 4 байта. Если это не так, вам нужно отрегулировать длину двоичного столбца и подстрок.

create table t(
column_id int identity(1,1), 
column_data int, 
column_stdev float,
prev3 varbinary(12))

ALTER TABLE [dbo].[t] ADD PRIMARY KEY CLUSTERED ([column_id] DESC)

GO

insert into t (column_data)
select top 10 CHECKSUM(newid()) from sys.objects

DECLARE @prev3 varbinary(12)
DECLARE @Anchor INT

UPDATE t
SET @prev3 = prev3 = SUBSTRING(CAST(column_data AS binary(4)) + ISNULL(@prev3,0x),1,12), 
@Anchor = column_id
   FROM t WITH (TABLOCKX)
 OPTION (MAXDOP 1)


UPDATE t
SET    column_stdev = 
              CASE Len(prev3)
                WHEN 12 THEN (SELECT STDEV(c)
                              FROM   (SELECT CAST(Substring(prev3, 1, 4) AS INT) AS c
                                      UNION ALL
                                      SELECT Substring(prev3, 5, 4)
                                      UNION ALL
                                      SELECT Substring(prev3, 9, 4)) t)
                WHEN 8 THEN (SELECT STDEV(c)
                             FROM   (SELECT CAST(Substring(prev3, 1, 4) AS INT) AS c
                                      UNION ALL
                                      SELECT Substring(prev3, 5, 4)
                                    ) t)
              END  
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...