기능을 계산하는 중에서 SQL 서버
-
20-09-2019 - |
문제
에 따라 MSDN, 중앙값을 사용할 수 없으로 집계에서 기능 Transact-SQL.그러나 내가 좋아하는 것이 가능 여부를 만드는 이 기능을 사용하여( Create Aggregate 기능,사용자 정의 기능,또는 다른 방법).
무엇을 할 수있는 가장 좋은 방법이 될 것이(가능하면)를 이용한 계산의 평균 값이(가정 숫자 데이터 형식)에서 집계 쿼리?
해결책
성능이 크게 다양한 방법으로는 여러 가지가 있습니다. 다음은 특히 잘 최적화 된 솔루션입니다 중앙값, ROW_NUMBERS 및 성과. 이는 실행 중에 생성 된 실제 I/O와 관련하여 특히 최적의 솔루션입니다. 다른 솔루션보다 비용이 많이 들지만 실제로는 훨씬 빠릅니다.
이 페이지에는 다른 솔루션 및 성능 테스트 세부 사항에 대한 토론도 포함되어 있습니다. 중앙 열의 값이 동일한 여러 행이있는 경우, Disambiguator로 고유 한 열을 사용하십시오.
모든 데이터베이스 성능 시나리오와 마찬가지로 항상 실제 하드웨어에 대한 실제 데이터로 솔루션을 테스트하십시오. 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
SQL Server 2012에서는 사용해야합니다 백분위 수 _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
나의 원래 빠른 답변은 다음과 같습니다.
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
짝수의 데이터 항목이있을 때 올바른 중앙값 및 백분위 수 값을 계산해야합니다. 다시 말하지만, 전체 백분위 수 분포가 아닌 중앙값 만 원한다면 최종 위치 조항.
더 나은 :
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 (및 이후)에는 정렬 된 값에 대해 특정 백분위 수를 계산하는 백분위 수 _DISC 기능이 있습니다. 백분위 수 _DISC (0.5)는 중앙값을 계산합니다. https://msdn.microsoft.com/en-us/library/hh231327.aspx
간단하고 빠르며 정확합니다
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)
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
그런 다음 쿼리를 작성하여 다음과 같이 중앙값을 계산할 수 있습니다.
중앙값에 대한 세트 기반 솔루션을 찾는 동안 방금이 페이지를 발견했습니다. 여기서 몇 가지 솔루션을 살펴본 후 다음을 생각해 냈습니다. 희망은 도움/일입니다.
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
다음 쿼리는 다음을 반환합니다 중앙값 한 열의 값 목록에서. 집계 함수와 함께 사용될 수는 없지만 내부 선택의 WARE 절과 함께 하위 쿼리로 사용할 수 있습니다.
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
지만 저스틴 그랜트의 솔루션이 나타납 솔리드 나가는 것을 발견할 때의 번호가 중복되는 값에서 특정 파티션 키 행 번호 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
내가 사용하는 저스틴의 코드에 대한 기준으로 이 솔루션입니다.지 않지만으로 효율적 사용의 파생된 여러 테이블은 그것을 해결하는 행 순서 문제가 발생합니다.어떤 개선을 환영받을 것으로 나가지 않는 경험 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
Justin의 위의 예는 매우 좋습니다. 그러나 그 주요 핵심 요구는 매우 명확하게 언급되어야합니다. 나는 열쇠가없는 야생에서 그 코드를 보았고 결과는 나쁘다.
백분위 수에 대해 얻는 불만은 데이터 세트에서 실제 값을 제공하지 않는다는 것입니다. "중간"에 도달하려면 DataSet의 실제 값 인 Estile_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
SQL의 중간 계산에 대한 다른 솔루션을 참조하십시오. "MySQL로 중앙값을 계산하는 간단한 방법"(솔루션은 대부분 공급 업체 독립적입니다).
연속 변수/측정 'col1'의 경우 '표 1'
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
나는 혼자서 해결책을 제시하고 싶었지만 내 뇌가 넘어져 넘어졌다. 나 생각한다 작동하지만 아침에 설명 해달라고 요청하지 마십시오. :피
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
위의 일부 코드를 절대적으로 경외하십시오 !!!
이것은 내가 생각해 낼 수있는 것만 큼 간단한 대답입니다. 내 데이터와 잘 작동했습니다. 특정 값을 제외하려면 내부 선택에 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
다음과 같은 솔루션을 작동에 이러한 가정:
- 중복된 값이 없
- No 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) ;
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
몇 가지 대안으로 시도하지만 데이터 레코드가 반복적으로 값을 얻었 기 때문에 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의 답변을 바탕으로 그룹에 의해 그룹과 각 그룹의 중앙값을 얻는 상관 관계가 있습니다.
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 예제를 기반으로합니다. 테이블 판매에 대한 색인을 확인하십시오. SalesOrderHeader는 INDEX COLUMS COUNTERID 및 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
업데이트
어떤 방법이 최상의 성능을 가지고 있는지 확실하지 않았으므로 Justin Grants와 Jeff Atwoods의 방법을 비교하여 한 번의 배치로 세 가지 방법을 기반으로 쿼리를 실행하여 각 쿼리의 배치 비용은 다음과 같습니다.
색인없이 :
- 광산 30%
- 저스틴은 13%를 부여합니다
- Jeff Atwoods 58%
그리고 색인으로
- 광산 3%.
- 저스틴은 10%를 부여합니다
- Jeff Atwoods 87%
약 14,000 행에서 더 많은 데이터를 2 배까지 512로 만들어 인덱스가 얼마나 잘 표시되는지 확인하려고 노력했습니다. 이는 결국 약 7,2 백만 행을 의미합니다. 참고 단일 사본을 할 때마다 고유 한 CustomeID 필드를 확인 했으므로 CustomerID의 고유 인스턴스와 비교하여 행의 비율이 일정하게 유지되었습니다. 내가이 작업을 수행하는 동안 나는 나중에 인덱스를 재건 한 곳에서 실행을 실행했고, 결과가 다음 값에 대한 데이터와 함께 약 128의 계수로 안정화되는 것을 발견했습니다.
- 광산 3%.
- 저스틴은 5%를 부여합니다
- Jeff Atwoods 92%
나는 행의 수를 스케일링하지만 고유 한 고객을 일정하게 유지함으로써 성능이 어떻게 영향을받을 수 있는지 궁금했기 때문에 내가 한 곳에서 새로운 테스트를 설정합니다. 이제 안정화 대신, 배치 비용 비율은 계속 분기되었으며, 그러한 고유 한 ID 당 약 10000 줄의 평균 당 고객 ID 당 약 20 행 대신에도 계속 분기되었습니다. 여기서 숫자 :
- 광산 4%
- Justins 60%
- Jeffs 35%
결과를 비교하여 각 방법을 올바르게 구현했는지 확인했습니다. 내 결론은 내가 사용한 방법이 일반적으로 인덱스가 존재하는 한 더 빠릅니다. 또한이 방법은이 기사 에서이 특정 문제에 권장되는 것임을 알았습니다. https://www.microsoftpressstore.com/articles/article.aspx?p=2314819&seqnum=5
이 쿼리에 대한 후속 통화의 성능을 더욱 향상시키는 방법은 보조 테이블에서 카운트 정보를 지속하는 것입니다. CustomerID에 의존하는 SalesOrderheader 행의 수에 관한 정보를 보유하고있는 트리거를 사용하여 유지 관리 할 수도 있습니다. 물론 중앙값도 간단하게 저장할 수 있습니다.
대규모 데이터 세트의 경우이 요점을 시도 할 수 있습니다.
https://gist.github.com/chrisknoll/1b38761ce8c5016ec5b2
세트에서 찾을 수있는 뚜렷한 값 (예 : 연령, 출생 연도 등)을 집계하여 작동하며 SQL 창 함수를 사용하여 쿼리에 지정한 백분위 수 위치를 찾습니다.
중간 발견
이것은 속성의 중앙값을 찾는 가장 간단한 방법입니다.
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)