Chức năng tính toán trung vị trong SQL Server


227

Theo MSDN , Median không có sẵn dưới dạng hàm tổng hợp trong Transact-SQL. Tuy nhiên, tôi muốn tìm hiểu xem có thể tạo chức năng này không (sử dụng chức năng Tạo tổng hợp , chức năng do người dùng xác định hoặc một số phương pháp khác).

Điều gì sẽ là cách tốt nhất (nếu có thể) để làm điều này - cho phép tính giá trị trung bình (giả sử kiểu dữ liệu số) trong truy vấn tổng hợp?


Câu trả lời:


145

CẬP NHẬT 2019: Trong 10 năm kể từ khi tôi viết câu trả lời này, nhiều giải pháp đã được phát hiện có thể mang lại kết quả tốt hơn. Ngoài ra, SQL Server phát hành kể từ đó (đặc biệt là SQL 2012) đã giới thiệu các tính năng T-SQL mới có thể được sử dụng để tính toán trung vị. Các bản phát hành SQL Server cũng đã cải thiện trình tối ưu hóa truy vấn của nó, điều này có thể ảnh hưởng đến sự hoàn hảo của các giải pháp trung bình khác nhau. Net-net, bài đăng gốc năm 2009 của tôi vẫn ổn nhưng có thể có các giải pháp tốt hơn cho các ứng dụng SQL Server hiện đại. Hãy xem bài viết này từ năm 2012, đây là một tài nguyên tuyệt vời: https://sqlperformance.com/2012/08/t-sql-queries/median

Bài viết này đã tìm thấy mô hình sau đây nhanh hơn nhiều so với tất cả các lựa chọn thay thế khác, ít nhất là trên lược đồ đơn giản mà họ đã thử nghiệm. Giải pháp này nhanh hơn 373 lần (!!!) so với PERCENTILE_CONTgiải pháp chậm nhất ( ) được thử nghiệm. Lưu ý rằng thủ thuật này yêu cầu hai truy vấn riêng biệt có thể không thực tế trong mọi trường hợp. Nó cũng yêu cầu SQL 2012 trở lên.

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;

Tất nhiên, chỉ vì một thử nghiệm trên một lược đồ vào năm 2012 mang lại kết quả tuyệt vời, số dặm của bạn có thể thay đổi, đặc biệt nếu bạn sử dụng SQL Server 2014 trở lên. Nếu sự hoàn hảo là quan trọng đối với tính toán trung bình của bạn, tôi thực sự khuyên bạn nên thử và kiểm tra hoàn hảo một số tùy chọn được đề xuất trong bài viết đó để đảm bảo rằng bạn đã tìm thấy lựa chọn tốt nhất cho lược đồ của mình.

Tôi cũng đặc biệt cẩn thận khi sử dụng hàm (mới trong SQL Server 2012) được PERCENTILE_CONTđề xuất trong một trong những câu trả lời khác cho câu hỏi này, vì bài viết được liên kết ở trên thấy hàm tích hợp này chậm hơn 373 lần so với giải pháp nhanh nhất. Có thể sự chênh lệch này đã được cải thiện trong 7 năm kể từ đó, nhưng cá nhân tôi sẽ không sử dụng chức năng này trên một bảng lớn cho đến khi tôi xác minh hiệu suất của nó so với các giải pháp khác.

BÀI VIẾT GỐC 2009 LÀ DƯỚI ĐÂY:

Có rất nhiều cách để làm điều này, với hiệu suất thay đổi đáng kể. Đây là một giải pháp được tối ưu hóa đặc biệt, từ Median, ROW_NUMBER và hiệu suất . Đây là một giải pháp đặc biệt tối ưu khi nói đến I / O thực tế được tạo ra trong quá trình thực thi - nó có vẻ tốn kém hơn các giải pháp khác, nhưng nó thực sự nhanh hơn nhiều.

Trang đó cũng chứa một cuộc thảo luận về các giải pháp và chi tiết kiểm tra hiệu suất khác. Lưu ý việc sử dụng một cột duy nhất làm bộ phân tán trong trường hợp có nhiều hàng có cùng giá trị của cột trung bình.

