Berfungsi untuk Menghitung Median di SQL Server

227

Menurut MSDN , Median tidak tersedia sebagai fungsi agregat di Transact-SQL. Namun, saya ingin mengetahui apakah mungkin untuk membuat fungsionalitas ini (menggunakan fungsi Buat Agregat , fungsi yang ditentukan pengguna, atau beberapa metode lain).

Apa yang akan menjadi cara terbaik (jika mungkin) untuk melakukan ini - memungkinkan untuk perhitungan nilai median (dengan asumsi tipe data numerik) dalam permintaan agregat?

Yaakov Ellis
sumber

Jawaban:

145

PEMBARUAN 2019: Dalam 10 tahun sejak saya menulis jawaban ini, lebih banyak solusi telah ditemukan yang dapat memberikan hasil yang lebih baik. Juga, rilis SQL Server sejak saat itu (terutama SQL 2012) telah memperkenalkan fitur T-SQL baru yang dapat digunakan untuk menghitung median. Rilis SQL Server juga telah meningkatkan optimizer kueri yang dapat memengaruhi berbagai solusi median. Net-net, posting asli 2009 saya masih OK tapi mungkin ada solusi yang lebih baik untuk aplikasi SQL Server modern. Lihatlah artikel ini dari 2012 yang merupakan sumber yang bagus: https://sqlperformance.com/2012/08/t-sql-queries/median

Artikel ini menemukan pola berikut ini jauh lebih cepat daripada semua alternatif lain, setidaknya pada skema sederhana yang mereka uji. Solusi ini 373x lebih cepat (!!!) daripada solusi paling lambat ( PERCENTILE_CONT) yang diuji. Perhatikan bahwa trik ini memerlukan dua kueri terpisah yang mungkin tidak praktis dalam semua kasus. Ini juga membutuhkan SQL 2012 atau yang lebih baru.

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;

Tentu saja, hanya karena satu tes pada satu skema di 2012 menghasilkan hasil yang bagus, jarak tempuh Anda dapat bervariasi, terutama jika Anda menggunakan SQL Server 2014 atau lebih baru. Jika perf penting untuk perhitungan median Anda, saya sangat menyarankan mencoba dan menguji beberapa opsi yang direkomendasikan dalam artikel itu untuk memastikan bahwa Anda telah menemukan yang terbaik untuk skema Anda.

Saya juga sangat berhati-hati dalam menggunakan fungsi (baru dalam SQL Server 2012) PERCENTILE_CONTyang direkomendasikan dalam salah satu jawaban lain untuk pertanyaan ini, karena artikel yang ditautkan di atas menemukan bahwa fungsi bawaan ini 373x lebih lambat daripada solusi tercepat. Mungkin perbedaan ini telah diperbaiki dalam 7 tahun sejak itu, tetapi secara pribadi saya tidak akan menggunakan fungsi ini di atas meja besar sampai saya memverifikasi kinerjanya vs solusi lain.

ASLI 2009 POST DI BAWAH INI:

Ada banyak cara untuk melakukan ini, dengan kinerja yang sangat beragam. Inilah salah satu solusi yang dioptimalkan dengan sangat baik, dari Medians, ROW_NUMBERs, dan kinerja . Ini adalah solusi yang sangat optimal ketika datang ke I / O aktual yang dihasilkan selama eksekusi - ini terlihat lebih mahal daripada solusi lain, tetapi sebenarnya jauh lebih cepat.

Halaman itu juga berisi diskusi tentang solusi lain dan detail pengujian kinerja. Perhatikan penggunaan kolom unik sebagai disambiguator jika ada beberapa baris dengan nilai yang sama dari kolom median.

