SQL Server에서 중앙값을 계산하는 함수


227

MSDN 에 따르면 Median은 Transact-SQL에서 집계 함수로 사용할 수 없습니다. 그러나 ( Create Aggregate 함수, 사용자 정의 함수 또는 다른 방법을 사용하여)이 기능을 만들 수 있는지 확인하고 싶습니다 .

이를 수행하는 가장 좋은 방법은 무엇입니까 (가능하면) 집계 쿼리에서 중앙값을 계산할 수 있습니까 (숫자 데이터 유형 가정)?


답변:


145

2019 업데이트 : 이 답변을 작성한 후 10 년 동안 더 많은 솔루션이 발견되어 더 나은 결과를 얻을 수 있습니다. 또한 이후 SQL Server 릴리스 (특히 SQL 2012)에서는 중앙값을 계산하는 데 사용할 수있는 새로운 T-SQL 기능이 도입되었습니다. SQL Server 릴리스는 다양한 중앙 솔루션의 성능에 영향을 줄 수있는 쿼리 최적화 프로그램도 개선했습니다. Net-net, 2009 년 원본 게시물은 여전히 ​​문제 없지만 최신 SQL Server 앱에 대한 더 나은 솔루션이있을 수 있습니다. https://sqlperformance.com/2012/08/t-sql-queries/median 에서 훌륭한 리소스 인 2012 년부터이 기사를 살펴보십시오.

이 기사에서는 다음 패턴이 다른 모든 대안보다 훨씬 빠르며 최소한 테스트 한 간단한 스키마에서 더 빠르다는 것을 발견했습니다. 이 솔루션은 PERCENTILE_CONT테스트 된 가장 느린 솔루션 ( ) 보다 373 배 더 빠릅니다 (!!!) . 이 트릭에는 두 가지 별도의 쿼리가 필요하지만 모든 경우에 실용적이지는 않을 수 있습니다. 또한 SQL 2012 이상이 필요합니다.

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;

물론 2012 년 한 스키마에서 한 번의 테스트를 수행해도 큰 결과를 얻을 수 있었기 때문에 특히 SQL Server 2014 이상인 경우 마일리지가 다를 수 있습니다. perf가 중앙값 계산에 중요한 경우에는이 기사에서 권장하는 몇 가지 옵션을 시도하고 성능을 테스트하여 스키마에 가장 적합한 옵션을 찾아 보는 것이 좋습니다.

또한 위의 링크 된 기사 에서이 내장 함수가 가장 빠른 솔루션보다 373 배 느리기 때문에이 질문 PERCENTILE_CONT에 대한 다른 답변 중 하나에서 권장 되는 (SQL Server 2012의 새로운 기능) 기능 을 사용하는 데 특히주의 해야합니다. 이 불일치가 7 년 후에 개선되었을 수도 있지만 개인적으로 성능을 다른 솔루션과 비교할 때까지 큰 테이블에서이 기능을 사용하지 않을 것입니다.

2009 년 최초 게시일 :

성능을 극적으로 변화시키는 방법에는 여러 가지가 있습니다. 다음은 중간 값, ROW_NUMBER 및 성능 에서 최적화 된 솔루션 중 하나 입니다. 이는 실행 중에 생성 된 실제 I / O와 관련하여 특히 최적의 솔루션입니다. 다른 솔루션보다 비용이 많이 들지만 실제로는 훨씬 빠릅니다.

이 페이지에는 다른 솔루션 및 성능 테스트 세부 사항에 대한 설명도 포함되어 있습니다. 중앙값 값이 동일한 행이 여러 개인 경우 고유 열을 명확성으로 사용하십시오.

모든 데이터베이스 성능 시나리오와 마찬가지로 항상 실제 하드웨어에서 실제 데이터를 사용하여 솔루션을 테스트 해보십시오. SQL Server의 옵티마이 저나 환경의 특이성으로 인해 일반적으로 빠른 솔루션이 느려질 때를 알 수는 없습니다.

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
데이터에 듀피, 특히 많은 듀피가있는 경우 이것이 효과가 있다고 생각하지 않습니다. row_numbers가 줄을 보장 할 수는 없습니다. 당신은 당신의 중앙값에 대한 정말 미친 답을 얻거나 전혀 중앙값을 얻지 못할 수도 있습니다.
Jonathan Beerhalter