Như với tất cả các kịch bản hiệu suất cơ sở dữ liệu, luôn cố gắng thử nghiệm giải pháp với dữ liệu thực trên phần cứng thực - bạn không bao giờ biết khi nào thay đổi đối với trình tối ưu hóa của SQL Server hoặc đặc thù trong môi trường của bạn sẽ khiến giải pháp nhanh hơn bình thường trở nên chậm hơn.

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
Tôi không nghĩ rằng điều này hoạt động nếu bạn có bản sao, đặc biệt là nhiều bản sao, trong dữ liệu của bạn. Bạn không thể đảm bảo row_numbers sẽ xếp hàng. Bạn có thể nhận được một số câu trả lời thực sự điên rồ cho trung vị của bạn, hoặc thậm chí tệ hơn, không có trung bình nào cả.
Jonathan Biahalter

26
Đó là lý do tại sao việc có một bộ phân phối (SalesOrderId trong ví dụ mã ở trên) rất quan trọng, vì vậy bạn có thể đảm bảo rằng thứ tự của các hàng trong tập kết quả là nhất quán cả về phía trước và phía trước. Thông thường, một khóa chính duy nhất tạo ra một bộ định hướng lý tưởng bởi vì nó có sẵn mà không cần tra cứu chỉ mục riêng. Nếu không có cột định hướng khả dụng (ví dụ: nếu bảng không có khóa xác định), thì phải sử dụng một cách tiếp cận khác để tính trung bình, vì khi bạn chỉ ra chính xác, nếu bạn không thể đảm bảo rằng các số hàng của DESC là hình ảnh phản chiếu của Số hàng ASC, sau đó kết quả là không thể đoán trước.
Justin Grant

4
Cảm ơn, khi chuyển đổi các cột sang DB của tôi, tôi đã bỏ bộ định hướng, nghĩ rằng nó không liên quan. Trong trường hợp đó, giải pháp này hoạt động thực sự tốt.
Jonathan Biahalter

8
Tôi đề nghị thêm một nhận xét vào chính mã, mô tả sự cần thiết của bộ giải mã.
hoffmanc

4
Tuyệt vời! từ lâu tôi đã biết tầm quan trọng của nó nhưng bây giờ tôi có thể đặt cho nó một cái tên ... người định hướng! Cảm ơn Justin!
CodeMonkey

204

Nếu bạn đang sử dụng SQL 2005 hoặc tốt hơn thì đây là một phép tính trung bình đơn giản, đẹp mắt cho một cột trong bảng:

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
Điều đó thật thông minh và tương đối đơn giản khi không tồn tại hàm tổng hợp Median (). Nhưng làm thế nào mà không có hàm Median () tồn tại!? Tôi là một chút FLOOR () ed, thẳng thắn.
Charlie Kilian

Chà, đẹp và đơn giản, nhưng thông thường bạn cần trung bình cho mỗi loại nhóm nhất định, tức là thích select gid, median(score) from T group by gid. Bạn có cần một truy vấn con tương quan cho điều đó?
TMS

1
... Ý tôi là như thế trong trường hợp này (truy vấn thứ 2 có tên "Người dùng có điểm trả lời trung bình cao nhất").
TMS

Tomas - bạn đã quản lý để giải quyết vấn đề "theo từng nhóm nhất định" chưa? Như tôi có cùng một vấn đề. Cảm ơn.
Stu Harper

3
Làm thế nào để sử dụng giải pháp này với NHÓM THEO?
Przemyslaw Remin

82

Trong SQL Server 2012, bạn nên sử dụng 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

Xem thêm: http://blog.sqlauthority.com/2011/11/20/sql-server-intributiontion-to-percentile_cont-analytic-fifts-int sinhed-in-sql-server-2012 /


12
Phân tích chuyên gia này đưa ra một lập luận thuyết phục chống lại các chức năng PERCENTILE do hiệu suất kém. sqlperformance.com/2012/08/t-sql-queries/median
carl.anderson

4
Bạn không cần thêm DISTINCThay GROUPY BY SalesOrderID? Nếu không, bạn sẽ có rất nhiều hàng trùng lặp.
Konstantin

1
đây là câu trả lời. không biết tại sao tôi phải di chuyển xa đến thế
FistOfFury

Ngoài ra còn có một phiên bản kín đáo bằng cách sử dụngPERCENTILE_DISC
johnDanger

nhấn mạnh quan điểm của @ carl.anderson ở trên: một giải pháp PERCENTILE_CONT được đo là chậm hơn 373 lần (!!!!) so với giải pháp nhanh nhất mà họ đã thử nghiệm trên SQL Server 2012 trên lược đồ thử nghiệm cụ thể của họ. Đọc bài viết mà carl liên kết để biết thêm chi tiết.
Justin Grant

21

Câu trả lời nhanh ban đầu của tôi là:

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

