SQL Server'da Medyanı Hesaplama İşlevi


227

MSDN'ye göre , Median Transact-SQL'de bir toplama işlevi olarak mevcut değildir. Ancak, bu işlevi oluşturmanın mümkün olup olmadığını öğrenmek istiyorum ( Toplama işlevi, kullanıcı tanımlı işlev veya başka bir yöntem kullanarak).

Bunu yapmanın (mümkünse) en iyi yolu ne olabilir - bir toplu sorguda medyan bir değerin (sayısal bir veri türü olduğu varsayılarak) hesaplanmasına izin verin?


Yanıtlar:


145

2019 GÜNCELLEME: Bu yanıtı yazdığım 10 yıl içinde, daha iyi sonuçlar verebilecek daha fazla çözüm ortaya çıkarıldı. Ayrıca, o zamandan beri SQL Server sürümleri (özellikle SQL 2012), medyanları hesaplamak için kullanılabilecek yeni T-SQL özelliklerini tanıttı. SQL Server sürümleri, çeşitli medyan çözümlerin mükemmelliğini etkileyebilecek sorgu iyileştiricisini de geliştirmiştir. Net-net, benim orijinal 2009 yazı hala iyi ama modern SQL Server uygulamaları için daha iyi çözümler olabilir. Harika bir kaynak olan 2012'den bu makaleye göz atın: https://sqlperformance.com/2012/08/t-sql-queries/median

Bu makale, en azından test ettikleri basit şemada, aşağıdaki modelin diğer tüm alternatiflerden çok, çok daha hızlı olduğunu buldu. Bu çözelti, PERCENTILE_CONTtest edilen en yavaş ( ) çözeltiden 373 kat daha hızlıydı (!!!) . Bu hile, her durumda pratik olmayabilecek iki ayrı sorgu gerektirir. Ayrıca SQL 2012 veya üstünü gerektirir.

DECLARE @c BIGINT = (SELECT COUNT(*) FROM dbo.EvenRows);

SELECT AVG(1.0 * val)
FROM (
    SELECT val FROM dbo.EvenRows
     ORDER BY val
     OFFSET (@c - 1) / 2 ROWS
     FETCH NEXT 1 + (1 - @c % 2) ROWS ONLY
) AS x;

Tabii ki, 2012'de bir şemada yapılan bir testin harika sonuçlar verdiği için, özellikle SQL Server 2014 veya sonraki bir sürümdeyseniz, kilometreniz değişebilir. Medyan hesaplamanız için perf önemliyse, şemanız için en iyisini bulduğunuzdan emin olmak için bu makalede önerilen seçeneklerden birkaçını denemenizi ve mükemmel bir şekilde test etmenizi öneririm.