Seperti halnya semua skenario kinerja database, selalu mencoba menguji solusi dengan data nyata pada perangkat keras nyata - Anda tidak pernah tahu kapan perubahan ke pengoptimal SQL Server atau kekhasan di lingkungan Anda akan membuat solusi yang biasanya cepat lebih lambat.

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;
Justin Grant
sumber
12
Saya tidak berpikir ini berfungsi jika Anda memiliki dupes, terutama banyak dupes, dalam data Anda. Anda tidak dapat menjamin row_number akan berbaris. Anda bisa mendapatkan jawaban yang benar-benar gila untuk median Anda, atau bahkan lebih buruk, tidak ada median sama sekali.
Jonathan Beerhalter
26
Itu sebabnya memiliki disambiguator (SalesOrderId dalam contoh kode di atas) penting, sehingga Anda dapat memastikan bahwa urutan baris hasil-set konsisten baik ke belakang dan ke depan. Seringkali kunci primer unik membuat disambiguator ideal karena tersedia tanpa indeks pencarian terpisah. Jika tidak ada kolom disambiguasi yang tersedia (misalnya, jika tabel tidak memiliki kunci uniquifying), maka pendekatan lain harus digunakan untuk menghitung median, karena seperti yang Anda tunjukkan dengan benar, jika Anda tidak dapat menjamin bahwa nomor baris DESC adalah gambar cermin dari Nomor baris ASC, maka hasilnya tidak dapat diprediksi.
Justin Grant
4
Terima kasih, ketika mengganti kolom ke DB saya, saya menjatuhkan disambiguator, berpikir itu tidak relevan. Dalam hal ini, solusi ini bekerja dengan sangat baik.
Jonathan Beerhalter
8
Saya sarankan menambahkan komentar ke kode itu sendiri, menjelaskan perlunya disambiguator.
hoffmanc
4
Luar biasa! sudah lama saya tahu pentingnya tetapi sekarang saya bisa memberikan nama ... disambiguator! Justin terima kasih!
CodeMonkey
204

Jika Anda menggunakan SQL 2005 atau lebih baik ini adalah perhitungan median bagus, sederhana untuk satu kolom dalam tabel:

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
Jeff Atwood
sumber
62
Itu pintar, dan relatif sederhana mengingat bahwa tidak ada fungsi agregat Median (). Tapi bagaimana mungkin tidak ada fungsi Median () !? Saya sedikit LANTAI () ed, terus terang.
Charlie Kilian
Yah, bagus dan sederhana, tetapi biasanya Anda perlu median per kategori grup tertentu, yaitu suka select gid, median(score) from T group by gid. Apakah Anda memerlukan subquery berkorelasi untuk itu?
TMS
1
... Maksud saya seperti dalam kasus ini (kueri ke-2 bernama "Pengguna dengan skor jawaban median tertinggi").
TMS
Tomas - apakah Anda berhasil menyelesaikan masalah plese "per kelompok tertentu"? Karena saya memiliki masalah yang sama. Terima kasih.
Stu Harper
3
Bagaimana cara menggunakan solusi ini dengan GROUP BY?
Przemyslaw Remin
82

Di SQL Server 2012 Anda harus menggunakan PERCENTILE_CONT :

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

Lihat juga: http://blog.sqlauthority.com/2011/11/20/sql-server-introduction-to-percentile_cont-analytic-functions-introduced-in-sql-server-2012/

Simon_Weaver
sumber
12
Analisis pakar ini membuat argumen yang meyakinkan terhadap fungsi PERCENTILE karena kinerjanya yang buruk. sqlperformance.com/2012/08/t-sql-queries/median
carl.anderson
4
Apakah Anda tidak perlu menambahkan DISTINCTatau GROUPY BY SalesOrderID? Kalau tidak, Anda akan memiliki banyak baris duplikat.
Konstantin
1
ini jawabannya. tidak tahu mengapa saya harus menggulir sejauh ini
FistOfFury
Ada juga versi diam-diam menggunakanPERCENTILE_DISC
johnDanger
menekankan poin @ carl.anderson di atas: solusi PERCENTILE_CONT diukur menjadi 373x lebih lambat (!!!!) dibandingkan dengan solusi tercepat yang mereka uji pada SQL Server 2012 pada skema pengujian khusus mereka. Baca artikel yang terhubung dengan carl untuk lebih jelasnya.
Justin Grant
21

Jawaban cepat asli saya adalah:

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

Ini akan memberi Anda kisaran median dan interkuartil dalam satu gerakan. Jika Anda benar-benar hanya ingin satu baris yang merupakan median maka batalkan komentar di mana klausa.

Ketika Anda memasukkannya ke dalam rencana penjelasan, 60% pekerjaannya menyortir data yang tidak dapat dihindari saat menghitung statistik dependen posisi seperti ini.

Saya telah mengubah jawaban untuk mengikuti saran luar biasa dari Robert Ševčík-Robajz dalam komentar di bawah:

;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