Điều này sẽ cung cấp cho bạn phạm vi trung bình và liên vùng trong một cú trượt ngã. Nếu bạn thực sự chỉ muốn một hàng là trung tuyến thì bỏ qua mệnh đề where.

Khi bạn đưa nó vào một kế hoạch giải thích, 60% công việc là sắp xếp dữ liệu không thể tránh khỏi khi tính toán thống kê phụ thuộc vị trí như thế này.

Tôi đã sửa đổi câu trả lời để làm theo gợi ý tuyệt vời từ Robert evčík-Robajz trong các bình luận dưới đây:

;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

Điều này sẽ tính toán các giá trị trung bình và phần trăm chính xác khi bạn có số lượng mục dữ liệu chẵn. Một lần nữa, bỏ ghi chú mệnh đề where nếu bạn chỉ muốn trung bình và không phải toàn bộ phân phối phần trăm.


1
Điều này thực sự hoạt động khá tốt, và cho phép phân vùng dữ liệu.
Jonathan Biahalter

3
Nếu nó không bị tắt bởi một, thì truy vấn trên là ổn. Nhưng nếu bạn cần trung bình chính xác, thì bạn sẽ gặp rắc rối. Ví dụ: đối với chuỗi (1,3,5,7) trung vị là 4 nhưng truy vấn trên trả về 3. Với (1,2,3,503,603,703) trung vị là 258 nhưng truy vấn trên trả về 503.
Justin Grant

1
Bạn có thể khắc phục lỗ hổng của sự thiếu chính xác bằng cách lấy tối đa và tối thiểu của mỗi phần tư trong một truy vấn con, sau đó AVGing MAX của phần trước và MIN của phần tiếp theo?
Rbjz

18

Thậm chí còn tốt hơn:

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

Từ chính chủ nhân, Itzik Ben-Gan !


8

MS SQL Server 2012 (và mới hơn) có hàm PERCENTILE_DISC tính toán một tỷ lệ phần trăm cụ thể cho các giá trị được sắp xếp. PERCENTILE_DISC (0,5) sẽ tính toán trung vị - https://msdn.microsoft.com/en-us/l Library / hh231327.aspx


4

Đơn giản, nhanh chóng, chính xác

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

Nếu bạn muốn sử dụng chức năng Tạo tổng hợp trong SQL Server, đây là cách thực hiện. Làm theo cách này có lợi ích là có thể viết các truy vấn sạch. Lưu ý quá trình này có thể được điều chỉnh để tính giá trị Phần trăm khá dễ dàng.

Tạo một dự án Visual Studio mới và đặt khung mục tiêu thành .NET 3.5 (đây là phiên bản SQL 2008, nó có thể khác trong SQL 2012). Sau đó tạo một tệp lớp và đặt mã sau hoặc tương đương 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

Sau đó biên dịch nó và sao chép tệp DLL và PDB vào máy chủ SQL của bạn và chạy lệnh sau trong 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

Sau đó, bạn có thể viết một truy vấn để tính toán trung vị như thế này: CHỌN dbo.Median (Trường) TỪ Bảng


3

Tôi vừa xem qua trang này trong khi tìm kiếm một giải pháp dựa trên tập hợp. Sau khi xem xét một số giải pháp ở đây, tôi đã đưa ra những điều sau đây. Hy vọng là giúp / làm việc.

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

Truy vấn sau đây trả về giá trị trung bình từ danh sách các giá trị trong một cột. Nó không thể được sử dụng như hoặc cùng với một hàm tổng hợp, nhưng bạn vẫn có thể sử dụng nó như một truy vấn phụ với mệnh đề WHERE trong phần chọn bên trong.

Máy chủ SQL 2005 trở lên:

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

3

Mặc dù giải pháp của Justin Grant có vẻ chắc chắn nhưng tôi thấy rằng khi bạn có một số giá trị trùng lặp trong một khóa phân vùng nhất định, các số hàng cho các giá trị trùng lặp ASC sẽ kết thúc theo thứ tự để chúng không được căn chỉnh chính xác.

Đây là một đoạn từ kết quả của tôi:

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

Tôi đã sử dụng mã của Justin làm cơ sở cho giải pháp này. Mặc dù không hiệu quả bằng việc sử dụng nhiều bảng dẫn xuất nhưng nó giải quyết được vấn đề đặt hàng mà tôi gặp phải. Mọi cải tiến sẽ được hoan nghênh vì tôi không có kinh nghiệm về 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

2

Ví dụ của Justin ở trên là rất tốt. Nhưng nhu cầu chính đó cần được nêu rất rõ ràng. Tôi đã thấy mã đó trong tự nhiên mà không có khóa và kết quả rất tệ.