26
그렇기 때문에 명확성 검사기 (위의 코드 예제에서 SalesOrderId)가 중요하므로 결과 집합 행의 순서가 앞뒤로 일치하도록 할 수 있습니다. 고유 한 기본 키는 별도의 인덱스 조회없이 사용할 수 있기 때문에 이상적인 명확성을 보장합니다. 사용 가능한 명확성 열이없는 경우 (예 : 테이블에 고유 키가없는 경우) DESC 행 번호가 미러 이미지임을 보장 할 수없는 경우 올바르게 지적 했으므로 중간 값을 계산하는 데 다른 방법을 사용해야합니다. ASC 행 번호를 입력하면 결과를 예측할 수 없습니다.
저스틴 그랜트

4
감사합니다. 열을 DB로 전환 할 때 명확하지 않다고 생각하여 명확성을 떨어 뜨 렸습니다. 이 경우이 솔루션은 실제로 잘 작동합니다.
Jonathan Beerhalter

8
명확성에 대한 필요성을 설명하면서 코드 자체에 주석을 추가하는 것이 좋습니다.
hoffmanc

4
대박! 오랫동안 그 중요성을 알고 있었지만 이제는 이름을 말할 수 있습니다 ... 명확 성어! 저스틴 감사합니다!
CodeMonkey

204

SQL 2005 이상을 사용하는 경우 이는 테이블의 단일 열에 대한 훌륭하고 간단한 중앙값 계산입니다.

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
Median () 집계 함수가 없으므로 영리하고 비교적 간단합니다. 그러나 어떻게 Median () 함수가 존재하지 않습니까!? 솔직히 조금 FLOOR ()입니다.
Charlie Kilian

글쎄, 좋고 간단하지만 일반적으로 특정 그룹 카테고리 당 중간 값이 필요합니다 select gid, median(score) from T group by gid. 이에 대한 상관 하위 쿼리가 필요합니까?
TMS

1
... 이 경우 와 같은 의미입니다 ( "중앙 응답 점수가 가장 높은 사용자"라는 두 번째 쿼리).
TMS

토마스- "특정 그룹 범주 별"문제를 해결하셨습니까? 같은 문제가 있습니다. 감사.
Stu Harper

3
이 솔루션을 GROUP BY와 함께 사용하는 방법은 무엇입니까?
Przemyslaw 레민

82

SQL Server 2012에서는 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

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


12
이 전문가 분석은 성능 저하로 인해 PERCENTILE 기능에 대해 설득력있는 주장을합니다. sqlperformance.com/2012/08/t-sql-queries/median
carl.anderson

4
당신은 추가 할 필요가 없습니다 DISTINCT또는 GROUPY BY SalesOrderID? 그렇지 않으면 많은 중복 행이 생깁니다.
Konstantin

1
이것이 답입니다. 내가 왜 이쪽으로 스크롤
해야하는지 몰라

사용 신중한 버전도 있습니다PERCENTILE_DISC
johnDanger

위의 @ carl.anderson의 요점을 강조합니다. PERCENTILE_CONT 솔루션은 특정 테스트 스키마에 대해 SQL Server 2012에서 테스트 한 가장 빠른 솔루션과 비교하여 373 배 더 느립니다 (!!!!). 자세한 내용은 carl이 링크 한 기사를 읽으십시오.
저스틴 그랜트

21

내 원래 빠른 답변은 다음과 같습니다

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

이것은 당신에게 한 번에 중간 및 사 분위 범위를 제공합니다. 중앙값 인 행을 하나만 원하면 where 절의 주석 처리를 제거하십시오.

이를 설명 계획에 집어 넣을 때, 작업의 60 %가 이와 같은 위치 종속 통계를 계산할 때 피할 수없는 데이터를 정렬합니다.

아래 의견에서 Robert Ševčík-Robajz의 훌륭한 제안을 따르기 위해 답변을 수정했습니다.

;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

짝수 개의 데이터 항목이있는 경우 올바른 중앙값 및 백분위 수 값을 계산해야합니다. 전체 백분위 수 분포가 아닌 중앙값 만 원하는 경우 마지막 where 절의 주석을 해제하십시오.


1
이것은 실제로 잘 작동하며 데이터를 분할 할 수 있습니다.
Jonathan Beerhalter

3
하나만 꺼져도 괜찮다면 위의 쿼리는 괜찮습니다. 그러나 정확한 중앙값이 필요한 경우 문제가 발생합니다. 예를 들어, 시퀀스 (1,3,5,7)의 경우 중앙값은 4이지만 위의 쿼리는 3을 반환합니다. (1,2,3,503,603,703)의 경우 중앙값은 258이지만 위의 쿼리는 503을 반환합니다.
Justin Grant

1
하위 쿼리에서 각 사 분위의 최대 값과 최소값을 취한 다음 이전의 최대 값과 다음의 MIN을 AVG하여 부정확성의 결함을 해결할 수 있습니까?
Rbjz

18

더 나은 :

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

마스터 자신으로부터, Itzik Ben-Gan !



