Pregunta

Según MSDN , mediana no está disponible como una función de agregado en Transact-SQL. Sin embargo, me gustaría saber si es posible crear esta funcionalidad (utilizando el crear función de agregado , la función definida por el usuario, o algún otro método).

¿Cuál sería la mejor manera (si es posible) para hacer esto - permitir el cálculo de un valor de la mediana (suponiendo un tipo de dato numérico) en una consulta agregada

¿Fue útil?

Solución

Hay un montón de maneras de hacer esto, con un rendimiento que varía dramáticamente. He aquí una solución particularmente bien optimizado, de Las medianas, ROW_NUMBERs, y el rendimiento . Esta es una solución óptima sobre todo cuando se trata de lo real / S generada durante la ejecución -. Se parece más costosa que otras soluciones, pero en realidad es mucho más rápido

Esa página también contiene una discusión de otras soluciones y detalles de la prueba de rendimiento. Observe el uso de una columna única como desambiguador en caso de que haya varias filas con el mismo valor de la columna media.

Al igual que con todos los escenarios de rendimiento de base de datos, siempre trata de probar una solución con datos reales de hardware real - nunca se sabe cuando un cambio a optimizador de SQL Server o una peculiaridad en su entorno harán una solución normalmente rápida más lenta <. / 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;

Otros consejos

Si está utilizando SQL 2005 o mejor este es un bonito, sencillo y tantos cálculo mediana para una sola columna en una tabla:

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

En SQL Server 2012 se debe utilizar 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

Vea también: http://blog.sqlauthority.com/2011/11/20/sql-server-introduction-to-percentile_cont-analytic-functions-introduced-in-sql-server-2012/

Mi respuesta rápida original era:

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

Esto le dará a la mediana y rango intercuartil de un solo golpe. Si realmente sólo desea una fila que es la mediana continuación, elimine el comentario de la cláusula where.

Cuando se quede en un plan que explicar, el 60% del trabajo es clasificar los datos que es inevitable en el cálculo de las estadísticas dependientes de la posición de este tipo.

He modificado la respuesta para seguir la excelente sugerencia de Robert Ševčík-Robajz en los comentarios a continuación:

;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

Esto debe calcular la mediana correcta y valores del percentil cuando se tiene un número par de elementos de datos. Una vez más, elimine la cláusula final donde si solo deseas la mediana y no toda la distribución porcentual.

Aún mejor:

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);

Desde el Maestro mismo, Itzik Ben-Gan !

MS SQL Server 2012 (y posteriores) tiene la función PERCENTILE_DISC que calcula un percentil específico de valores ordenados. PERCENTILE_DISC (0.5) calculará la mediana - https://msdn.microsoft.com/ en-us / library / hh231327.aspx

simple, rápida, precisa

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)  

Si desea utilizar la función de agregado Crear en SQL Server, esto es cómo hacerlo. Hacerlo de esta manera tiene la ventaja de ser capaz de escribir consultas limpias. Nota de este proceso podría ser adaptado para calcular un valor percentil con bastante facilidad.

Crear un nuevo proyecto de Visual Studio y establecer el marco de destino para .NET 3.5 (esto es para SQL 2008, que puede ser diferente en SQL 2012). A continuación, cree un archivo de clase y poner en el código siguiente, o C # equivalentes:

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

A continuación, compilar y copiar el archivo DLL y el archivo PDB a su máquina de SQL Server y ejecute el siguiente comando en 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

A continuación, puede escribir una consulta para calcular la mediana de la siguiente manera:     SELECT dbo.Median (campo) de la tabla

Me encontré con esta página en la búsqueda de una solución basada en conjunto con la mediana. Después de mirar algunas de las soluciones aquí, se me ocurrió lo siguiente. La esperanza se ayuda / obras.

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

La siguiente consulta devuelve el mediana a partir de una lista de valores en una columna. No se puede utilizar como o junto con una función de agregado, pero todavía se puede utilizar como un sub-consulta con una cláusula WHERE en el selecto interior.

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

A pesar de que la solución de Justin subvención parece sólida He encontrado que cuando se tiene una serie de valores duplicados dentro de una clave de partición dado los números de fila para los valores de ASC duplicados terminan fuera de secuencia para que no alinear correctamente.

Aquí está un fragmento de mi resultado:

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

He utilizado el código de Justin como base para esta solución. Aunque no es tan eficiente dado el uso de múltiples tablas derivadas lo hace resolver el problema fila pedido que me encontré. Cualquier mejora sería bienvenido ya que no soy la experimentada en 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

El ejemplo de Justin arriba es muy bueno. Pero esa necesidad de clave primaria debe indicar con toda claridad. He visto que el código en la naturaleza sin la llave y los resultados son malos.

La queja recibo de la PERCENTILE_CONT es que no le dará un valor real del conjunto de datos. Para llegar a un "medio" que es un valor real del conjunto de datos utilizar 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

En una UDF, escribir:

 Select Top 1 medianSortColumn from Table T
  Where (Select Count(*) from Table
         Where MedianSortColumn <
           (Select Count(*) From Table) / 2)
  Order By medianSortColumn