Ini harus menghitung nilai median dan persentil yang benar ketika Anda memiliki jumlah item data yang genap. Sekali lagi, batalkan komentar di mana klausa akhir jika Anda hanya ingin median dan bukan seluruh distribusi persentil.

Pak Wobin
sumber
1
Ini sebenarnya bekerja cukup baik, dan memungkinkan untuk mempartisi data.
Jonathan Beerhalter
3
Jika OK untuk dimatikan oleh satu, maka permintaan di atas baik-baik saja. Tetapi jika Anda membutuhkan median yang tepat, maka Anda akan mengalami kesulitan. Misalnya, untuk urutan (1,3,5,7) median adalah 4 tetapi kueri di atas mengembalikan 3. Untuk (1,2,3,503,603,703) median adalah 258 tetapi kueri di atas mengembalikan 503.
Justin Grant
1
Anda dapat memperbaiki cacat ketidaktepatan dengan mengambil maks dan min setiap kuartil dalam subquery, lalu AVGing MAX dari sebelumnya dan MIN berikutnya?
Rbjz
18

Bahkan lebih baik:

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

Dari sang master sendiri, Itzik Ben-Gan !

l --''''''---------------- '' '' '' '' '' '' '
sumber
8

MS SQL Server 2012 (dan yang lebih baru) memiliki fungsi PERCENTILE_DISC yang menghitung persentil tertentu untuk nilai yang diurutkan. PERCENTILE_DISC (0,5) akan menghitung median - https://msdn.microsoft.com/en-us/library/hh231327.aspx

enkryptor
sumber
4

Sederhana, cepat, akurat

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)  
Tobbi
sumber
4

Jika Anda ingin menggunakan fungsi Buat Agregat di SQL Server, ini adalah bagaimana melakukannya. Melakukannya dengan cara ini bermanfaat untuk dapat menulis pertanyaan yang bersih. Perhatikan bahwa proses ini dapat disesuaikan untuk menghitung nilai Persentil dengan cukup mudah.

Buat proyek Visual Studio baru dan atur kerangka target menjadi .NET 3.5 (ini untuk SQL 2008, mungkin berbeda di SQL 2012). Kemudian buat file kelas dan masukkan kode berikut, atau setara dengan c #:

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

Kemudian kompilasi dan salin file DLL dan PDB ke mesin SQL Server Anda dan jalankan perintah berikut dalam SQL Server:

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

Anda kemudian dapat menulis kueri untuk menghitung median seperti ini: SELECT dbo.Median (Field) FROM Table

Rono
sumber
3

Saya baru saja menemukan halaman ini sambil mencari solusi berbasis set untuk median. Setelah melihat beberapa solusi di sini, saya datang dengan yang berikut ini. Harapan itu membantu / bekerja.

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
brian
sumber
3

Kueri berikut mengembalikan median dari daftar nilai dalam satu kolom. Itu tidak dapat digunakan sebagai atau bersama dengan fungsi agregat, tetapi Anda masih dapat menggunakannya sebagai sub-kueri dengan klausa WHERE di pilih dalam.

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
PyQL
sumber
3

Meskipun solusi Justin grant tampak solid, saya menemukan bahwa ketika Anda memiliki sejumlah nilai duplikat di dalam kunci partisi yang diberikan, nomor baris untuk nilai duplikat ASC berakhir di luar urutan sehingga mereka tidak benar menyelaraskan.

Ini adalah bagian dari hasil saya:

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

Saya menggunakan kode Justin sebagai dasar untuk solusi ini. Meskipun tidak seefisien mengingat penggunaan beberapa tabel turunan, ia menyelesaikan masalah pemesanan baris yang saya temui. Setiap perbaikan akan disambut baik karena saya tidak berpengalaman dalam T-SQL.

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
Jeff Sisson
sumber
2

Contoh Justin di atas sangat bagus. Tetapi kebutuhan kunci Primer itu harus dinyatakan dengan sangat jelas. Saya telah melihat kode di alam bebas tanpa kunci dan hasilnya buruk.

Keluhan yang saya dapatkan tentang Percentile_Cont adalah bahwa ia tidak akan memberi Anda nilai aktual dari dataset. Untuk mendapatkan "median" yang merupakan nilai aktual dari dataset gunakan Percentile_Disc.

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
Brian Nordberg
sumber
2