Ayrıca , bu sorunun diğer yanıtlarındanPERCENTILE_CONT birinde önerilen (SQL Server 2012'de yeni) işlevini kullanırken özellikle dikkatli olurum , çünkü yukarıda bağlantılı makale bu yerleşik işlevi en hızlı çözümden 373x daha yavaş buldu. Bu eşitsizliğin o zamandan bu yana 7 yılda iyileştirilmiş olması olasıdır, ancak şahsen performansını diğer çözümlere kıyasla doğrulayana kadar bu işlevi büyük bir tabloda kullanmam.

ORİJİNAL 2009 SONRASI AŞAĞIDAKİ:

Bunu çarpıcı bir şekilde değişen performansla yapmanın birçok yolu vardır. Medians, ROW_NUMBERs ve performanstan özellikle iyi optimize edilmiş bir çözüm . Bu, yürütme sırasında üretilen gerçek I / O'lar söz konusu olduğunda özellikle en uygun çözümdür - diğer çözümlerden daha pahalı görünüyor, ancak aslında çok daha hızlı.

Bu sayfada ayrıca diğer çözümler ve performans testi ayrıntıları hakkında bir tartışma yer almaktadır. Ortanca sütunun aynı değerine sahip birden çok satır olması durumunda, disambiguator olarak benzersiz bir sütunun kullanıldığına dikkat edin.

Tüm veritabanı performans senaryolarında olduğu gibi, her zaman bir çözümü gerçek donanımdaki gerçek verilerle test etmeye çalışın - SQL Server'ın optimize edicisinde yapılan bir değişikliğin veya ortamınızdaki bir özelliğin normalde hızlı bir çözümü ne zaman yavaşlatacağını asla bilemezsiniz.

SELECT
   CustomerId,
   AVG(TotalDue)
FROM
(
   SELECT
      CustomerId,
      TotalDue,
      -- SalesOrderId in the ORDER BY is a disambiguator to break ties
      ROW_NUMBER() OVER (
         PARTITION BY CustomerId
         ORDER BY TotalDue ASC, SalesOrderId ASC) AS RowAsc,
      ROW_NUMBER() OVER (
         PARTITION BY CustomerId
         ORDER BY TotalDue DESC, SalesOrderId DESC) AS RowDesc
   FROM Sales.SalesOrderHeader SOH
) x
WHERE
   RowAsc IN (RowDesc, RowDesc - 1, RowDesc + 1)
GROUP BY CustomerId
ORDER BY CustomerId;

12
Verilerinizde dupes, özellikle çok dupes varsa bunun işe yaramadığını düşünüyorum. Row_numbers öğesinin sıralanacağını garanti edemezsiniz. Medyanınız için gerçekten çılgınca cevaplar alabilirsiniz, hatta daha da kötüsü, hiç medyan yok.
Jonathan Beerhalter

26
Bu nedenle bir dezibiguator (yukarıdaki kod örneğinde SalesOrderId) olması önemlidir, böylece sonuç kümesi satırlarının sırasının hem geriye hem de ileriye doğru olmasını sağlayabilirsiniz. Genellikle benzersiz bir birincil anahtar, ayrı bir dizin araması olmadan kullanılabildiğinden ideal bir dezibiguator yapar. Kullanılabilir bir ayırma sütunu yoksa (örneğin, tablonun benzersiz anahtarı yoksa), medyanı hesaplamak için başka bir yaklaşım kullanılmalıdır, çünkü doğru şekilde işaret ettiğiniz gibi, DESC satır numaralarının ayna görüntüsü olduğunu garanti edemezseniz ASC satır numaraları, daha sonra sonuçlar tahmin edilemez.
Justin Grant

4
Teşekkürler, sütunları DB'ye değiştirirken, ilgili olmadığını düşünerek disambiguator'ı bıraktım. Bu durumda, bu çözüm gerçekten çok iyi çalışıyor.
Jonathan Beerhalter

8
Disambiguator ihtiyacını açıklayan kodun kendisine bir yorum eklemenizi öneririm.
hoffmanc

4
Müthiş! uzun süredir önemini biliyordum ama şimdi bir isim verebilirim ... dezavantajcı! Teşekkürler Justin!
CodeMonkey

204

SQL 2005 veya daha iyisini kullanıyorsanız, bu tablodaki tek bir sütun için güzel, basit-ish medyan bir hesaplamadır:

SELECT
(
 (SELECT MAX(Score) FROM
   (SELECT TOP 50 PERCENT Score FROM Posts ORDER BY Score) AS BottomHalf)
 +
 (SELECT MIN(Score) FROM
   (SELECT TOP 50 PERCENT Score FROM Posts ORDER BY Score DESC) AS TopHalf)
) / 2 AS Median

62
Medyan () toplama işlevi olmadığı için bu zekice ve nispeten basittir. Ama nasıl Medyan () işlevi yoktur !? Açıkçası biraz FLOOR () ed.
Charlie Kilian

Peki, güzel ve basit, ama genellikle belirli bir grup kategorisine göre medyan gerekir, yani select gid, median(score) from T group by gid. Bunun için ilişkili bir alt sorguya mı ihtiyacınız var?
TMS

1
... yani bu durumda olduğu gibi ("En yüksek medyan cevap skoruna sahip kullanıcılar" adlı 2. sorgu).
TMS

Tomas - "Belirli bir grup kategorisine göre" sorun bölümünüzü çözmeyi başardınız mı? Aynı problemim olduğu için. Teşekkürler.
Stu Harper

3
Bu çözümü GROUP BY ile nasıl kullanabilirim?
Przemyslaw Remin

82

SQL Server 2012'de PERCENTILE_CONT kullanmanız gerekir :

SELECT SalesOrderID, OrderQty,
    PERCENTILE_CONT(0.5) 
        WITHIN GROUP (ORDER BY OrderQty)
        OVER (PARTITION BY SalesOrderID) AS MedianCont
FROM Sales.SalesOrderDetail
WHERE SalesOrderID IN (43670, 43669, 43667, 43663)
ORDER BY SalesOrderID DESC

Ayrıca bkz: http://blog.sqlauthority.com/2011/11/20/sql-server-introduction-to-percentile_cont-analytic-functions-introduced-in-sql-server-2012/


12
Bu uzman analizi, düşük performans nedeniyle YÜZDEBİRLİK işlevlerine karşı zorlayıcı bir argüman yapar. sqlperformance.com/2012/08/t-sql-queries/median
carl.anderson

4
DISTINCTVeya eklemenize gerek yok GROUPY BY SalesOrderIDmu? Aksi takdirde çok sayıda yinelenen satırınız olur.
Konstantin

1
işte cevap. neden bu kadar
ilerlemek

Ayrıca gizli bir sürümü de varPERCENTILE_DISC
johnDanger

@ carl.anderson'ın yukarısındaki noktaya vurgu yaparak: PERCENTILE_CONT çözümü, SQL Server 2012'de kendi test şemalarında test ettikleri en hızlı çözümle karşılaştırıldığında 373 kat daha yavaş (!!!!) olarak ölçülmüştür. Daha fazla bilgi için carl'ın bağlı olduğu makaleyi okuyun.
Justin Grant

21

Orijinal hızlı cevabım şuydu:

select  max(my_column) as [my_column], quartile
from    (select my_column, ntile(4) over (order by my_column) as [quartile]
         from   my_table) i
--where quartile = 2
group by quartile

Bu size tek bir düşüşte orta ve çeyrekler arası menzili verecektir. Gerçekten sadece medyan bir satır istiyorsanız o zaman nerede yan tümcelerini uncomment.

Bunu bir açıklama planına yapıştırdığınızda, çalışmanın% 60'ı bu gibi pozisyona bağlı istatistikleri hesaplarken kaçınılmaz olan verileri sıralıyor.

Aşağıdaki yorumlarda Robert Ševčík-Robajz'ın mükemmel önerisini takip etmek için cevabı değiştirdim:

;with PartitionedData as
  (select my_column, ntile(10) over (order by my_column) as [percentile]
   from   my_table),
MinimaAndMaxima as
  (select  min(my_column) as [low], max(my_column) as [high], percentile
   from    PartitionedData
   group by percentile)
select
  case
    when b.percentile = 10 then cast(b.high as decimal(18,2))
    else cast((a.low + b.high)  as decimal(18,2)) / 2
  end as [value], --b.high, a.low,
  b.percentile
from    MinimaAndMaxima a
  join  MinimaAndMaxima b on (a.percentile -1 = b.percentile) or (a.percentile = 10 and b.percentile = 10)
--where b.percentile = 5

Bu, çift sayıda veri öğeniz olduğunda doğru medyan ve yüzdelik değerleri hesaplamalıdır. Yine, tüm persentil dağılımını değil, yalnızca medyanı istiyorsanız, final nerede yan tümcesini kaldırın.


1
Bu aslında oldukça iyi çalışıyor ve verilerin bölümlenmesine izin veriyor.
Jonathan Beerhalter

3
Birer birer kapalı olmak uygunsa, yukarıdaki sorgu iyidir. Ancak tam ortancaya ihtiyacınız varsa, sorun yaşayacaksınız. Örneğin, (1,3,5,7) dizisi için medyan 4'tür, ancak yukarıdaki sorgu 3 değerini döndürür. (1,2,3,503,603,703) için medyan 258'dir, ancak yukarıdaki sorgu 503'ü döndürür.
Justin Grant

1
Bir alt sorguda her dörtte birlik bölümün maksimum ve dakikasını alıp bir önceki sorgunun MAX'ını ve bir sonrakinin MIN'ini AVGing yaparak belirsizliğin kusurunu düzeltebilirsiniz?
Rbjz

18

Daha iyi:

SELECT @Median = AVG(1.0 * val)
FROM
(
    SELECT o.val, rn = ROW_NUMBER() OVER (ORDER BY o.val), c.c
    FROM dbo.EvenRows AS o
    CROSS JOIN (SELECT c = COUNT(*) FROM dbo.EvenRows) AS c
) AS x
WHERE rn IN ((c + 1)/2, (c + 2)/2);

Ustadan, Itzik Ben-Gan !



4

Basit, hızlı, doğru

SELECT x.Amount 
FROM   (SELECT amount, 
               Count(1) OVER (partition BY 'A')        AS TotalRows, 
               Row_number() OVER (ORDER BY Amount ASC) AS AmountOrder 
        FROM   facttransaction ft) x 
WHERE  x.AmountOrder = Round(x.TotalRows / 2.0, 0)  

4

SQL Server'da Toplama Oluştur işlevini kullanmak istiyorsanız, bu nasıl yapılır. Bu şekilde yapılması temiz sorgular yazabilme avantajına sahiptir. Bu işlemin bir Yüzdelik değeri oldukça kolay hesaplamak için uyarlanabileceğini unutmayın.

Yeni bir Visual Studio projesi oluşturun ve hedef çerçeveyi .NET 3.5 olarak ayarlayın (bu SQL 2008 içindir, SQL 2012'de farklı olabilir). Sonra bir sınıf dosyası oluşturun ve aşağıdaki kodu veya c # eşdeğerini girin:

Imports Microsoft.SqlServer.Server
Imports System.Data.SqlTypes
Imports System.IO

<Serializable>
<SqlUserDefinedAggregate(Format.UserDefined, IsInvariantToNulls:=True, IsInvariantToDuplicates:=False, _
  IsInvariantToOrder:=True, MaxByteSize:=-1, IsNullIfEmpty:=True)>
Public Class Median
  Implements IBinarySerialize
  Private _items As List(Of Decimal)

  Public Sub Init()
    _items = New List(Of Decimal)()
  End Sub

  Public Sub Accumulate(value As SqlDecimal)
    If Not value.IsNull Then
      _items.Add(value.Value)
    End If
  End Sub

  Public Sub Merge(other As Median)
    If other._items IsNot Nothing Then
      _items.AddRange(other._items)
    End If
  End Sub

  Public Function Terminate() As SqlDecimal
    If _items.Count <> 0 Then
      Dim result As Decimal
      _items = _items.OrderBy(Function(i) i).ToList()
      If _items.Count Mod 2 = 0 Then
        result = ((_items((_items.Count / 2) - 1)) + (_items(_items.Count / 2))) / 2@
      Else
        result = _items((_items.Count - 1) / 2)
      End If

      Return New SqlDecimal(result)
    Else
      Return New SqlDecimal()
    End If
  End Function

  Public Sub Read(r As BinaryReader) Implements IBinarySerialize.Read
    'deserialize it from a string
    Dim list = r.ReadString()
    _items = New List(Of Decimal)

    For Each value In list.Split(","c)
      Dim number As Decimal
      If Decimal.TryParse(value, number) Then
        _items.Add(number)
      End If
    Next

  End Sub

  Public Sub Write(w As BinaryWriter) Implements IBinarySerialize.Write
    'serialize the list to a string
    Dim list = ""

    For Each item In _items
      If list <> "" Then
        list += ","
      End If      
      list += item.ToString()
    Next
    w.Write(list)
  End Sub
End Class

Sonra derleyin ve DLL ve PDB dosyasını SQL Server makinenize kopyalayın ve SQL Server'da aşağıdaki komutu çalıştırın:

CREATE ASSEMBLY CustomAggregate FROM '{path to your DLL}'
WITH PERMISSION_SET=SAFE;
GO

CREATE AGGREGATE Median(@value decimal(9, 3))
RETURNS decimal(9, 3) 
EXTERNAL NAME [CustomAggregate].[{namespace of your DLL}.Median];
GO

Daha sonra medyanı şu şekilde hesaplamak için bir sorgu yazabilirsiniz: SELECT dbo.Median (Field) FROM Table


3

Medyan için set tabanlı bir çözüm ararken bu sayfaya rastladım. Buradaki bazı çözümlere baktıktan sonra aşağıdakileri buldum. Umut yardımcı olur / çalışır.

DECLARE @test TABLE(
    i int identity(1,1),
    id int,
    score float
)

INSERT INTO @test (id,score) VALUES (1,10)
INSERT INTO @test (id,score) VALUES (1,11)
INSERT INTO @test (id,score) VALUES (1,15)
INSERT INTO @test (id,score) VALUES (1,19)
INSERT INTO @test (id,score) VALUES (1,20)

INSERT INTO @test (id,score) VALUES (2,20)
INSERT INTO @test (id,score) VALUES (2,21)
INSERT INTO @test (id,score) VALUES (2,25)
INSERT INTO @test (id,score) VALUES (2,29)
INSERT INTO @test (id,score) VALUES (2,30)

INSERT INTO @test (id,score) VALUES (3,20)
INSERT INTO @test (id,score) VALUES (3,21)
INSERT INTO @test (id,score) VALUES (3,25)
INSERT INTO @test (id,score) VALUES (3,29)

DECLARE @counts TABLE(
    id int,
    cnt int
)

INSERT INTO @counts (
    id,
    cnt
)
SELECT
    id,
    COUNT(*)
FROM
    @test
GROUP BY
    id

SELECT
    drv.id,
    drv.start,
    AVG(t.score)
FROM
    (
        SELECT
            MIN(t.i)-1 AS start,
            t.id
        FROM
            @test t
        GROUP BY
            t.id
    ) drv
    INNER JOIN @test t ON drv.id = t.id
    INNER JOIN @counts c ON t.id = c.id
WHERE
    t.i = ((c.cnt+1)/2)+drv.start
    OR (
        t.i = (((c.cnt+1)%2) * ((c.cnt+2)/2))+drv.start
        AND ((c.cnt+1)%2) * ((c.cnt+2)/2) <> 0
    )
GROUP BY
    drv.id,
    drv.start

3

Aşağıdaki sorgu , bir sütundaki değer listesinden medyan değerini döndürür . Toplama işlevi olarak veya birlikte kullanılamaz, ancak yine de iç seçimde WHERE yan tümcesi içeren bir alt sorgu olarak kullanabilirsiniz.

SQL Server 2005+:

SELECT TOP 1 value from
(
    SELECT TOP 50 PERCENT value 
    FROM table_name 
    ORDER BY  value
)for_median
ORDER BY value DESC

3

Justin hibesinin çözümü sağlam görünse de, belirli bir bölüm anahtarında birkaç yinelenen değere sahip olduğunuzda, ASC yinelenen değerleri için satır numaralarının sıralamanın dışına çıktığını ve böylece düzgün hizalanmadıklarını buldum.

İşte benim sonucumdan bir parça:

KEY VALUE ROWA ROWD  

13  2     22   182
13  1     6    183
13  1     7    184
13  1     8    185
13  1     9    186
13  1     10   187
13  1     11   188
13  1     12   189
13  0     1    190
13  0     2    191
13  0     3    192
13  0     4    193
13  0     5    194

Justin'in kodunu bu çözümün temeli olarak kullandım. Birden fazla türetilmiş tablo kullanımı göz önüne alındığında verimli olmasa da, karşılaştığım satır sipariş sorununu çözer. T-SQL'de deneyimli olmadığım için herhangi bir iyileştirme memnuniyetle karşılanacaktır.

SELECT PKEY, cast(AVG(VALUE)as decimal(5,2)) as MEDIANVALUE
FROM
(
  SELECT PKEY,VALUE,ROWA,ROWD,
  'FLAG' = (CASE WHEN ROWA IN (ROWD,ROWD-1,ROWD+1) THEN 1 ELSE 0 END)
  FROM
  (
    SELECT
    PKEY,
    cast(VALUE as decimal(5,2)) as VALUE,
    ROWA,
    ROW_NUMBER() OVER (PARTITION BY PKEY ORDER BY ROWA DESC) as ROWD 

    FROM
    (
      SELECT
      PKEY, 
      VALUE,
      ROW_NUMBER() OVER (PARTITION BY PKEY ORDER BY VALUE ASC,PKEY ASC ) as ROWA 
      FROM [MTEST]
    )T1
  )T2
)T3
WHERE FLAG = '1'
GROUP BY PKEY
ORDER BY PKEY

2

Justin'in yukarıdaki örneği çok iyi. Ancak bu Birincil anahtar ihtiyacı çok açık bir şekilde belirtilmelidir. Ben anahtar olmadan vahşi bu kodu gördüm ve sonuçları kötü.

Percentile_Cont hakkında aldığım şikayet veri kümesinden gerçek bir değer vermeyecek olmasıdır. Veri kümesinden gerçek bir değer olan bir "medyan" değerine ulaşmak için Percentile_Disc kullanın.

SELECT SalesOrderID, OrderQty,
    PERCENTILE_DISC(0.5) 
        WITHIN GROUP (ORDER BY OrderQty)
        OVER (PARTITION BY SalesOrderID) AS MedianCont
FROM Sales.SalesOrderDetail
WHERE SalesOrderID IN (43670, 43669, 43667, 43663)
ORDER BY SalesOrderID DESC

2

UDF'de şunu yazın:

 Select Top 1 medianSortColumn from Table T
  Where (Select Count(*) from Table
         Where MedianSortColumn <
           (Select Count(*) From Table) / 2)
  Order By medianSortColumn

7
Eşit sayıda öğe olması durumunda, medyan, bu UDF tarafından kapsanmayan iki orta öğenin ortalamasıdır.
Yaakov Ellis

1
Tüm UDF'de yeniden yazabilir misiniz?
Przemyslaw Remin

2

Medyan Bulma

Bu, bir özelliğin medyanını bulmak için en basit yöntemdir.

Select round(S.salary,4) median from employee S where (select count(salary) from station where salary < S.salary ) = (select count(salary) from station where salary > S.salary)

satır sayısı eşit olduğunda dava nasıl ele alınır?
priojeet priyom


1

'Table1'den sürekli bir değişken / ölçü' col1 'için

select col1  
from
    (select top 50 percent col1, 
    ROW_NUMBER() OVER(ORDER BY col1 ASC) AS Rowa,
    ROW_NUMBER() OVER(ORDER BY col1 DESC) AS Rowd
    from table1 ) tmp
where tmp.Rowa = tmp.Rowd

1

COUNT toplamı kullanarak önce kaç satır olduğunu sayabilir ve @cnt adlı bir değişkende depolayabilirsiniz. Daha sonra, miktar sırasına göre kaç satırın atlanacağını (ofset değeri) ve kaçının filtreleneceğini (getirme değeri) belirlemek için OFFSET-FETCH filtresinin parametrelerini hesaplayabilirsiniz.

Atlanacak satır sayısı (@cnt - 1) / 2'dir. Tek bir sayı için bu hesaplamanın doğru olduğu açıktır, çünkü 2'ye bölmeden önce tek bir orta değer için 1'i çıkarırsınız.

İfadede kullanılan bölüm tamsayı bölümü olduğundan, bu çift sayı için de doğru çalışır; yani, çift sayımdan 1 çıkarılırken, tek bir değer kalır.

Bu tek değer 2'ye bölündüğünde, sonucun kesir kısmı (.5) kesilir. Getirilecek satır sayısı 2 - (@cnt% 2). Fikir, sayı garip olduğunda modulo işleminin sonucunun 1 olması ve 1 satır getirmeniz gerektiğidir. Sayım, modulo işleminin sonucu olsa bile 0'dır ve 2 satır getirmeniz gerekir. Modulo işleminin 1 veya 0 sonucunu 2'den çıkararak, sırasıyla istenen 1 veya 2'yi elde edersiniz. Son olarak, ortalama miktarı hesaplamak için, bir veya iki sonuç miktarını alın ve giriş tamsayı değerini aşağıdaki gibi sayısal bir değere dönüştürdükten sonra bir ortalama uygulayın:

DECLARE @cnt AS INT = (SELECT COUNT(*) FROM [Sales].[production].[stocks]);
SELECT AVG(1.0 * quantity) AS median
FROM ( SELECT quantity
FROM [Sales].[production].[stocks]
ORDER BY quantity
OFFSET (@cnt - 1) / 2 ROWS FETCH NEXT 2 - @cnt % 2 ROWS ONLY ) AS D;

0

Kendi başıma bir çözüm bulmak istedim, ama beynim takıldı ve yola düştü. Ben düşünüyorum çalıştığını, ama sabah anlatmaya bana sorma. : P

DECLARE @table AS TABLE
(
    Number int not null
);

insert into @table select 2;
insert into @table select 4;
insert into @table select 9;
insert into @table select 15;
insert into @table select 22;
insert into @table select 26;
insert into @table select 37;
insert into @table select 49;

DECLARE @Count AS INT
SELECT @Count = COUNT(*) FROM @table;

WITH MyResults(RowNo, Number) AS
(
    SELECT RowNo, Number FROM
        (SELECT ROW_NUMBER() OVER (ORDER BY Number) AS RowNo, Number FROM @table) AS Foo
)
SELECT AVG(Number) FROM MyResults WHERE RowNo = (@Count+1)/2 OR RowNo = ((@Count+1)%2) * ((@Count+2)/2)

0
--Create Temp Table to Store Results in
DECLARE @results AS TABLE 
(
    [Month] datetime not null
 ,[Median] int not null
);

--This variable will determine the date
DECLARE @IntDate as int 
set @IntDate = -13


WHILE (@IntDate < 0) 
BEGIN

--Create Temp Table
DECLARE @table AS TABLE 
(
    [Rank] int not null
 ,[Days Open] int not null
);

--Insert records into Temp Table
insert into @table 

SELECT 
    rank() OVER (ORDER BY DATEADD(mm, DATEDIFF(mm, 0, DATEADD(ss, SVR.close_date, '1970')), 0), DATEDIFF(day,DATEADD(ss, SVR.open_date, '1970'),DATEADD(ss, SVR.close_date, '1970')),[SVR].[ref_num]) as [Rank]
 ,DATEDIFF(day,DATEADD(ss, SVR.open_date, '1970'),DATEADD(ss, SVR.close_date, '1970')) as [Days Open]
FROM
 mdbrpt.dbo.View_Request SVR
 LEFT OUTER JOIN dbo.dtv_apps_systems vapp 
 on SVR.category = vapp.persid
 LEFT OUTER JOIN dbo.prob_ctg pctg 
 on SVR.category = pctg.persid
 Left Outer Join [mdbrpt].[dbo].[rootcause] as [Root Cause] 
 on [SVR].[rootcause]=[Root Cause].[id]
 Left Outer Join [mdbrpt].[dbo].[cr_stat] as [Status]
 on [SVR].[status]=[Status].[code]
 LEFT OUTER JOIN [mdbrpt].[dbo].[net_res] as [net] 
 on [net].[id]=SVR.[affected_rc]
WHERE
 SVR.Type IN ('P') 
 AND
 SVR.close_date IS NOT NULL 
 AND
 [Status].[SYM] = 'Closed'
 AND
 SVR.parent is null
 AND
 [Root Cause].[sym] in ( 'RC - Application','RC - Hardware', 'RC - Operational', 'RC - Unknown')
 AND
 (
  [vapp].[appl_name] in ('3PI','Billing Rpts/Files','Collabrent','Reports','STMS','STMS 2','Telco','Comergent','OOM','C3-BAU','C3-DD','DIRECTV','DIRECTV Sales','DIRECTV Self Care','Dealer Website','EI Servlet','Enterprise Integration','ET','ICAN','ODS','SB-SCM','SeeBeyond','Digital Dashboard','IVR','OMS','Order Services','Retail Services','OSCAR','SAP','CTI','RIO','RIO Call Center','RIO Field Services','FSS-RIO3','TAOS','TCS')
 OR
  pctg.sym in ('Systems.Release Health Dashboard.Problem','DTV QA Test.Enterprise Release.Deferred Defect Log')
 AND  
  [Net].[nr_desc] in ('3PI','Billing Rpts/Files','Collabrent','Reports','STMS','STMS 2','Telco','Comergent','OOM','C3-BAU','C3-DD','DIRECTV','DIRECTV Sales','DIRECTV Self Care','Dealer Website','EI Servlet','Enterprise Integration','ET','ICAN','ODS','SB-SCM','SeeBeyond','Digital Dashboard','IVR','OMS','Order Services','Retail Services','OSCAR','SAP','CTI','RIO','RIO Call Center','RIO Field Services','FSS-RIO3','TAOS','TCS')
 )
 AND
 DATEADD(mm, DATEDIFF(mm, 0, DATEADD(ss, SVR.close_date, '1970')), 0) = DATEADD(mm, DATEDIFF(mm,0,DATEADD(mm,@IntDate,getdate())), 0)
ORDER BY [Days Open]



DECLARE @Count AS INT
SELECT @Count = COUNT(*) FROM @table;

WITH MyResults(RowNo, [Days Open]) AS
(
    SELECT RowNo, [Days Open] FROM
        (SELECT ROW_NUMBER() OVER (ORDER BY [Days Open]) AS RowNo, [Days Open] FROM @table) AS Foo
)


insert into @results
SELECT 
 DATEADD(mm, DATEDIFF(mm,0,DATEADD(mm,@IntDate,getdate())), 0) as [Month]
 ,AVG([Days Open])as [Median] FROM MyResults WHERE RowNo = (@Count+1)/2 OR RowNo = ((@Count+1)%2) * ((@Count+2)/2) 


set @IntDate = @IntDate+1
DELETE FROM @table
END

select *
from @results
order by [Month]

0

SQL 2000 ile çalışır:

DECLARE @testTable TABLE 
( 
    VALUE   INT
)
--INSERT INTO @testTable -- Even Test
--SELECT 3 UNION ALL
--SELECT 5 UNION ALL
--SELECT 7 UNION ALL
--SELECT 12 UNION ALL
--SELECT 13 UNION ALL
--SELECT 14 UNION ALL
--SELECT 21 UNION ALL
--SELECT 23 UNION ALL
--SELECT 23 UNION ALL
--SELECT 23 UNION ALL
--SELECT 23 UNION ALL
--SELECT 29 UNION ALL
--SELECT 40 UNION ALL
--SELECT 56

--
--INSERT INTO @testTable -- Odd Test
--SELECT 3 UNION ALL
--SELECT 5 UNION ALL
--SELECT 7 UNION ALL
--SELECT 12 UNION ALL
--SELECT 13 UNION ALL
--SELECT 14 UNION ALL
--SELECT 21 UNION ALL
--SELECT 23 UNION ALL
--SELECT 23 UNION ALL
--SELECT 23 UNION ALL
--SELECT 23 UNION ALL
--SELECT 29 UNION ALL
--SELECT 39 UNION ALL
--SELECT 40 UNION ALL
--SELECT 56


DECLARE @RowAsc TABLE
(
    ID      INT IDENTITY,
    Amount  INT
)

INSERT INTO @RowAsc
SELECT  VALUE 
FROM    @testTable 
ORDER BY VALUE ASC

SELECT  AVG(amount)
FROM @RowAsc ra
WHERE ra.id IN
(
    SELECT  ID 
    FROM    @RowAsc
    WHERE   ra.id -
    (
        SELECT  MAX(id) / 2.0 
        FROM    @RowAsc
    ) BETWEEN 0 AND 1

)

0

Kendim gibi temelleri öğrenen yeni başlayanlar için, kişisel olarak bu örneği takip etmeyi daha kolay buluyorum, çünkü tam olarak ne olduğunu ve medyan değerlerin nereden geldiğini anlamak daha kolay ...

select
 ( max(a.[Value1]) + min(a.[Value1]) ) / 2 as [Median Value1]
,( max(a.[Value2]) + min(a.[Value2]) ) / 2 as [Median Value2]

from (select
    datediff(dd,startdate,enddate) as [Value1]
    ,xxxxxxxxxxxxxx as [Value2]
     from dbo.table1
     )a

Yukarıdaki kodların bazıları mutlak huşu içinde olsa !!!


0

Bu benim bulabildiğim kadar basit bir cevap. Verilerimle iyi çalıştı. Belirli değerleri hariç tutmak istiyorsanız, iç seçime bir nereye yan tümcesi ekleyin.

SELECT TOP 1 
    ValueField AS MedianValue
FROM
    (SELECT TOP(SELECT COUNT(1)/2 FROM tTABLE)
        ValueField
    FROM 
        tTABLE
    ORDER BY 
        ValueField) A
ORDER BY
    ValueField DESC

0

Aşağıdaki çözüm bu varsayımlar altında çalışır:

  • Yinelenen değer yok
  • NULL yok

Kod:

IF OBJECT_ID('dbo.R', 'U') IS NOT NULL
  DROP TABLE dbo.R

CREATE TABLE R (
    A FLOAT NOT NULL);

INSERT INTO R VALUES (1);
INSERT INTO R VALUES (2);
INSERT INTO R VALUES (3);
INSERT INTO R VALUES (4);
INSERT INTO R VALUES (5);
INSERT INTO R VALUES (6);

-- Returns Median(R)
select SUM(A) / CAST(COUNT(A) AS FLOAT)
from R R1 
where ((select count(A) from R R2 where R1.A > R2.A) = 
      (select count(A) from R R2 where R1.A < R2.A)) OR
      ((select count(A) from R R2 where R1.A > R2.A) + 1 = 
      (select count(A) from R R2 where R1.A < R2.A)) OR
      ((select count(A) from R R2 where R1.A > R2.A) = 
      (select count(A) from R R2 where R1.A < R2.A) + 1) ; 

0
DECLARE @Obs int
DECLARE @RowAsc table
(
ID      INT IDENTITY,
Observation  FLOAT
)
INSERT INTO @RowAsc
SELECT Observations FROM MyTable
ORDER BY 1 
SELECT @Obs=COUNT(*)/2 FROM @RowAsc
SELECT Observation AS Median FROM @RowAsc WHERE ID=@Obs

0

Birkaç alternatif ile deniyorum, ancak veri kayıtlarım tekrarlanan değerlere sahip olduğundan, ROW_NUMBER sürümleri benim için bir seçim değil gibi görünüyor. Yani burada kullandığım sorgu (NTILE ile bir sürüm):

SELECT distinct
   CustomerId,
   (
       MAX(CASE WHEN Percent50_Asc=1 THEN TotalDue END) OVER (PARTITION BY CustomerId)  +
       MIN(CASE WHEN Percent50_desc=1 THEN TotalDue END) OVER (PARTITION BY CustomerId) 
   )/2 MEDIAN
FROM
(
   SELECT
      CustomerId,
      TotalDue,
     NTILE(2) OVER (
         PARTITION BY CustomerId
         ORDER BY TotalDue ASC) AS Percent50_Asc,
     NTILE(2) OVER (
         PARTITION BY CustomerId
         ORDER BY TotalDue DESC) AS Percent50_desc
   FROM Sales.SalesOrderHeader SOH
) x
ORDER BY CustomerId;

0

Jeff Atwood'un yukarıdaki cevabına dayanarak, GROUP BY ve her bir grup için medyan almak için ilişkili bir alt sorgu ile.

SELECT TestID, 
(
 (SELECT MAX(Score) FROM
   (SELECT TOP 50 PERCENT Score FROM Posts WHERE TestID = Posts_parent.TestID ORDER BY Score) AS BottomHalf)
 +
 (SELECT MIN(Score) FROM
   (SELECT TOP 50 PERCENT Score FROM Posts WHERE TestID = Posts_parent.TestID ORDER BY Score DESC) AS TopHalf)
) / 2 AS MedianScore,
AVG(Score) AS AvgScore, MIN(Score) AS MinScore, MAX(Score) AS MaxScore
FROM Posts_parent
GROUP BY Posts_parent.TestID

0

Sıklıkla, Medyan'ı sadece tüm tablo için değil, aynı zamanda bazı kimliğe göre agregalar için de hesaplamamız gerekebilir. Başka bir deyişle, her kimliğin çok sayıda kayda sahip olduğu tablonuzdaki her kimlik için medyan hesaplayın. (@gdoron tarafından düzenlenen çözümü temel alır: iyi performans ve birçok SQL'de çalışır)

SELECT our_id, AVG(1.0 * our_val) as Median
FROM
( SELECT our_id, our_val, 
  COUNT(*) OVER (PARTITION BY our_id) AS cnt,
  ROW_NUMBER() OVER (PARTITION BY our_id ORDER BY our_val) AS rnk
  FROM our_table
) AS x
WHERE rnk IN ((cnt + 1)/2, (cnt + 2)/2) GROUP BY our_id;

Umarım yardımcı olur.


0

Sorunuz için Jeff Atwood zaten basit ve etkili bir çözüm sunmuştu. Ancak, medyanı hesaplamak için alternatif bir yaklaşım arıyorsanız, aşağıdaki SQL kodu size yardımcı olacaktır.

create table employees(salary int);

insert into employees values(8); insert into employees values(23); insert into employees values(45); insert into employees values(123); insert into employees values(93); insert into employees values(2342); insert into employees values(2238);

select * from employees;

declare @odd_even int; declare @cnt int; declare @middle_no int;


set @cnt=(select count(*) from employees); set @middle_no=(@cnt/2)+1; select @odd_even=case when (@cnt%2=0) THEN -1 ELse 0 END ;


 select AVG(tbl.salary) from  (select  salary,ROW_NUMBER() over (order by salary) as rno from employees group by salary) tbl  where tbl.rno=@middle_no or tbl.rno=@middle_no+@odd_even;

MySQL'de medyan hesaplamak istiyorsanız, bu github bağlantısı yararlı olacaktır.


0

Bu, aklıma gelen medyanları bulmak için en uygun çözümdür. Örnekteki isimler Justin örneğine dayanmaktadır. Sales.SalesOrderHeader tablosu için bir dizinin, bu sırada CustomerId ve TotalDue dizin sütunlarında bulunduğundan emin olun.

SELECT
 sohCount.CustomerId,
 AVG(sohMid.TotalDue) as TotalDueMedian
FROM 
(SELECT 
  soh.CustomerId,
  COUNT(*) as NumberOfRows
FROM 
  Sales.SalesOrderHeader soh 
GROUP BY soh.CustomerId) As sohCount
CROSS APPLY 
    (Select 
       soh.TotalDue
    FROM 
    Sales.SalesOrderHeader soh 
    WHERE soh.CustomerId = sohCount.CustomerId 
    ORDER BY soh.TotalDue
    OFFSET sohCount.NumberOfRows / 2 - ((sohCount.NumberOfRows + 1) % 2) ROWS 
    FETCH NEXT 1 + ((sohCount.NumberOfRows + 1) % 2) ROWS ONLY
    ) As sohMid
GROUP BY sohCount.CustomerId

GÜNCELLEME

Hangi yöntemin en iyi performansa sahip olduğundan emin değilim, bu yüzden bir yöntemdeki her üç yönteme ve her bir sorgunun toplu maliyetine dayalı sorgu çalıştırarak yöntemim Justin Grants ve Jeff Atwoods arasında bir karşılaştırma yaptım:

Dizin olmadan:

  • Benim% 30
  • Justin Grants% 13
  • Jeff Atwoods% 58

Ve endeks ile

  • Benimki% 3.
  • Justin% 10 veriyor
  • Jeff Atwoods% 87

Sonunda 7,2 milyon satır anlamına gelen 2 ile 512 arasında bir faktörle yaklaşık 14 000 satırdan daha fazla veri oluşturarak dizin varsa sorguların ne kadar iyi görmeye çalıştım. Not Tek bir kopya yaptığım her seferde benzersiz olan CustomeId alanının olduğundan emin oldum, bu nedenle CustomerId'ın benzersiz örneğine kıyasla satırların oranı sabit tutuldu. Bunu yaparken, daha sonra endeksi yeniden oluşturduğum infazları yürüttüm ve sonuçların bu değerlere sahip olduğum verilerle 128 civarında bir faktörde stabilize olduğunu fark ettim:

  • Benimki% 3.
  • Justin% 5 veriyor
  • Jeff Atwoods% 92

Performansın satır sayısını ölçeklendirerek ancak benzersiz CustomerId'i sabit tutarak nasıl etkilenebileceğini merak ettim, bu yüzden sadece bunu yaptığım yerde yeni bir test yaptım. Şimdi stabilize etmek yerine, parti maliyet oranı ayrışmaya devam etti, ayrıca bu benzersiz bir ID başına yaklaşık 10000 satır sonunda ortalama olarak Müşteri başına yaklaşık 20 satır yerine. Sayılar:

  • Benimki 4%
  • Justins% 60
  • Jeffs% 35

Sonuçları karşılaştırarak her yöntemi doğru uyguladığımdan emin oldum. Sonuç olarak, kullandığım yöntem, dizin mevcut olduğu sürece genellikle daha hızlıdır. Bu makalede, bu yöntemin bu sorun için önerilen yöntem olduğunu da fark ettiniz https://www.microsoftpressstore.com/articles/article.aspx?p=2314819&seqNum=5

Bu sorguya daha sonra yapılan çağrıların performansını daha da iyileştirmenin bir yolu, sayım bilgilerinin yardımcı bir tabloda kalmasını sağlamaktır. CustomerId'ye bağlı SalesOrderHeader satırlarının sayısı ile ilgili bilgileri güncelleyen ve saklayan bir tetikleyici ile bunu koruyabilirsiniz, elbette medyanı da basit bir şekilde saklayabilirsiniz.


Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.