Функция для вычисления медианы в SQL Server
по данным MSDN, Median недоступна в качестве агрегатной функции в Transact-SQL. Однако, я хотел бы узнать, можно ли создать эту функцию (с помощью Создать Сводные функция, определяемая пользователем функция или какой-либо другой метод).
каков был бы лучший способ (если это возможно) сделать это - разрешить вычисление медианного значения (предполагая числовой тип данных) в агрегированном запросе?
30 ответов:
есть много способов сделать это, с резко различной производительности. Вот одно особенно хорошо оптимизированное решение, от медианы, ROW_NUMBERs и производительность. Это особенно оптимальное решение, когда дело доходит до фактического ввода-вывода, генерируемого во время выполнения-это выглядит дороже, чем другие решения, но на самом деле гораздо быстрее.
что страница также содержит обсуждение других решений и тестирования производительности. Обратите внимание на использование уникального столбца в качестве средства устранения неоднозначности при наличии нескольких строк с одинаковым значением медианного столбца.
Как и во всех сценариях производительности базы данных, всегда пытайтесь протестировать решение с реальными данными на реальном оборудовании-вы никогда не знаете, когда изменение оптимизатора 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, Вы должны использовать функция 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/
мой оригинальный ответ был:
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% работы сортируют данные, которые неизбежны при вычислении статистики, зависящей от позиции, как это.
я исправил ответ, чтобы следовать отличному предложению Роберта Шевчик-Робайз в комментариях ниже:
;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);
от самого мастера, Ицик Бен-Ган!
MS SQL Server 2012 (и более поздние версии) имеет функцию PERCENTILE_DISC, которая вычисляет определенный процентиль для отсортированных значений. PERCENTILE_DISC (0.5) вычислит медиану -https://msdn.microsoft.com/en-us/library/hh231327.aspx
Я просто наткнулся на эту страницу, ища решение на основе набора для медианы. Посмотрев на некоторые из решений здесь, я пришел к следующему. Надежда-это помогает / работает.
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
простой, быстрый, точный
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)
Если вы хотите использовать функцию Create Aggregate в 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
затем вы можете написать запрос для вычисления медианы следующим образом: Выберите dbo.Медиана (поле) из таблицы
следующий запрос возвращает в среднем из списка значений в одном столбце. Он не может использоваться как или вместе с агрегатной функцией, но вы все равно можете использовать его как подзапрос с предложением WHERE во внутреннем select.
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
пример Джастина выше очень хорош. Но эта потребность в первичном ключе должна быть сформулирована очень четко. Я видел, что код в дикой природе без ключа и результаты плохие.
жалоба, которую я получаю о 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
хотя решение Джастина гранта кажется твердым, я обнаружил, что когда у вас есть несколько повторяющихся значений в данном ключе раздела, номера строк для повторяющихся значений 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
в 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' из 'table1'
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
в абсолютном трепете от некоторых кодов выше, хотя!!!
Это самый простой ответ, который я мог придумать. Хорошо работал с моими данными. Если вы хотите исключить определенные значения, просто добавьте предложение where во внутренний select.
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
следующее решение работает в этих предположениях:
- нет повторяющихся значений
- Никаких Нулей
код:
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
часто нам может потребоваться вычислить медиану не только для всей таблицы, но и для агрегатов по некоторому идентификатору. Другими словами, вычислите медиану для каждого идентификатора в нашей таблице, где каждый идентификатор имеет много записей. (на основе решения под редакцией @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 ссылке будет полезно.
Это самое оптимальное решение для поиска медиан, которое я могу придумать. Имена в Примере основаны на примере Джастина. Убедитесь, что индекс для таблицы Продажи.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
обновление
Я немного вы не уверены, какой способ имеет лучшую производительность, поэтому я сделал сравнение между моим методом Джастин гранты и Джефф Atwoods, выполнив запрос на основе всех трех методов в одном пакете и стоимость пакета каждого запроса были:
без индекса:
- помоему 30%
- Джастин Дает 13%
- Джефф Atwoods 58%
и с индексом
- помоему 3%.
- Джастин Дает 10%
- Джефф Atwoods 87%
Я попытался увидеть, насколько хорошо масштабируются запросы, если у вас есть индекс, создавая больше данных примерно из 14 000 строк в 2 раза до 512, что означает в итоге около 7,2 миллионов строк. Примечание. Я убедился, что поле CustomeId уникально для каждого раза, когда я делал одну копию, поэтому доля строк по сравнению с уникальным экземпляром CustomerId была постоянной. В то время как я делал это, я запускал выполнение, где я впоследствии перестроил индекс, и я заметил, что результаты стабилизировались примерно в 128 раз с данными, которые у меня были для этих значений:
- помоему 3%.
- Джастин Дает 5%
- Джефф Atwoods 92%
Я задавался вопросом, как на производительность могло повлиять масштабирование количества строк, но сохранение уникальной константы CustomerId, поэтому я настроил новый тест, где я сделал именно это. Теперь вместо стабилизации соотношение стоимости пакета продолжало расходиться, также вместо примерно 20 строк на CustomerId в среднем у меня было в конце около 10000 строк на такой уникальный идентификатор. Цифры, где:
- шахты 4%
- Justins 60%
- Jeffs 35%
Я убедился, что я реализовал каждый метод правильно, сравнивая результаты. Мой вывод заключается в том, что метод, который я использовал, обычно быстрее, пока существует индекс. Также заметил, что этот метод является то, что рекомендуется для этой конкретной проблемы в этой статье https://www.microsoftpressstore.com/articles/article.aspx?p=2314819&seqNum=5
способ еще больше повысить производительность последующих вызовов этого запроса еще больше заключается в сохранении подсчет информации во вспомогательной таблице. Вы даже можете поддерживать его, имея триггер, который обновляет и хранит информацию о количестве строк SalesOrderHeader, зависящих от CustomerId, конечно, вы также можете просто хранить медиану.
для крупномасштабных наборов данных, вы можете попробовать эту суть:
https://gist.github.com/chrisknoll/1b38761ce8c5016ec5b2
Он работает путем агрегирования различных значений, которые вы найдете в наборе (таких как возраст или год рождения и т. д.) и использует функции окна SQL для поиска любой позиции процентиля, указанной в запросе.