Функція обчислення медіани на SQL сервері


227

Згідно з MSDN , Median недоступний як сукупна функція в Transact-SQL. Однак я хотів би з’ясувати, чи можна створити цю функціональність (використовуючи функцію Create Aggregate, функцію, визначену користувачем, або якийсь інший метод).

Який був би найкращий спосіб (якщо це можливо) зробити це - дозволити обчислення медіанного значення (припускаючи числовий тип даних) у сукупному запиті?


Відповіді:


145

ОНОВЛЕННЯ 2019 року: За 10 років, відколи я написав цю відповідь, було розкрито більше рішень, які можуть дати кращі результати. Також випуски SQL Server відтоді (особливо SQL 2012) запровадили нові функції T-SQL, які можна використовувати для обчислення медіанів. Випуски SQL Server також покращили його оптимізатор запитів, який може вплинути на різні види середніх рішень. Net-net, мій оригінальний пост 2009 року все ще в порядку, але можуть бути кращі рішення для сучасних програм SQL Server. Погляньте на цю статтю з 2012 року, яка є чудовим ресурсом: https://sqlperformance.com/2012/08/t-sql-queries/median

Ця стаття виявила, що наступна закономірність набагато, набагато швидша, ніж усі інші альтернативи, принаймні на простій схемі, яку вони перевіряли. Цей розчин був на 373 рази швидшим (!!!), ніж найповільніший ( PERCENTILE_CONT) тестований розчин. Зауважте, що для цього фокусу потрібні два окремі запити, які можуть бути практичними не у всіх випадках. Він також вимагає 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 році дав чудові результати, ваш пробіг може відрізнятися, особливо якщо ви перебуваєте на SQL Server 2014 або новіших версіях. Якщо perf важливий для вашого медіанного обчислення, я настійно рекомендую спробувати та перевірити кілька варіантів, рекомендованих у цій статті, щоб переконатися, що ви знайшли найкращий для вашої схеми.

Буду також особливо обережним, використовуючи функцію (нову в SQL Server 2012), PERCENTILE_CONTяку рекомендується в одній з інших відповідей на це питання, оскільки ця стаття вказує, що ця вбудована функція на 373 рази повільніше, ніж найшвидше рішення. Можливо, що ця невідповідність була покращена за 7 років, але особисто я не використовував би цю функцію на великій таблиці, поки не перевірив її ефективність та інші рішення.

ОРИГІНАЛЬНА ПОШТА 2009 НИСЬ:

Існує маса способів зробити це з різко різними показниками. Ось одне особливо добре оптимізоване рішення від Medians, ROW_NUMBER та продуктивність . Це особливо оптимальне рішення, коли мова йде про фактичні введення-виведення, що виникають під час виконання - воно виглядає дорожче, ніж інші рішення, але насправді набагато швидше.

Ця сторінка також містить обговорення інших рішень та детальну інформацію про тестування продуктивності. Зверніть увагу на використання унікального стовпчика в якості розбірника, якщо є кілька рядків з однаковим значенням медіанного стовпця.

Як і у всіх сценаріях продуктивності бази даних, завжди намагайтеся протестувати рішення з реальними даними про реальне обладнання - ви ніколи не знаєте, коли зміна оптимізатора 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;

12
Я не думаю, що це спрацьовує, якщо у ваших даних є дупи, особливо багато оперізуючих матеріалів. Ви не можете гарантувати, що рядкові номери будуть вибудовуватися. Ви можете отримати кілька дійсно шалених відповідей за свою медіану, або, що ще гірше, взагалі немає медіани.
Джонатан Беерхалтер

26
Ось чому важливим є наявність розмитнювача (SalesOrderId у наведеному вище прикладі коду), тому ви можете переконатися, що порядок рядків, встановлених результатами, відповідає як назад, так і вперед. Часто унікальний первинний ключ робить ідеальний розбірник, оскільки він доступний без окремого пошуку індексу. Якщо немає стовпчика розрізнень (наприклад, якщо в таблиці немає уніфікуючого ключа), для обчислення медіани слід використовувати інший підхід, оскільки, як ви правильно зазначаєте, якщо ви не можете гарантувати, що номери рядків DESC є дзеркальними зображеннями Номери рядків ASC, тоді результати непередбачувані.
Джастін Грант