Ver otras soluciones para el cálculo de la mediana en SQL aquí: " Una manera sencilla de calcular la mediana con MySQL " (las soluciones son en su mayoría independiente del proveedor).

Para una variable / medida 'col1' continua desde 'tabla1'

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

Yo quería encontrar una solución por mi cuenta, pero mi cerebro tropezó y cayó en el camino. I piensan funciona, pero no me pidan que lo explique en la mañana. : 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]

Esto funciona con 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

)

Para los novatos como yo, que están aprendiendo los conceptos básicos, personalmente encuentro este ejemplo más fácil de seguir, ya que es más fácil de entender exactamente lo que está sucediendo y donde los valores medios están viniendo de ...

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

En el temor absoluto de algunos de los códigos anteriores, aunque !!!

Esto es tan simple como una respuesta que pudiera ocurrir. Funcionado bien con mis datos. Si desea excluir ciertos valores sólo tiene que añadir una cláusula donde al selecto interior.

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

La siguiente solución funciona bajo estos supuestos:

  • No hay valores duplicados
  • No NULL

Código:

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

Trato con varias alternativas, pero debido a mis registros de datos ha repetido los valores, las versiones ROW_NUMBER parece no son una opción para mí. Así que aquí la consulta utilicé (una versión con 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;

Sobre la base de la respuesta de Jeff Atwood por encima de aquí es con GROUP BY y una consulta correlacionada para obtener la mediana para cada grupo.

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

Con frecuencia, es posible que tengamos para calcular La mediana no sólo por toda la mesa, pero para agregados con respecto a alguna identificación. En otras palabras, el cálculo de la mediana para cada ID en nuestra mesa, donde cada ID tiene muchos registros. (Basado en la solución editado por @gdoron: buen rendimiento y trabaja en muchos 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;

Espero que ayuda.

En su pregunta, Jeff Atwood ya había dado la solución simple y eficaz. Pero, si usted está buscando algún método alternativo para calcular la mediana, por debajo de código SQL le ayudará.

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;

Si usted está buscando para el cálculo de la mediana en MySQL, este github enlace será útil.

Esta es la solución más óptima para la búsqueda de las medianas que puedo imaginar. Los nombres en el ejemplo se basa en Justin ejemplo. Asegúrese de que un índice para la tabla Sales.SalesOrderHeader existe con columnas de índice CustomerId y TotalDue en ese orden.

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

Actualizar

Yo estaba un poco inseguro acerca de qué método tiene mejor rendimiento, por lo que hizo una comparación entre mi método Justin Subvenciones y Jeff Atwoods mediante la ejecución de consulta basada en los tres métodos en un lote y el costo lote de cada consulta fueron:

Sin índice:

  • Mina 30%
  • Justin Grants 13%
  • Jeff Atwoods 58%

Y con índice

  • Mine 3%.
  • Justin Grants 10%
  • Jeff Atwoods 87%

Traté de ver lo bien que escalar las consultas si tiene índice mediante la creación de más datos de alrededor de 14 000 filas por un factor de 2 hasta 512 lo que significa que al final de todo 7,2 millones de filas. Tenga en cuenta que hice campo CustomeId seguro donde único para cada vez que lo hice una sola copia, por lo que la proporción de filas en comparación con instancia única de CustomerId se mantuvo constante. Mientras hacía esto me encontré con las ejecuciones en el que reconstruyó el índice después, y me di cuenta de los resultados estabilizado en torno a un factor de 128 con los datos que tenía que estos valores:

  • Mine 3%.
  • Justin Subvenciones 5%
  • Jeff Atwoods 92%

Me preguntaba cómo el rendimiento podría haber sido afectada por la ampliación del número de filas, pero manteniendo constante CustomerId único, por lo que la configuración de una nueva prueba donde hice precisamente esto. Ahora, en lugar de estabilizar, la relación costo lote mantuvo divergente, también en lugar de alrededor de 20 filas por CustomerId por media que tenía en el extremo alrededor de 10000 filas por tales Id único. Los números donde:

  • Mine 4%
  • Justins 60%
  • Jeffs 35%

Me aseguré de que implementa cada método correcto mediante la comparación de los resultados. Mi conclusión es el método que utiliza es generalmente más rápido mientras exista el índice. También se dio cuenta de que este método es lo que se recomienda para este problema particular en este artículo https :? //www.microsoftpressstore.com/articles/article.aspx p = 2314819 y SEQNUM = 5

Una forma de mejorar aún más el rendimiento de las llamadas posteriores a esta consulta es aún más que persista la información de la cuenta en una mesa auxiliar. Incluso se puede mantener por tener un disparador que se actualizan y mantiene información sobre el recuento de filas SalesOrderHeader depende de CustomerId, por supuesto que se pueden almacenar a continuación sencilla la mediana también.

Para grandes conjuntos de datos de gran escala, puede probar este GIST:

https://gist.github.com/chrisknoll/1b38761ce8c5016ec5b2

Funciona mediante la agregación de los valores distintos que se encontraría en su conjunto (como las edades, o año de nacimiento, etc.), y utiliza funciones de la ventana de SQL para localizar cualquier posición percentil se especifica en la consulta.

La mediana Finding

Este es el método más simple para encontrar la mediana de un atributo.

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)
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top