MSDNによると、Median は Transact-SQL の集計関数として使用できません。ただし、この機能を作成できるかどうかを確認したいと思います ( Create Aggregate関数、ユーザー定義関数、またはその他の方法を使用)。
これを行うための最善の方法 (可能であれば) は何でしょうか?集計クエリで中央値 (数値データ型を想定) を計算できるようにしますか?
MSDNによると、Median は Transact-SQL の集計関数として使用できません。ただし、この機能を作成できるかどうかを確認したいと思います ( Create Aggregate関数、ユーザー定義関数、またはその他の方法を使用)。
これを行うための最善の方法 (可能であれば) は何でしょうか?集計クエリで中央値 (数値データ型を想定) を計算できるようにしますか?
SQL 2005 以降を使用している場合、これはテーブル内の 1 つの列の単純な中央値計算です。
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
2019 年の更新:この回答を書いてから 10 年間で、より良い結果が得られる可能性がある解決策がさらに発見されました。また、それ以降の SQL Server のリリース (特に SQL 2012) では、中央値の計算に使用できる新しい T-SQL 機能が導入されました。SQL Server のリリースでは、クエリ オプティマイザーも改善されており、さまざまな中央値ソリューションのパフォーマンスに影響を与える可能性があります。ネットネット、私の最初の 2009 年の投稿はまだ問題ありませんが、最新の SQL Server アプリにはより良い解決策があるかもしれません。優れたリソースである 2012 年のこの記事をご覧ください: https://sqlperformance.com/2012/08/t-sql-queries/median
この記事では、少なくともテストした単純なスキーマでは、次のパターンが他のすべての代替案よりもはるかに高速であることがわかりました。このソリューションは、テストした最も遅い ( PERCENTILE_CONT
) ソリューションよりも 373 倍高速 (!!!) でした。このトリックには 2 つの個別のクエリが必要であり、すべての場合に実用的ではないことに注意してください。また、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 年の 1 つのスキーマに対する 1 つのテストで優れた結果が得られたからといって、特に SQL Server 2014 以降を使用している場合は、結果が異なる可能性があります。中央値の計算でパフォーマンスが重要な場合は、その記事で推奨されているいくつかのオプションを試してパフォーマンス テストを行い、スキーマに最適なオプションを見つけたことを確認することを強くお勧めします。
また、この質問に対する他の回答のPERCENTILE_CONT
1 つで推奨されている (SQL Server 2012 の新機能) 関数の使用にも特に注意します。それ以来、この格差は 7 年で改善された可能性がありますが、個人的には、パフォーマンスと他のソリューションとの比較を確認するまで、大きなテーブルでこの関数を使用しませんでした。
元の 2009 年の投稿は以下のとおりです。
これを行うには多くの方法があり、パフォーマンスは劇的に異なります。Medians、ROW_NUMBERs、および performanceから、特に最適化されたソリューションの 1 つを次に示します。これは、実行中に生成される実際の 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 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
私の最初の簡単な答えは次のとおりです。
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% がデータの並べ替えであり、このような位置に依存する統計を計算する場合、これは避けられません。
以下のコメントで、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 で Create Aggregate 関数を使用する場合は、次のようにします。このようにすると、クリーンなクエリを記述できるという利点があります。このプロセスを適用して、パーセンタイル値をかなり簡単に計算できることに注意してください。
新しい 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
シンプル、高速、正確
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)
UDFに、次のように記述します。
Select Top 1 medianSortColumn from Table T
Where (Select Count(*) from Table
Where MedianSortColumn <
(Select Count(*) From Table) / 2)
Order By medianSortColumn
次のクエリは、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
中央値に対するセットベースのソリューションを探しているときに、このページに出くわしました。ここでいくつかの解決策を見た後、私は次のことを思いつきました。希望は助け/働きます。
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
上記のジャスティンの例は非常に優れています。しかし、その主キーの必要性は非常に明確に述べる必要があります。キーなしでコードが公開されているのを見たことがありますが、結果は良くありません。
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
上記の 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
大規模なデータセットの場合は、次の GIST を試すことができます。
https://gist.github.com/chrisknoll/1b38761ce8c5016ec5b2
セット内にある個別の値 (年齢、生年など) を集計することで機能し、SQL ウィンドウ関数を使用して、クエリで指定したパーセンタイル位置を特定します。
ここで、SQL での中央値計算の他のソリューションを参照してください: 「MySQL で中央値を計算する簡単な方法」 (ソリューションはほとんどベンダーに依存しません)。
多くの場合、テーブル全体だけでなく、いくつかの 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;
それが役に立てば幸い。
これはSQL2000で機能します。
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
)
次のソリューションは、これらの仮定の下で機能します。
コード:
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) ;
これは私が思いつく限りの簡単な答えです。私のデータでうまくいきました。特定の値を除外したい場合は、内側の 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
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
自分で解決策を考えたかったのですが、頭がトリップして途中で落ちてしまいました。うまくいくと思いますが、朝に説明を求めないでください。: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]
COUNT 集計を使用すると、最初に行数をカウントし、@cnt という変数に格納できます。次に、数量の順序に基づいて、スキップする行数 (オフセット値) とフィルタリングする行数 (フェッチ値) を指定する OFFSET-FETCH フィルターのパラメーターを計算できます。
スキップする行数は (@cnt - 1) / 2 です。奇数の場合、この計算が正しいことは明らかです。これは、2 で割る前に、最初に 1 つの中間値に対して 1 を減算するためです。
これは、式で使用される除算が整数除算であるため、偶数カウントでも正しく機能します。したがって、偶数から 1 を引くと、奇数の値が残ります。
その奇数値を 2 で除算すると、結果の小数部分 (.5) が切り捨てられます。フェッチする行数は 2 - (@cnt % 2) です。カウントが奇数の場合、モジュロ演算の結果は 1 であり、1 行をフェッチする必要があるという考え方です。カウントが偶数の場合、モジュロ演算の結果は 0 であり、2 行をフェッチする必要があります。モジュロ演算の 1 または 0 の結果を 2 から引くと、それぞれ目的の 1 または 2 が得られます。最後に、量の中央値を計算するには、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;
従業員テーブルから給与の中央値を取得するには
with cte as (select salary, ROW_NUMBER() over (order by salary asc) as num from employees)
select avg(salary) from cte where num in ((select (count(*)+1)/2 from employees), (select (count(*)+2)/2 from employees));
以下のロジックを試して、中央値を見つけてください。
以下の数字を持つテーブルを考えてみましょう: 1,1,2,3,4,5
中央値は2.5
with tempa as ( select num,count(num) over() as Cnt, row_number() over (num by num) as Rnum from temp), tempb as ( select round(cnt/2) as ref_value from tempa where mod(cnt ,2)<>0 union all select round(cnt/2) from tempa where mod(cnt,2)=0 union all select round(cnt/2+1) from tempa where mod(cnt,2)=0 ) select avg(num) from tempa where rnum in (select * from tempb);