SQL Serverの中央値を計算する関数
-
20-09-2019 - |
解決
パフォーマンスを劇的に変化させて、これを行うための方法はたくさんあります。ここでは1特によく最適化されたソリューションは、の<のhref =「http://sqlblog.com/blogs/adam_machanic/archive/2006/12/18/medians-row-numbers-and-performance.aspx」のrelから、です= "noreferrer">中央値、ROW_NUMBERs、およびパフォーマンスのの。それは、実際のI / Oをすることになるとこれは、実行中に生成され、特に最適なソリューションである - それは他のソリューションよりも高価に見えるが、それははるかに高速実際にある
。このページには、他のソリューションとパフォーマンステストの詳細についての議論が含まれています。メジアンカラムの同じ値を持つ複数の行が存在する場合にディスアンビギュエータとしてのユニークなカラムの使用に注意してください。
すべてのデータベース・パフォーマンスのシナリオと同様に、常に実際のハードウェア上の実際のデータでソリューションをテストしてみてください。- SQL Serverのオプティマイザへの変更や、ご使用の環境の特殊性は、通常、迅速な解決が遅くなりますとき、あなたは知っていることはありません<。 / P>
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;
他のヒント
これはテーブル内の単一の列のための素晴らしい、シンプルっぽい中央値の計算です。
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のでは、 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
も参照してください:<のhref = "http://blog.sqlauthority.com/2011/11/20/sql-server-introduction-to-percentile_cont-analytic-functions-introduced-in-sql-server-2012 /」のrel = "noreferrer"> http://blog.sqlauthority.com/2011/11/20/sql-server-introduction-to-percentile_cont-analytic-functions-introduced-in-sql-server-2012/
私のオリジナルの迅速な答えだっます:
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
このはあなたに一挙における中央値と四分位範囲を提供します。あなたが本当に唯一の中央値である1行をしたい場合は、where句ます。
のコメントを解除 あなたが実行計画に固執することをすると、作業の60%がこのような位置依存の統計を計算する際に避けられないデータをソートされます。
私は以下のコメントでロバートŠ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ベン・ガンに!
MS SQL Server 2012の(およびそれ以降)ソートされた値のための特定のパーセンタイルを計算PERCENTILE_DISC機能を有しています。 https://msdn.microsoft.com/ - PERCENTILE_DISC(0.5)は、中央値を計算しますEN-US /ライブラリ/ 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
そして、それをコンパイルして、SQL ServerマシンにDLLとPDBファイルをコピーして、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
あなたは、このような中央値を計算するクエリを記述することができます。 表から選択dbo.Median(フィールド)
私はこのページに出くわしました。ここでのソリューションの一部を見た後、私は次のを思い付きました。ホープ/作品を助けている。
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
次のクエリは、 中央値 1 つの列の値のリストから。集計関数として、または集計関数と一緒に使用することはできませんが、内部選択の 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
私はあなたが与えられたパーティション・キー内の重複値の数を持っているとき、彼らは適切に整列しないように、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
上記のジャスティンの例は非常に良いです。しかし、その主キーの必要性は非常に明確に記載すべき。私はキーなしで野生でそのコードを見ているとの結果が悪いです。
私は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
ここではSQLでの中央値の計算のための他のソリューションを参照してください。 「にして中央値を計算するための簡単な方法をhref="https://stackoverflow.com/questions/1291152/simple-way-to-calculate-median-with-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
私は自分で解決策を考え出すしたかったが、私の脳がトリップし、道に落ちました。私はが考えるのそれは動作しますが、午前中にそれを説明するために私に聞かないでください。 :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)
--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
しかし、上記のコードのいくつかの絶対AWEで!!!
これは私が思い付くことができような単純な答えです。私のデータとよく働きました。あなたが特定の値を除外したい場合は、単に内側のSELECTに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
次のソリューションは、次の仮定の下で機能します。
- 重複する値はありません
- 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;
ここではそれ以上のジェフ・アトウッドの答えのビルは、各グループの中央値を取得するには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;
それがお役に立てば幸いです。
あなたの質問については、ジェフ・アトウッドはすでに、シンプルで効果的な解決策を与えていました。しかし、あなたは中央値を計算するためのいくつかの代替的なアプローチを探している場合、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 の例に基づいています。Table 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
アップデート
どちらのメソッドが最高のパフォーマンスを発揮するか少し確信がなかったので、1 つのバッチで 3 つのメソッドすべてに基づいてクエリを実行して、自分のメソッド Justin Grants と Jeff Atwoods を比較しました。各クエリのバッチ コストは次のとおりでした。
インデックスなし:
- 私のもの 30%
- ジャスティン・グランツ 13%
- ジェフ・アトウッズ 58%
そしてインデックス付き
- 私の場合は3%です。
- ジャスティン・グラント 10%
- ジェフ・アトウッズ 87%
約 14,000 行から 2 倍の最大 512 行、つまり最終的に約 7,200 万行のデータを作成することで、インデックスがある場合にクエリがどの程度うまくスケールするかを確認しようとしました。単一のコピーを実行するたびに CustomeId フィールドが一意であることを確認したため、CustomerId の一意のインスタンスと比較した行の割合が一定に保たれたことに注意してください。これを実行している間、後でインデックスを再構築する実行を実行しました。データが次の値になると、結果が約 128 倍で安定していることに気付きました。
- 私の場合は3%です。
- ジャスティン・グランツ 5%
- ジェフ・アトウッズ 92%
一意の CustomerId を一定に保ちながら行数をスケーリングするとパフォーマンスにどのような影響が出るのか疑問に思ったので、これを実行する新しいテストをセットアップしました。バッチコスト率は安定するどころか発散し続け、また、CustomerId あたり平均約 20 行ではなく、最終的には一意の ID あたり約 10,000 行になりました。数値は次のとおりです。
- 私のもの 4%
- ジャスティンズ 60%
- ジェフス 35%
結果を比較することで、各メソッドが正しく実装されていることを確認しました。私の結論は、インデックスが存在する限り、私が使用した方法の方が一般的に高速であるということです。また、この記事でこの特定の問題に対してこの方法が推奨されていることに気付きました。 https://www.microsoftpressstore.com/articles/article.aspx?p=2314819&seqNum=5
このクエリに対する後続の呼び出しのパフォーマンスをさらに向上させる方法は、補助テーブルにカウント情報を永続化することです。CustomerId に応じて SalesOrderHeader 行の数に関する情報を更新および保持するトリガーを使用して、これを維持することもできます。もちろん、中央値を単純に保存することもできます。
大規模なデータセットの場合は、あなたがこのGISTを試すことができます:
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)