4

간단하고 빠르며 정확한

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에서 집계 생성 기능을 사용하려면이 방법을 사용하십시오. 이렇게하면 깨끗한 쿼리를 작성할 수 있다는 이점이 있습니다. 이 프로세스는 백분위 수 값을 상당히 쉽게 계산하도록 조정할 수 있습니다.

새 Visual Studio 프로젝트를 만들고 대상 프레임 워크를 .NET 3.5로 설정합니다 (SQL 2008의 경우 SQL 2012에서는 다를 수 있음). 그런 다음 클래스 파일을 만들고 다음 코드 또는 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

그런 다음이를 컴파일하고 DLL 및 PDB 파일을 SQL Server 시스템에 복사하고 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

그런 다음 다음과 같은 중간 값을 계산하는 쿼리를 작성할 수 있습니다. SELECT dbo.Median (Field) FROM Table


3

중간에 설정 기반 솔루션을 찾는 동안이 페이지를 방문했습니다. 여기에 몇 가지 솔루션을 살펴본 후 다음을 생각해 냈습니다. 희망은 도움 / 작동합니다.

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

다음 쿼리는 한 열의 값 목록 에서 중앙값 을 반환합니다 . 집계 함수로 사용하거나 집계 함수와 함께 사용할 수는 없지만 내부 선택에 WHERE 절이있는 하위 쿼리로 계속 사용할 수 있습니다.

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 grant의 솔루션은 확실하게 보이지만 주어진 파티션 키에 여러 개의 중복 값이 ​​있으면 ASC 중복 값의 행 번호가 순서를 벗어나서 올바르게 정렬되지 않는 것으로 나타났습니다.

결과는 다음과 같습니다.

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의 코드를 사용했습니다. 여러 파생 테이블을 사용하는 것이 효율적이지는 않지만 행 순서 문제를 해결합니다. 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

위의 저스틴의 예는 매우 좋습니다. 그러나 기본 키 필요성은 매우 명확하게 명시해야합니다. 키가없는 와일드 코드는 결과가 좋지 않습니다.

Percentile_Cont에 대한 불만은 데이터 세트에서 실제 가치를 제공하지 않는다는 것입니다. 데이터 세트에서 실제 값인 "중앙값"에 도달하려면 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

UDF에서 다음을 작성하십시오.

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

7
짝수 개의 항목이있는 경우 중앙값은 두 개의 중간 항목의 평균이며이 UDF에서 다루지 않습니다.
Yaakov Ellis

1
UDF 전체에서 다시 작성할 수 있습니까?
Przemyslaw Remin

2

중앙값 찾기

이것은 속성의 중앙값을 찾는 가장 간단한 방법입니다.

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)

행 수가 짝수 일 때까지 어떻게 처리 할 수 ​​있습니까?
priojeet priyom


1

'table1'의 연속 변수 / 측정 'col1'

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 집계를 사용하면 먼저 행 수를 계산하고 @cnt라는 변수에 저장할 수 있습니다. 그런 다음 수량 순서에 따라 건너 뛸 행 수 (오프셋 값) 및 필터링 수 (페치 값)를 지정하여 OFFSET-FETCH 필터에 대한 매개 변수를 계산할 수 있습니다.

건너 뛸 행 수는 (@cnt-1) / 2입니다. 홀수 카운트의 경우 2로 나누기 전에 먼저 단일 중간 값에서 1을 빼기 때문에이 계산이 올바른 것입니다.

표현식에 사용 된 나누기가 정수 나누기이므로 짝수에 대해서도 올바르게 작동합니다. 따라서 짝수에서 1을 빼면 홀수 값이 남습니다.

홀수 값을 2로 나누면 결과의 일부 (.5)가 잘립니다. 가져올 행 수는 2-(@cnt % 2)입니다. 카운트가 홀수 인 경우 모듈로 연산의 결과는 1이고 1 행을 가져와야한다는 아이디어입니다. 카운트가 모듈로 연산의 결과 인 경우에도 0이며 2 개의 행을 가져와야합니다. 2에서 모듈로 연산의 1 또는 0 결과를 빼면 원하는 1 또는 2를 얻을 수 있습니다. 마지막으로 중앙값을 계산하려면 하나 또는 두 개의 결과 수량을 가져 와서 다음과 같이 입력 정수 값을 숫자로 변환 한 후 평균을 적용하십시오.

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

나는 혼자서 해결책을 찾고 싶었지만 뇌가 넘어져 길에 떨어졌습니다. 나는 그것이 효과가 있다고 생각 하지만 아침에 그것을 설명하도록 요구하지 마십시오. :피

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에서 작동합니다.

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

