2010-10-13 2 views
6

Мне нужен подход ранжирования T-SQL, аналогичный тому, который предоставляется NTILE(), за исключением того, что члены каждой плитки будут располагаться в скользящем распределении, имеют меньше членов.T-SQL: лучшая функция распределения/запрос на рассылку

Например

CREATE TABLE #Rank_Table(
id int identity(1,1) not null, 
hits bigint not null default 0, 
PERCENTILE smallint null 
) 
--Slant the distribution of the data 
INSERT INTO #Rank_Table (hits) 
select CASE 
    when DATA > 9500 THEN DATA*30 
    WHEN data > 8000 THEN DATA*5 
    WHEN data < 7000 THEN DATA/3 +1 
    ELSE DATA 
END 
FROM 
(select top 10000 (ABS(CHECKSUM(NewId())) % 99 +1) * (ABS(CHECKSUM(NewId())) % 99 +1) DATA 
from master..spt_values t1 
    cross JOIN master..spt_values t2) exponential 

Declare @hitsPerGroup as bigint 
Declare @numGroups as smallint 
set @numGroups=100 

select @hitsPerGroup=SUM(hits)/(@numGroups -1) FROM #Rank_Table 

select @hitsPerGroup HITS_PER_GROUP 

--This is an even distribution 
SELECT id,HITS, NTILE(@numGroups) Over (Order By HITS DESC) PERCENTILE 
FROM #Rank_Table 
GROUP by id, HITS 

--This is my best attempt, but it skips groups because of the erratic distribution 
select 
    T1.ID, 
    T1.hits, 
    T.RunningTotal/@hitsPerGroup + 1 TILE, 
    T.RunningTotal 
FROM #Rank_Table T1 
     CROSS APPLY (Select SUM(hits) RunningTotal FROM #Rank_Table where hits <= T1.hits) T 
order by T1.hits 

DROP TABLE #Rank_Table 

В #Rank_table, NTILE (@numGroups) создает равномерное распределение @numGroups групп. Мне нужны группы @numGroups, в которых у плитки 1 есть наименьшее количество членов, у плитки 2 будет один или более плит 1, у плитки 3 будет 1 или больше, чем у плитки 2 ... у плитки 100 будет больше всего.

Я использую SQL Server 2008. На практике это будет выполняться против постоянной таблицы с потенциально миллионами строк, чтобы периодически обновлять столбец PERCENTILE с ее процентилем от 1 до 100.

Моя лучшая попытка выше пропустит процентили и будет работать плохо. Должен быть лучший способ.

+3

Статистика используется для описания большого набора данных кратким образом, который помогает понять. Из ваших вопросов неясно, что вы пытаетесь сделать или понять о своем наборе данных. Медианы, процентили и т. Д. Отлично подходят для нормальных распределений, и они устраняют экстремальные выбросы с очень небольшими проблемами. Вы уверены, что имеете нормальное распространение? Звучит скорее как экспоненциальное распределение. Было бы более полезно сказать, что вы пытаетесь понять о своих данных, а не спрашивать о функциях. –

ответ

0

Чтобы создать более линейное распределение, я добавил вычисляемый столбец в таблицу данных, HITS_SQRT HITS_SQRT AS (CONVERT([int],sqrt(HITS*4),(0))) PERSISTED.

С помощью этой колонки вы можете вычислить целевое количество «хитов на процентили».

select @hitsPerGroup=SUM(HITS_SQRT)/(@numGroups -1)[email protected], @dataPoints=COUNT(*) FROM #Rank_Table 

Скрипт создает временную таблицу с ROW_NUMBER() упорядоченных по количеством хитов и перебирает строки в порядке модернизирует свою процентиль от 100 до 1. спускающихся нарастающего итога хранятся числа обращений , и когда передается @hitsPerGroup, процентиль понижается от 100 до 99, от 99 до 98 и т. д.

Затем таблица исходных данных обновляется с ее процентилем. Для ускорения обновления есть индекс рабочей таблицы temp.

Полный скрипт с использованием #Rank_Table в качестве исходной таблицы данных.

--Create Test Data 
CREATE TABLE #Rank_Table(
id int identity(1,1) not null, 
hits bigint not null default 0, 
PERCENTILE smallint NULL, 
HITS_SQRT AS (CONVERT([int],sqrt(HITS*4),(0))) PERSISTED 
) 
--Slant the distribution of the data 
INSERT INTO #Rank_Table (hits) 
select CASE 
    when DATA > 9500 THEN DATA*30 
    WHEN data > 8000 THEN DATA*5 
    WHEN data < 7000 THEN DATA/3 +1 
    ELSE DATA 
END 
FROM 
(select top 10000 (ABS(CHECKSUM(NewId())) % 99 +1) * (ABS(CHECKSUM(NewId())) % 99 +1) DATA 
from master..spt_values t1 
    cross JOIN master..spt_values t2) exponential 

--Create temp work table and variables to calculate percentiles 
    Declare @hitsPerGroup as int 
    Declare @numGroups as int 
    Declare @dataPoints as int 
    set @numGroups=100 

    select @hitsPerGroup=SUM(HITS_SQRT)/(@numGroups -1)[email protected], @dataPoints=COUNT(*) FROM #Rank_Table 

    --show the number of hits that each group should have 
    select @hitsPerGroup HITS_PER_GROUP 

    --Use temp table for the calculation 
    CREATE TABLE #tbl (
     row int, 
     hits int, 
     ID bigint, 
     PERCENTILE smallint null 
    ) 
    --add index to row 
    CREATE CLUSTERED INDEX idxRow ON #tbl(row) 

    insert INTO #tbl 
    select ROW_NUMBER() over (ORDER BY HITS), hits_SQRT, ID, null from #Rank_Table 

    --Update each row with a running total. 
    --lower the percentile by one when we cross a threshold for the maximum number of hits per group (@hitsPerGroup) 
    DECLARE @row as int 
    DEClare @runningTotal as int 
    declare @percentile int 
    set @row = 0 
    set @runningTotal = 0 
    set @percentile = @numGroups 

    while @row <= @dataPoints 
    BEGIN 
     select @[email protected] + hits from #tbl where [email protected] 

     if @runningTotal >= @hitsPerGroup 
     BEGIN 

      update #tbl 
      set [email protected] 
      WHERE PERCENTILE is null and row <@row 

      set @percentile = @percentile - 1 

      set @runningTotal = 0 
     END 

     --change rows 
     set @row = @row + 1 
    END 

    --get remaining 
    update #tbl 
    set [email protected] 
    WHERE PERCENTILE is null 

    --update source data 
    UPDATE m SET PERCENTILE = t.PERCENTILE 
    FROM #tbl t 
    inner join #Rank_Table m on t.ID=m.ID 


--Show the results 
    SELECT PERCENTILE, COUNT(id) NUMBER_RECORDS, SUM(HITS) HITS_IN_PERCENTILE 
    FROM #Rank_Table 
    GROUP BY PERCENTILE 
    ORDER BY PERCENTILE 

--cleanup 
    DROP TABLE #Rank_Table 
    DROP TABLE #tbl 

Производительность не звездная, но она достигла цели плавного распределения скольжения.