4
Дякую, перемикаючи стовпчики на свою БД, я скинув розбірник, вважаючи, що це не актуально. У такому випадку це рішення працює дуже добре.
Джонатан Беерхалтер

8
Я пропоную додати коментар до самого коду, описуючи необхідність розбірника.
hoffmanc

4
Дивовижно! давно я знаю його важливість, але тепер я можу дати йому ім'я ... розбірник! Дякую Джастіну!
CodeMonkey

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 () не існує !? Я трохи ПОВЕРХ () Ед, відверто кажучи.
Чарлі Кіліан

Ну, приємно і просто, але зазвичай вам потрібна медіана на певну групову категорію, тобто подібне select gid, median(score) from T group by gid. Вам потрібен відповідний запит для цього?
TMS

1
... Я маю на увазі, як у цьому випадку (2-й запит під назвою "Користувачі з найвищим середнім балом відповідей").
TMS

Томаш - вам вдалося вирішити своє питання "для певної групи"? У мене така ж проблема. Дякую.
Стю Харпер

3
Як використовувати це рішення з групою BY?
Перемислав Ремін

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 було виміряно на 373 рази повільніше (!!!!) порівняно з найшвидшим рішенням, яке вони протестували на SQL Server 2012 на їх конкретній тестовій схемі. Прочитайте статтю, з якою Карл посилався для отримання детальної інформації
Джастін Грант

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

Це дасть вам середній і міжквартирний діапазон одним махом. Якщо ви дійсно хочете лише один рядок, який є медіаною, скасуйте цей пункт.

Якщо ви докладете це до плану пояснення, 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

Це має обчислити правильні медіани та відсоткові значення, коли у вас є парне число даних. Знову ж, коментуйте заключний пункт де, якщо ви хочете лише медіану, а не весь відсотковий розподіл.


1
Це насправді працює досить добре і дозволяє розділити дані.
Джонатан Беерхалтер

3
Якщо це нормально, щоб вимкнутись одним, то вище вказаний запит. Але якщо вам потрібна точна медіана, то у вас виникнуть проблеми. Наприклад, для послідовності (1,3,5,7) медіана становить 4, але запит вище повертається 3. Для (1,2,3503,603,703) медіана - 258, але запит вище повертає 503.
Джастін Грант

1
Ви можете виправити недолік неточності, взявши максимум і хв кожного кварталу в підзапиті, а потім наділивши MAX попереднього та MIN наступного?
Rbjz



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

Якщо ви хочете використовувати функцію 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

Потім ви можете написати запит, щоб обчислити медіану так: SELECT dbo.Median (Поле) З таблиці


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

Наступний запит повертає медіану зі списку значень в одному стовпчику. Він не може бути використаний як або поряд із сукупною функцією, але ви все одно можете використовувати його як підзапит із пунктом 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

У АДС пишіть:

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

7
У випадку парної кількості предметів, медіана - це середнє значення середніх двох позицій, яке не підпадає під дію цього АДС.
Яаков Елліс

1
Чи можете ви переписати це в цілому АДС?
Перемислав Ремін

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

Для безперервної змінної / вимірювання '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

1

Використовуючи сукупність COUNT, ви можете спочатку підрахувати, скільки рядків існує і зберегти в змінній під назвою @cnt. Тоді ви можете обчислити параметри для фільтра OFFSET-FETCH, щоб вказати на основі qty впорядкування, скільки рядків для пропуску (значення зміщення) та скільки для фільтрування (значення отримання).

Кількість рядків, які потрібно пропустити, (@cnt - 1) / 2. Зрозуміло, що для непарного підрахунку цей розрахунок правильний, оскільки ви спочатку віднімаєте 1 для єдиного середнього значення, перш ніж ділити на 2.