아주 기초를 배우고있는 저와 같은 초보자들에게는,이 사건을 정확히 이해하기가 더 쉽기 때문에 개인적으로이 예를 따르기가 더 쉽습니다.

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

위의 코드 중 일부에 대한 절대 경외감!


0

이것은 내가 생각해 낼 수있는 간단한 대답입니다. 내 데이터로 잘 작동했습니다. 특정 값을 제외하려면 내부 선택에 where 절을 추가하십시오.

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

다음 솔루션은 이러한 가정 하에서 작동합니다.

  • 중복 값이 ​​없습니다
  • NULL 없음

암호:

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

여러 대안을 시도했지만 데이터 레코드의 값이 반복되어 ROW_NUMBER 버전이 적합하지 않은 것 같습니다. 그래서 여기에 내가 사용한 쿼리 (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

위의 Jeff Atwood의 대답을 바탕으로 GROUP BY 및 각 하위 그룹의 중앙값을 얻기위한 상관 하위 쿼리가 있습니다.

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

종종 전체 테이블뿐만 아니라 일부 ID와 관련된 집계에 대한 중앙값을 계산해야 할 수도 있습니다. 다시 말해, 표에서 각 ID에 많은 레코드가있는 각 ID의 중앙값을 계산하십시오. (@gdoron이 편집 한 솔루션을 기반으로 : 우수한 성능과 많은 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;

도움이 되길 바랍니다.


0

귀하의 질문에 대해 Jeff Atwood는 이미 간단하고 효과적인 해결책을 제시했습니다. 그러나 중앙값을 계산하는 대체 방법을 찾고 있다면 아래 SQL 코드가 도움이 될 것입니다.

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에서 중앙값을 계산하려는 경우이 github 링크 가 유용합니다.


0

이것은 내가 생각할 수있는 중간 값을 찾는 가장 최적의 솔루션입니다. 예제의 이름은 Justin 예제를 기반으로합니다. Sales.SalesOrderHeader 테이블의 인덱스가 CustomerId 및 TotalDue 인덱스 열과 함께 순서대로 존재하는지 확인하십시오.

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

최신 정보

어떤 방법이 최고의 성능을 발휘하는지 확신 할 수 없었기 때문에 한 번에 세 가지 방법을 모두 기반으로 쿼리를 실행하여 각 방법의 배치 비용은 다음과 같습니다.

색인이없는 경우 :

  • 광산 30 %
  • 저스틴 그랜트 13 %
  • 제프 앳 우드 58 %

그리고 색인

  • 내 3 %.
  • 저스틴 그랜트 10 %
  • 제프 앳 우드 87 %

약 14 000 행에서 2 ~ 512 배까지 더 많은 데이터를 만들어 인덱스가있는 경우 쿼리가 얼마나 잘 확장되는지 확인하려고 시도했습니다. 이는 약 7,2 백만 행입니다. 참고 매번 복사 할 때마다 고유 한 CustomeId 필드를 확인 했으므로 CustomerId의 고유 인스턴스와 비교 한 행 비율이 일정하게 유지되었습니다. 이 작업을 수행하는 동안 나중에 인덱스를 다시 작성하는 실행을 실행했으며 결과가 다음 값에 대한 데이터로 약 128 배 정도 안정화되었습니다.

  • 내 3 %.
  • 저스틴 그랜트 5 %
  • 제프 앳 우드 92 %

행 수를 확장하면서 고유 한 CustomerId를 일정하게 유지하여 성능에 어떤 영향을 줄 수 있을지 궁금해서이 작업을 수행 한 새로운 테스트를 설정했습니다. 이제 배치 비용 비율이 안정화되는 대신 배치 ID 비율이 계속 변했습니다. 이러한 고유 ID 당 약 10000 행의 평균으로 CustomerId 당 약 20 개의 행 대신에. 숫자는 다음과 같습니다.

  • 광산 4 %
  • 저스틴 60 %
  • 제프 35 %

결과를 비교하여 각 방법을 올바르게 구현했는지 확인했습니다. 내 결론은 인덱스가 존재하는 한 일반적으로 사용 된 방법이 더 빠르다는 것입니다. 이 방법은이 기사에서이 특정 문제에 권장되는 방법입니다. https://www.microsoftpressstore.com/articles/article.aspx?p=2314819&seqNum=5

이 쿼리에 대한 후속 호출의 성능을 더욱 향상시키는 방법은 카운트 정보를 보조 테이블에 유지하는 것입니다. CustomerId에 따라 SalesOrderHeader 행 수에 관한 정보를 업데이트하고 보유하는 트리거를 사용하여이를 유지 관리 할 수도 있습니다. 물론 중간 값도 간단히 저장할 수 있습니다.


당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.