Khiếu nại tôi nhận được về Percentile_Cont là nó sẽ không cung cấp cho bạn một giá trị thực tế từ bộ dữ liệu. Để có được "trung vị" là giá trị thực từ bộ dữ liệu, hãy sử dụng 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

2

Trong UDF, viết:

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

7
Trong trường hợp số lượng vật phẩm chẵn, trung vị là trung bình của hai vật phẩm ở giữa, không thuộc phạm vi UDF này.
Yaakov Ellis

1
Bạn có thể viết lại nó trong toàn bộ UDF không?
Przemyslaw Remin

2

Tìm kiếm trung bình

Đây là phương pháp đơn giản nhất để tìm trung vị của một thuộc tính.

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)

Làm thế nào sẽ xử lý trường hợp khi số lượng hàng là chẵn?
tiên tri


1

Đối với một biến liên tục / số đo 'col1' từ 'bảng1'

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

Sử dụng tổng hợp COUNT, trước tiên bạn có thể đếm có bao nhiêu hàng và lưu trữ trong một biến có tên là @cnt. Sau đó, bạn có thể tính toán các tham số cho bộ lọc OFFSET-FETCH để chỉ định, dựa trên thứ tự qty, số lượng hàng cần bỏ qua (giá trị bù) và số lượng để lọc (giá trị tìm nạp).

Số lượng hàng cần bỏ qua là (@cnt - 1) / 2. Rõ ràng rằng đối với một số lẻ, phép tính này là chính xác vì trước tiên bạn trừ 1 cho giá trị trung bình duy nhất, trước khi bạn chia cho 2.

Điều này cũng hoạt động chính xác cho một số chẵn bởi vì phép chia được sử dụng trong biểu thức là phép chia số nguyên; vì vậy, khi trừ 1 từ số chẵn, bạn còn lại một giá trị lẻ.

Khi chia giá trị lẻ đó cho 2, phần phân số của kết quả (.5) bị cắt bớt. Số lượng hàng cần tìm là 2 - (@cnt% 2). Ý tưởng là khi số lẻ là kết quả của phép toán modulo là 1 và bạn cần tìm nạp 1 hàng. Khi số đếm thậm chí là kết quả của phép toán modulo bằng 0 và bạn cần tìm nạp 2 hàng. Bằng cách trừ kết quả 1 hoặc 0 của phép toán modulo từ 2, bạn sẽ có được 1 hoặc 2 mong muốn tương ứng. Cuối cùng, để tính đại lượng trung bình, lấy một hoặc hai đại lượng kết quả và áp dụng trung bình sau khi chuyển đổi giá trị nguyên đầu vào thành số như sau:

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

Tôi muốn tự mình tìm ra giải pháp, nhưng não tôi bị vấp và ngã trên đường. Tôi nghĩ rằng nó hoạt động, nhưng đừng yêu cầu tôi giải thích nó vào buổi sáng. : 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

Điều này hoạt động với 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

)

0

Đối với những người mới như tôi đang học những điều cơ bản, cá nhân tôi thấy ví dụ này dễ theo dõi hơn, vì dễ hiểu chính xác hơn những gì đang xảy ra và nơi các giá trị trung bình đến từ ...

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

Mặc dù rất sợ một số mã ở trên !!!


0

Đây là một câu trả lời đơn giản như tôi có thể đưa ra. Làm việc tốt với dữ liệu của tôi. Nếu bạn muốn loại trừ các giá trị nhất định, chỉ cần thêm mệnh đề where vào phần chọn bên trong.

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

Giải pháp sau đây hoạt động theo các giả định sau:

  • Không có giá trị trùng lặp
  • Không có NULL

Mã số:

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