Це також працює правильно для рівномірного підрахунку, оскільки поділ, що використовується в виразі, є цілим поділом; тож, віднімаючи 1 з парного числа, вам залишається непарне значення.

Якщо ділити це непарне значення на 2, частка дробу результату (.5) обрізається. Кількість рядків для отримання - 2 - (@cnt% 2). Ідея полягає в тому, що коли число непарних, результат модульної операції дорівнює 1, і вам потрібно отримати 1 ряд. Коли підрахунок рівний результат роботи модуля дорівнює 0, вам потрібно отримати 2 ряди. Віднімаючи результат 1 або 0 модульної операції від 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

Це така ж проста відповідь, як я міг придумати. Добре працював із моїми даними. Якщо ви хочете виключити певні значення, просто додайте до внутрішнього вибору пункт де.

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

Спираючись на відповідь Джеффа Етвуда вище, саме за допомогою 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

Часто нам може знадобитися обчислювати медіану не лише для всієї таблиці, але і для агрегатів щодо певного ідентифікатора. Іншими словами, обчисліть медіану для кожного ідентифікатора в нашій таблиці, де кожен ідентифікатор має багато записів. (на основі рішення, відредагованого @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

ОНОВЛЕННЯ

Я трохи не знав, який метод має найкращі показники, тому я порівняв мій метод Джастіна Гранта і Джеффа Етвудса, запустивши запит, заснований на всіх трьох методах в одній партії, і пакетна вартість кожного запиту:

Без індексу:

  • Шахта 30%
  • Джастін Грантс 13%
  • Джефф Етвудс 58%

І з індексом

  • Шахта 3%.
  • Джастін Грант 10%
  • Джефф Етвудс 87%

Я спробував побачити, наскільки добре запит масштабується, якщо у вас є індекс, створивши більше даних приблизно від 14 000 рядків в коефіцієнті від 2 до 512, що в кінцевому підсумку означає близько 7,2 мільйонів рядків. Примітка. Я переконався, що поле CouomeId є унікальним для кожного разу, коли я робив одну копію, тому пропорція рядків порівняно з унікальним екземпляром CustomerId залишалася незмінною. Поки я робив це, я запускав виконання, де потім перебудовував індекс, і я помітив, що результати стабілізувалися приблизно в коефіцієнт 128 із даними, які я мав до цих значень:

  • Шахта 3%.
  • Джастін Гранти 5%
  • Джефф Етвудс 92%

Мені було цікаво, як на ефективність може вплинути масштабування кількості рядків, але зберігаючи унікальний CustomerId постійний, тому я встановив новий тест, де я робив саме це. Тепер замість стабілізації коефіцієнт пакетної вартості постійно розходився, а також приблизно 20 рядків на CustomerId в середньому у мене було в кінцевому підсумку близько 10000 рядків за такий унікальний Id. Номери, де:

  • Шахта 4%
  • Джастінс 60%
  • Джеффи 35%

Я переконався, що я реалізував правильно кожен метод, порівнюючи результати. Мій висновок - метод, який я використовував, як правило, швидший, поки існує індекс. Також зауважили, що саме цей метод рекомендований для цієї конкретної проблеми у цій статті https://www.microsoftpressstore.com/articles/article.aspx?p=2314819&seqNum=5

Спосіб ще більше покращити ефективність наступних дзвінків на цей запит - зберегти інформацію про кількість в допоміжній таблиці. Ви навіть можете підтримувати його, маючи тригер, що оновляє, і містить інформацію про кількість рядків SalesOrderHeader, залежних від CustomerId, звичайно, ви також можете просто зберігати медіану.


0

Для великих масштабних наборів даних ви можете спробувати цей ГІСТ:

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

Він працює, агрегуючи різні значення, які ви знайдете у своєму наборі (наприклад, вік, рік народження тощо), і використовує функції вікна SQL для знаходження будь-якої процентної позиції, вказаної у запиті.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.