Dalam UDF, tulis:

 Select Top 1 medianSortColumn from Table T
  Where (Select Count(*) from Table
         Where MedianSortColumn <
           (Select Count(*) From Table) / 2)
  Order By medianSortColumn
Charles Bretana
sumber
7
Dalam hal jumlah item genap, median adalah rata-rata dari dua item tengah, yang tidak tercakup oleh UDF ini.
Yaakov Ellis
1
Bisakah Anda menulis ulang di seluruh UDF?
Przemyslaw Remin
2

Temuan Median

Ini adalah metode paling sederhana untuk menemukan median atribut.

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)
Nivesh Krishna
sumber
bagaimana sampai menangani kasus ketika jumlah baris genap?
priojeet priyom
1

Untuk variabel kontinu / ukur 'col1' dari 'table1'

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
karishma kavle
sumber
1

Menggunakan agregat COUNT, Anda dapat menghitung berapa banyak baris yang ada dan menyimpan dalam variabel yang disebut @cnt. Kemudian Anda dapat menghitung parameter untuk filter OFFSET-FETCH untuk menentukan, berdasarkan urutan qty, berapa banyak baris untuk dilewati (nilai offset) dan berapa banyak untuk menyaring (mengambil nilai).

Jumlah baris yang dilewati adalah (@cnt - 1) / 2. Jelas bahwa untuk hitungan ganjil perhitungan ini benar karena Anda pertama-tama mengurangi 1 untuk nilai tengah tunggal, sebelum Anda bagi dengan 2.

Ini juga berfungsi dengan benar untuk penghitungan genap karena pembagian yang digunakan dalam ekspresi adalah pembagian bilangan bulat; jadi, saat mengurangkan 1 dari hitungan genap, Anda memiliki nilai ganjil.

Ketika membagi nilai ganjil itu dengan 2, bagian fraksi dari hasil (0,5) terpotong. Jumlah baris yang akan diambil adalah 2 - (@cnt% 2). Idenya adalah ketika hitungannya ganjil, hasil operasi modulo adalah 1, dan Anda harus mengambil 1 baris. Ketika hitungan bahkan hasil operasi modulo adalah 0, dan Anda perlu mengambil 2 baris. Dengan mengurangi hasil 1 atau 0 dari operasi modulo dari 2, Anda mendapatkan masing-masing 1 atau 2 yang diinginkan. Akhirnya, untuk menghitung kuantitas median, ambil satu atau dua jumlah hasil, dan terapkan rata-rata setelah mengonversi nilai integer input ke numerik sebagai berikut:

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;
Amira Bedhiafi
sumber
0

Saya ingin mencari solusi sendiri, tetapi otak saya tersandung dan jatuh di jalan. Saya pikir itu berhasil, tetapi jangan meminta saya untuk menjelaskannya di pagi hari. : 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)
Gavin
sumber
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]
Gregg Silverman
sumber
0

Ini bekerja dengan SQL 2000:

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

)
SQLMason
sumber
0

Untuk pemula seperti saya yang mempelajari dasar-dasarnya, saya pribadi menemukan contoh ini lebih mudah diikuti, karena lebih mudah untuk memahami apa yang terjadi dan dari mana nilai median berasal ...

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

Sangat mengagumi beberapa kode di atas !!!

Justine
sumber
0

Ini sesederhana jawaban yang bisa saya berikan. Bekerja dengan baik dengan data saya. Jika Anda ingin mengecualikan nilai-nilai tertentu, tambahkan saja klausa where ke inner select.

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
John P.
sumber
0

Solusi berikut berfungsi berdasarkan asumsi ini:

  • Tidak ada nilai duplikat
  • Tidak ada NULL

Kode:

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) ; 
Maria Ines Parnisari
sumber
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
Arie Yehieli
sumber
0

Saya mencoba dengan beberapa alternatif, tetapi karena catatan data saya memiliki nilai berulang, versi ROW_NUMBER tampaknya bukan pilihan bagi saya. Jadi di sini kueri yang saya gunakan (versi dengan NTILE):

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;
Galled
sumber
0

Membangun jawaban Jeff Atwood di atas di sini adalah dengan GROUP BY dan subquery yang berkorelasi untuk mendapatkan median untuk setiap grup.

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
Jim B
sumber
0

