2010-10-13 16 views
6

NTILE() tarafından sağlanana benzer bir T-SQL sıralaması yaklaşımına ihtiyacım var, ancak her bir döşemenin üyeleri kayma dağılımında olacak ve böylece daha yüksek sıradaki döşemeler olacak daha az üye var. Örneğin #Rank_table, NTILE (@numGroups) 'deT-SQL: Daha iyi bir kayan dağıtım işlevi/sorgusu

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 

için

@numGroups grupların eşit bir şekilde dağılımını oluşturur. İhtiyacım olan şey, kiremit 1'in en az üyeye sahip olduğu @numGroups grupları, 2. karonun kiremit 1'den bir veya daha fazlasına sahip olması, 3. karonun kiremit 2'den 1 veya daha fazlasına sahip olması ...

SQL Server 2008 kullanıyorum. Uygulamada, PERCENTILE sütununun periyodik olarak yüzdesel olarak 1-100 arasında güncellenmesi için milyonlarca satırlık bir kalıcı tabloya karşı çalıştırılacak.

Yukarıdaki en iyi denemem yüzdelikleri atlayacak ve kötü performans gösterecektir. Daha iyi bir yol olmalı.

+3

İstatistikler, büyük bir veri kümesini anlaşılmaya yardımcı olan kısa bir şekilde tanımlamak için kullanılır. Sorularınızdan, veri kümeniz hakkında ne yapmaya çalıştığınızı veya anladığınızdan emin değilsiniz. Yöreler, yüzdelikler vb. Normal dağılımlar için mükemmeldir ve çok az güçlükle aşırı aykırı değerleri ortadan kaldırırlar. Normal dağılımın olduğuna emin misin? Üstel dağılımın olabileceğine benziyor. İşlevler hakkında soru sormak yerine, neyi anlamaya çalıştığınızı söylemek daha yararlı olacaktır. –

cevap

0

Daha doğrusal bir dağılım oluşturmak için, HITS_SQRT HITS_SQRT AS (CONVERT([int],sqrt(HITS*4),(0))) PERSISTED veri tablosuna hesaplanmış bir sütun ekledim.

Bu sütunu kullanarak "yüzdelik isabet sayısı" için bir hedef sayı hesaplayabilirsiniz.

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

Komut daha sonra isabet sayısına göre sıralanmış olan bir ROW_NUMBER() ile geçici bir tablo oluşturur ve bir çalışan toplam hit sayısı tutulur 100 ila 1 arasında olan yüzdelik güncelleme azalan satır dolaşır ve @hitsPerGroup geçirildiğinde, yüzdelik, 100 ila 99, 99 ila 98, vb. biçime indirilir.

Ardından kaynak veri tablosu yüzdelik değeriyle güncellenir. Güncellemeyi hızlandırmak için geçici çalışma tablosunun bir dizini var.

Kaynak veri tablosunu #Rank_Table kullanarak tam kod olarak.

--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 

Performans stellar değildir, ancak düzgün bir kaydırma dağılımı hedefine ulaşır.