SQL Serverで中央値を計算する関数


227

MSDNによると、MedianはTransact-SQLの集計関数として使用できません。ただし、この機能を作成できるかどうかを確認したいと思います(Create Aggregate関数、ユーザー定義関数、またはその他の方法を使用)。

これを行うには(可能な場合)最良の方法は何ですか?集約クエリで中央値(数値データ型を想定)の計算を可能にしますか?


回答:


145

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以降を使用している場合は、走行距離が異なる場合があります。中央値の計算にperfが重要な場合は、その記事で推奨されているいくつかのオプションを試し、perfテストを実行して、スキーマに最適なオプションが見つかることを確認することを強くお勧めします。

上記のリンク先の記事で、この組み込み関数は最速のソリューションより373倍遅いことがわかったため、この質問の他の回答のPERCENTILE_CONT 1つで推奨されている(SQL Server 2012の新機能)関数の使用にも特に注意します。それ以来7年間でこの格差が改善された可能性がありますが、個人的には、他のソリューションと比較してそのパフォーマンスを確認するまで、大きなテーブルでこの関数を使用しませんでした。

2009年の元の投稿は以下のとおりです。

これを行う方法はたくさんあり、パフォーマンスは劇的に異なります。これは、中央値、ROW_NUMBER、パフォーマンスから特によく最適化された1つのソリューションです。これは、実行中に生成される実際のI / Oに関しては、特に最適なソリューションです。他のソリューションよりもコストがかかりますが、実際にははるかに高速です。

そのページには、他のソリューションとパフォーマンステストの詳細の説明も含まれています。中央値列の同じ値を持つ複数の行がある場合の明確化子としての一意の列の使用に注意してください。

すべてのデータベースパフォーマンスシナリオと同様に、実際のハードウェアで実際のデータを使用してソリューションをテストしてみてください。SQLServerのオプティマイザーへの変更や環境の特殊性により、通常は高速なソリューションが遅くなる時期はわかりません。

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 2012年

4
驚くばかり!長い間、その重要性を知っていましたが、名前を付けることができます... ジャスティンありがとう!
CodeMonkey 2013

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()です。
チャーリーキリアン2012年

まあ、素晴らしくてシンプルですが、通常は特定のグループカテゴリごとに中央値が必要select gid, median(score) from T group by gidです。そのために相関サブクエリが必要ですか?
TMS 2013

1
... つまり、この場合と同じです(2番目のクエリは、「中央値の回答スコアが最も高いユーザー」です)。
TMS 2013

トーマス-「特定のグループカテゴリごと」の問題の解決に成功しましたか?同じ問題があるので。ありがとう。
Stu Harper

3
GROUP BYでこのソリューションを使用する方法?
Przemyslaw Remin

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?そうしないと、多くの重複行ができてしまいます。
コンスタンティン

1
これが答えです。なぜここまでスクロールしなければならなかったのかわからない
FistOfFury

を使用した控えめなバージョンもあります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

これにより、一気に中央値と四分位範囲が得られます。中央値である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句のコメントを外します。


1
これは実際にはかなりうまく機能し、データの分割を可能にします。
Jonathan Beerhalter

3
1つずらしても問題ない場合は、上記のクエリで問題ありません。しかし、正確な中央値が必要な場合は、問題が発生します。例えば、シーケンス(1,3,5,7)の中央値は4であるがために戻る3.上記のクエリ(1,2,3,503,603,703)のための中央値は258であるが、戻り503上のクエリ
ジャスティン・グラント

1
サブクエリで各四分位数の最大値と最小値を取得し、次に前のMAXと次のMINを平均することにより、不正確さの欠陥を修正できますか?
Rbjz 2013


8

MS SQL Server 2012以降には、並べ替えられた値の特定の百分位数を計算するPERCENTILE_DISC関数があります。PERCENTILE_DISC(0.5)は中央値を計算します -https://msdn.microsoft.com/en-us/library/hh231327.aspx


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でCreate Aggregate関数を使用する場合は、次のようにします。この方法で行うと、クリーンなクエリを記述できるという利点があります。このプロセスは、パーセンタイル値をかなり簡単に計算するように適合できることに注意してください。

新しいVisual Studioプロジェクトを作成し、ターゲットフレームワークを.NET 3.5に設定します(これはSQL 2008の場合です。SQL2012では異なる場合があります)。次に、クラスファイルを作成し、次のコードまたは同等の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

次のクエリは、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

3

ジャスティングラントのソリューションはしっかりしているように見えますが、特定のパーティションキー内に多数の重複値がある場合、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

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
アイテムの数が偶数の場合、中央値は2つの中間アイテムの平均であり、この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が得られます。最後に、中央値の数量を計算するには、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

自分で解決策を考えたかったのですが、頭がおかしくなり途中で落ちてしまいました。うまくいくと思いますが、午前中に説明するように言わないでください。: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)

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

あなたの質問については、ジェフ・アトウッドはすでにシンプルで効果的なソリューションを提供してくれました。ただし、中央値を計算する別の方法を探している場合は、以下の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

これは、私が考えることができる中央値を見つけるための最も最適なソリューションです。例の名前は、ジャスティンの例に基づいています。テーブル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%

インデックスがある場合、2から512倍の係数で約14 000行からより多くのデータを作成することで、クエリがどの程度適切にスケーリングするかを確認しようとしました。つまり、最終的には約720万行になります。注:1つのコピーを実行するたびにCustomeIdフィールドが一意になるようにしたので、CustomerIdの一意のインスタンスと比較した行の比率は一定に保たれました。これを実行しているときに、後でインデックスを再構築する実行を実行しました。結果は、これらの値に必要なデータで約128の係数で安定していることに気付きました。

  • 鉱山3%。
  • ジャスティン・グランツ5%
  • ジェフ・アトウッズ92%

行数のスケーリングによってパフォーマンスがどのように影響を受ける可能性があるかを考えましたが、一意のCustomerIdを一定に維持したため、これを実行する新しいテストをセットアップしました。これで、安定化する代わりに、バッチコスト比は発散し続けました。また、このような一意のIDごとに、最終的には、平均でCustomerIdごとに約20行が約10000行でした。以下の数字:

  • 鉱山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.