Tôi thử với một vài lựa chọn thay thế, nhưng do các bản ghi dữ liệu của tôi có các giá trị lặp lại, các phiên bản ROW_NUMBER dường như không phải là một lựa chọn cho tôi. Vì vậy, ở đây truy vấn tôi đã sử dụng (một phiên bản với 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;

0

Dựa trên câu trả lời của Jeff Atwood ở trên, đây là với GROUP BY và một truy vấn con tương quan để lấy trung vị cho mỗi nhóm.

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

Thông thường, chúng ta có thể cần tính toán Median không chỉ cho toàn bộ bảng, mà còn cho các tổng hợp liên quan đến một số ID. Nói cách khác, tính toán trung vị cho mỗi ID trong bảng của chúng tôi, trong đó mỗi ID có nhiều bản ghi. (dựa trên giải pháp được chỉnh sửa bởi @gdoron: hiệu suất tốt và hoạt động trong nhiều 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;

Hy vọng nó giúp.


0

Đối với câu hỏi của bạn, Jeff Atwood đã đưa ra giải pháp đơn giản và hiệu quả. Nhưng, nếu bạn đang tìm kiếm một số cách tiếp cận khác để tính toán trung vị, bên dưới mã SQL sẽ giúp bạn.

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;

Nếu bạn đang tìm cách tính toán trung vị trong MySQL, liên kết github này sẽ hữu ích.


0

Đây là giải pháp tối ưu nhất để tìm trung bình mà tôi có thể nghĩ ra. Các tên trong ví dụ được dựa trên ví dụ Justin. Đảm bảo chỉ mục cho bảng Sales.SalesOrderHeader tồn tại với các cột chỉ mục CustomerId và TotalDue theo thứ tự đó.

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

CẬP NHẬT

Tôi hơi không chắc chắn về phương pháp nào có hiệu suất tốt nhất, vì vậy tôi đã so sánh giữa phương pháp của mình Justin Grants và Jeff Atwoods bằng cách chạy truy vấn dựa trên cả ba phương thức trong một đợt và chi phí lô của mỗi truy vấn là:

Không có chỉ số:

  • Khai thác 30%
  • Justin tài trợ 13%
  • Jeff Atwoods 58%

Và với chỉ số

  • Của tôi 3%.
  • Justin tài trợ 10%
  • Jeff Atwoods 87%

Tôi đã cố gắng xem quy mô truy vấn tốt như thế nào nếu bạn có chỉ mục bằng cách tạo thêm dữ liệu từ khoảng 14 000 hàng theo hệ số 2 lên tới 512, nghĩa là cuối cùng khoảng 7,2 triệu hàng. Lưu ý Tôi đã đảm bảo trường CustomeId trong đó duy nhất cho mỗi lần tôi thực hiện một bản sao duy nhất, vì vậy tỷ lệ các hàng so với phiên bản duy nhất của CustomerId được giữ nguyên. Trong khi tôi đang làm điều này, tôi đã chạy các lệnh thực thi nơi tôi đã xây dựng lại chỉ mục sau đó và tôi nhận thấy kết quả ổn định ở khoảng 128 với dữ liệu tôi có với các giá trị này:

  • Của tôi 3%.
  • Justin tài trợ 5%
  • Jeff Atwoods 92%

Tôi đã tự hỏi làm thế nào hiệu suất có thể bị ảnh hưởng bởi việc tăng số lượng hàng nhưng vẫn giữ hằng số Khách hàng duy nhất, vì vậy tôi đã thiết lập một thử nghiệm mới trong đó tôi đã làm điều này. Bây giờ thay vì ổn định, tỷ lệ chi phí hàng loạt tiếp tục phân kỳ, thay vì khoảng 20 hàng trên mỗi Khách hàng trung bình tôi có cuối cùng khoảng 10000 hàng cho mỗi Id duy nhất như vậy. Các số trong đó:

  • Của tôi 4%
  • Chỉ 60%
  • Jeff 35%

Tôi chắc chắn rằng tôi đã thực hiện đúng từng phương pháp bằng cách so sánh kết quả. Kết luận của tôi là phương pháp tôi sử dụng thường nhanh hơn miễn là chỉ số tồn tại. Cũng lưu ý rằng phương pháp này là những gì được đề xuất cho vấn đề cụ thể này trong bài viết này https://www.microsoftpressstore.com/articles/article.aspx?p=2314819&seqNum=5

Một cách để cải thiện hơn nữa hiệu năng của các cuộc gọi tiếp theo đến truy vấn này hơn nữa là duy trì thông tin đếm trong một bảng phụ trợ. Bạn thậm chí có thể duy trì nó bằng cách có một trình kích hoạt cập nhật và lưu giữ thông tin liên quan đến số lượng hàng SalesOrderHeader phụ thuộc vào CustomerId, tất nhiên sau đó bạn cũng có thể lưu trữ đơn giản trung vị.


0

Đối với các bộ dữ liệu quy mô lớn, bạn có thể thử GIST này:

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

Nó hoạt động bằng cách tổng hợp các giá trị riêng biệt bạn sẽ tìm thấy trong tập hợp của mình (chẳng hạn như tuổi hoặc năm sinh, v.v.) và sử dụng các hàm cửa sổ SQL để xác định vị trí phần trăm bạn chỉ định trong truy vấn.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.