Seringkali, kita mungkin perlu menghitung Median tidak hanya untuk seluruh tabel, tetapi untuk agregat sehubungan dengan beberapa ID. Dengan kata lain, hitung median untuk setiap ID di tabel kami, di mana setiap ID memiliki banyak catatan. (berdasarkan solusi yang diedit oleh @gdoron: kinerja bagus dan berfungsi di banyak SQL)

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;

Semoga ini bisa membantu.

Danylo Zherebetskyy
sumber
0

Untuk pertanyaan Anda, Jeff Atwood sudah memberikan solusi sederhana dan efektif. Tetapi, jika Anda mencari beberapa pendekatan alternatif untuk menghitung median, kode SQL di bawah ini akan membantu Anda.

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;

Jika Anda ingin menghitung median di MySQL, tautan github ini akan berguna.

Veeramani Natarajan
sumber
0

Ini adalah solusi paling optimal untuk menemukan median yang bisa saya pikirkan. Nama-nama dalam contoh ini didasarkan pada contoh Justin. Pastikan indeks untuk tabel Sales.SalesOrderHeader ada dengan kolom indeks CustomerId dan TotalDue dalam urutan itu.

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

MEMPERBARUI

Saya agak tidak yakin tentang metode mana yang memiliki kinerja terbaik, jadi saya melakukan perbandingan antara metode saya Justin Grants dan Jeff Atwoods dengan menjalankan kueri berdasarkan ketiga metode dalam satu batch dan biaya batch dari setiap kueri adalah:

Tanpa indeks:

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

Dan dengan indeks

  • Tambang 3%.
  • Justin Grants 10%
  • Jeff Atwoods 87%

Saya mencoba melihat seberapa baik skala kueri jika Anda memiliki indeks dengan membuat lebih banyak data dari sekitar 14.000 baris dengan faktor 2 hingga 512 yang berarti pada akhirnya sekitar 7,2 juta baris. Catatan saya memastikan bidang CustomeId di mana unik untuk setiap kali saya melakukan satu salinan, sehingga proporsi baris dibandingkan dengan contoh unik CustomerId tetap konstan. Ketika saya melakukan ini, saya menjalankan eksekusi di mana saya membangun kembali indeks setelah itu, dan saya perhatikan hasilnya stabil di sekitar faktor 128 dengan data yang saya miliki untuk nilai-nilai ini:

  • Tambang 3%.
  • Justin Grants 5%
  • Jeff Atwoods 92%

Saya bertanya-tanya bagaimana kinerja dapat dipengaruhi oleh penskalaan jumlah baris tetapi menjaga CustomerId unik konstan, jadi saya menyiapkan tes baru di mana saya melakukan ini. Sekarang alih-alih menstabilkan, rasio biaya batch terus menyimpang, juga bukannya sekitar 20 baris per CustomerId per rata-rata saya pada akhirnya sekitar 10.000 baris per Id unik tersebut. Angka-angka di mana:

  • Tambang 4%
  • Justin 60%
  • Jeffs 35%

Saya memastikan saya menerapkan setiap metode dengan benar dengan membandingkan hasilnya. Kesimpulan saya adalah metode yang saya gunakan umumnya lebih cepat selama indeks ada. Juga memperhatikan bahwa metode ini adalah apa yang direkomendasikan untuk masalah khusus ini dalam artikel ini https://www.microsoftpressstore.com/articles/article.aspx?p=2314819&seqNum=5

Cara untuk lebih meningkatkan kinerja panggilan berikutnya ke permintaan ini lebih jauh adalah dengan tetap menggunakan informasi jumlah dalam tabel tambahan. Anda bahkan dapat mempertahankannya dengan memiliki pemicu yang memutakhirkan dan menyimpan informasi mengenai jumlah baris SalesOrderHeader tergantung pada CustomerId, tentu saja Anda kemudian dapat menyimpan median juga.

Kaveh Hadjari
sumber
0

Untuk dataset skala besar, Anda dapat mencoba GIST ini:

https://gist.github.com/chrisknoll/1b38761ce8c5016ec5b2

Ia bekerja dengan menggabungkan nilai-nilai berbeda yang akan Anda temukan di set Anda (seperti usia, atau tahun kelahiran, dll.), Dan menggunakan fungsi-fungsi jendela SQL untuk menemukan posisi persentil yang Anda tentukan dalam kueri.

Chris Knoll
sumber