MSDN 에 따르면 Median은 Transact-SQL에서 집계 함수로 사용할 수 없습니다. 그러나 ( Create Aggregate 함수, 사용자 정의 함수 또는 다른 방법을 사용하여)이 기능을 만들 수 있는지 확인하고 싶습니다 .
이를 수행하는 가장 좋은 방법은 무엇입니까 (가능하면) 집계 쿼리에서 중앙값을 계산할 수 있습니까 (숫자 데이터 유형 가정)?
MSDN 에 따르면 Median은 Transact-SQL에서 집계 함수로 사용할 수 없습니다. 그러나 ( Create Aggregate 함수, 사용자 정의 함수 또는 다른 방법을 사용하여)이 기능을 만들 수 있는지 확인하고 싶습니다 .
이를 수행하는 가장 좋은 방법은 무엇입니까 (가능하면) 집계 쿼리에서 중앙값을 계산할 수 있습니까 (숫자 데이터 유형 가정)?
답변:
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;
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
select gid, median(score) from T group by gid
. 이에 대한 상관 하위 쿼리가 필요합니까?
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
DISTINCT
또는 GROUPY BY SalesOrderID
? 그렇지 않으면 많은 중복 행이 생깁니다.
PERCENTILE_DISC
내 원래 빠른 답변은 다음과 같습니다
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 절의 주석을 해제하십시오.
더 나은 :
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 !
MS SQL Server 2012 이상에는 정렬 된 값에 대한 특정 백분위 수를 계산하는 PERCENTILE_DISC 함수가 있습니다. - PERCENTILE_DISC (0.5) 중간 계산됩니다 https://msdn.microsoft.com/en-us/library/hh231327.aspx을
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
중간에 설정 기반 솔루션을 찾는 동안이 페이지를 방문했습니다. 여기에 몇 가지 솔루션을 살펴본 후 다음을 생각해 냈습니다. 희망은 도움 / 작동합니다.
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
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
위의 저스틴의 예는 매우 좋습니다. 그러나 기본 키 필요성은 매우 명확하게 명시해야합니다. 키가없는 와일드 코드는 결과가 좋지 않습니다.
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
UDF에서 다음을 작성하십시오.
Select Top 1 medianSortColumn from Table T
Where (Select Count(*) from Table
Where MedianSortColumn <
(Select Count(*) From Table) / 2)
Order By medianSortColumn
중앙값 찾기
이것은 속성의 중앙값을 찾는 가장 간단한 방법입니다.
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)
여기에 SQL에서 중간 계산을위한 다른 솔루션을 참조하십시오 : " MySQL의와 계산의 중간에 간단한 방법 "(솔루션은 대부분 벤더 독립적이다).
'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
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;
나는 혼자서 해결책을 찾고 싶었지만 뇌가 넘어져 길에 떨어졌습니다. 나는 그것이 효과가 있다고 생각 하지만 아침에 그것을 설명하도록 요구하지 마십시오. :피
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)
--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]
이것은 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
)
아주 기초를 배우고있는 저와 같은 초보자들에게는,이 사건을 정확히 이해하기가 더 쉽기 때문에 개인적으로이 예를 따르기가 더 쉽습니다.
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
위의 코드 중 일부에 대한 절대 경외감!
다음 솔루션은 이러한 가정 하에서 작동합니다.
암호:
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) ;
여러 대안을 시도했지만 데이터 레코드의 값이 반복되어 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;
위의 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
종종 전체 테이블뿐만 아니라 일부 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;
도움이 되길 바랍니다.
귀하의 질문에 대해 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 링크 가 유용합니다.
이것은 내가 생각할 수있는 중간 값을 찾는 가장 최적의 솔루션입니다. 예제의 이름은 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
최신 정보
어떤 방법이 최고의 성능을 발휘하는지 확신 할 수 없었기 때문에 한 번에 세 가지 방법을 모두 기반으로 쿼리를 실행하여 각 방법의 배치 비용은 다음과 같습니다.
색인이없는 경우 :
그리고 색인
약 14 000 행에서 2 ~ 512 배까지 더 많은 데이터를 만들어 인덱스가있는 경우 쿼리가 얼마나 잘 확장되는지 확인하려고 시도했습니다. 이는 약 7,2 백만 행입니다. 참고 매번 복사 할 때마다 고유 한 CustomeId 필드를 확인 했으므로 CustomerId의 고유 인스턴스와 비교 한 행 비율이 일정하게 유지되었습니다. 이 작업을 수행하는 동안 나중에 인덱스를 다시 작성하는 실행을 실행했으며 결과가 다음 값에 대한 데이터로 약 128 배 정도 안정화되었습니다.
행 수를 확장하면서 고유 한 CustomerId를 일정하게 유지하여 성능에 어떤 영향을 줄 수 있을지 궁금해서이 작업을 수행 한 새로운 테스트를 설정했습니다. 이제 배치 비용 비율이 안정화되는 대신 배치 ID 비율이 계속 변했습니다. 이러한 고유 ID 당 약 10000 행의 평균으로 CustomerId 당 약 20 개의 행 대신에. 숫자는 다음과 같습니다.
결과를 비교하여 각 방법을 올바르게 구현했는지 확인했습니다. 내 결론은 인덱스가 존재하는 한 일반적으로 사용 된 방법이 더 빠르다는 것입니다. 이 방법은이 기사에서이 특정 문제에 권장되는 방법입니다. https://www.microsoftpressstore.com/articles/article.aspx?p=2314819&seqNum=5
이 쿼리에 대한 후속 호출의 성능을 더욱 향상시키는 방법은 카운트 정보를 보조 테이블에 유지하는 것입니다. CustomerId에 따라 SalesOrderHeader 행 수에 관한 정보를 업데이트하고 보유하는 트리거를 사용하여이를 유지 관리 할 수도 있습니다. 물론 중간 값도 간단히 저장할 수 있습니다.
대규모 데이터 세트의 경우 다음 GIST를 시도 할 수 있습니다.
https://gist.github.com/chrisknoll/1b38761ce8c5016ec5b2
집합에서 찾은 고유 한 값 (예 : 연령, 생년월일 등)을 집계하여 작동하며 SQL 창 함수를 사용하여 쿼리에서 지정한 백분위 수 위치를 찾습니다.