Функция для вычисления медианы в SQL Server - PullRequest
202 голосов
/ 27 августа 2009

Согласно MSDN , Медиана недоступна как агрегатная функция в Transact-SQL. Однако я хотел бы узнать, возможно ли создать эту функцию (используя функцию Create Aggregate , пользовательскую функцию или какой-либо другой метод).

Каков наилучший способ (если это возможно) сделать это - разрешить вычисление медианного значения (принимая числовой тип данных) в агрегированном запросе?

Ответы [ 29 ]

0 голосов
/ 01 августа 2015

Я пробую несколько вариантов, но из-за того, что мои записи данных имеют повторяющиеся значения, версии 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 голосов
/ 17 июня 2015

Для крупномасштабных наборов данных вы можете попробовать этот GIST:

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

Он работает путем агрегирования различных значений, которые вы найдете в вашем наборе (например, возраст или годрождение и т. д.) и использует оконные функции SQL, чтобы найти любую процентильную позицию, указанную в запросе.

0 голосов
/ 25 февраля 2015
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 голосов
/ 18 февраля 2015

Следующее решение работает при следующих предположениях:

  • Нет повторяющихся значений
  • Нет 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 голосов
/ 17 июля 2013

Это настолько простой ответ, насколько я мог придумать. Работал хорошо с моими данными. Если вы хотите исключить определенные значения, просто добавьте предложение 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 голосов
/ 02 декабря 2017

Это самое оптимальное решение для поиска медиан, о которых я могу думать. Имена в примере основаны на примере Джастина. Убедитесь, что индекс для таблицы 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

UPDATE

Я был немного не уверен, какой метод имеет лучшую производительность, поэтому я провел сравнение между моим методом Джастином Грантом и Джеффом Этвудсом, выполнив запрос на основе всех трех методов в одном пакете, и стоимость пакета каждого запроса составила:

без индекса:

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

А с индексом

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

Я пытался увидеть, насколько хорошо масштабируются запросы, если у вас есть индекс, создавая больше данных из примерно 14 000 строк с коэффициентом от 2 до 512, что в итоге составляет около 7,2 миллионов строк. Обратите внимание, что я удостоверился, что поле CustomeId было уникальным для каждого раза, когда я делал одну копию, чтобы пропорция строк по сравнению с уникальным экземпляром 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 голосов
/ 13 июня 2011

Это работает с 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 голосов
/ 05 сентября 2018

Среднее значение

Это самый простой способ найти медиану атрибута.

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)
0 голосов
/ 18 июня 2013

Новичкам, таким как я, которые изучают самые основы, лично я нахожу этот пример легче следовать, так как легче точно понять, что происходит и откуда берутся медианные значения ...

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

В полном восторге от некоторых из приведенных выше кодов, хотя !!!

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...