Найти два локальных средних в одном наборе данных SQL Server - PullRequest
1 голос
/ 01 мая 2019

На заводе в нашей компании есть физический процесс, который имеет двухэтапный запуск и двухэтапный процесс. Когда виджет начинает входить в процесс, создается новая запись, содержащая идентификатор виджета и метку времени (DateTimeCreated), и как только виджет полностью входит в процесс, другая метка времени регистрируется в другом поле для той же записи (DateTimeUpdated). Интервал составляет считанные минуты.

Точно так же, когда виджет начинает выходить из процесса, создается другая запись, содержащая идентификатор виджета и DateTimeCreated, причем DateTimeUpdated заполняется, когда виджет полностью выходит из процесса. В текущем дизайне таблицы «выходящая» запись неотличима от «входящей» записи (хотя данный идентификатор виджета встречается только один или два раза, поэтому представление может использовать этот факт для проведения различия, но давайте пока проигнорируем это).

Общее время работы виджета составляет несколько дней, но это не имеет большого значения для обсуждения. Что важно , так это то, что интервал при выходе из процесса всегда больше, чем при входе. Таким образом, очень упрощенный, воображаемый набор отсортированных значений интервала может выглядеть следующим образом:

1, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 6, 7, 7, 7, 7, 8, 8, 8, 8, 10, 10, 10

Вы можете видеть, что есть пик в интервалах вокруг 3-минутной отметки («вход») и другой пик вокруг 7/8-минутной отметки («выход»). Я также исключил интервалы в 5 минут, чтобы продемонстрировать, что интервалы входа и выхода могут считаться взаимоисключающими.

Мы хотим ежедневно отслеживать производительность каждого этапа процесса, используя запрос для определения локальных средних значений кластеров точек входа и выхода. Таким образом, концептуально два набора данных могут быть разделены по обе стороны от общего среднего (в данном случае 5,375), а затем среднее значение рассчитывается для значений ниже разделения (2,75) и другого среднего значения выше разделения (8). Используя данные выше (в случайном порядке), средние значения отображаются в виде пунктирных линий на диаграмме ниже.

Interval Chart

Мой текущий подход заключается в использовании двух общих табличных выражений, за которыми следует заключительный запрос объединения трех таблиц. Кажется, все в порядке, но я не могу не чувствовать, что это может быть лучше. Кто-нибудь хотел бы предложить альтернативный подход или другие наблюдения?

Table Structure

WITH cte_Raw AS
     (
            SELECT
                   DATEDIFF(minute, DateTimeCreated, DateTimeUpdated) AS [Interval]
            FROM
                   MyTable
            WHERE
                   DateTimeCreated > CAST(CAST(GETDATE() AS date) AS datetime)  -- Today
     )
   , cte_Midpoint AS
     (
            SELECT
                   AVG(Interval) AS Interval
            FROM
                   cte_Raw
     )
SELECT
           AVG([Entry].Interval) AS AverageEntryInterval
         , AVG([Exit].Interval)  AS AverageExitInterval
FROM
           cte_Raw AS [Entry]
           INNER JOIN
                      cte_Midpoint
                      ON
                                 [Entry].Interval < cte_Midpoint.Interval
           INNER JOIN
                      cte_Raw AS [Exit]
                      ON
                                 [Exit].Interval > cte_Midpoint.Interval

Ответы [ 2 ]

1 голос
/ 01 мая 2019

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

Если вы просто ищете среднее значение, которое меньше, чем общее среднее и больше, чем общее среднее, тогда вы используете оконные функции:

WITH t AS (
      SELECT t.*, v.[Interval],
             AVG(v.[Interval]) OVER () as avg_interval
      FROM MyTable t CROSS JOIN
           (VALUES (DATEDIFF(minute, DateTimeCreated, DateTimeUpdated))
           ) v(Interval)
      WHERE DateTimeCreated > CAST(CAST(GETDATE() AS date) AS datetime)
     )
SELECT AVG(CASE WHEN t.[Interval] < t.avg_interval THEN t.[Interval] END) AS AverageEntryInterval,
       AVG(CASE WHEN t.[Interval] > t.avg_interval THEN t.[Interval] END) AS AverageExitInterval
FROM t;
0 голосов
/ 02 мая 2019

Я решил опубликовать свой собственный ответ, так как на момент написания ни один из двух предложенных ответов не запускался. Однако я удалил операторы JOIN и использовал подход CASE, предложенный Гордоном.

Я также умножил результат DATEDIFF на 1,0, чтобы предотвратить округление результатов с помощью функции AVG.

WITH cte_Raw AS
     (
            SELECT
                   1.0 * DATEDIFF(minute, DateTimeCreated, DateTimeUpdated) AS [Interval]
            FROM
                   MyTable
            WHERE
                   DateTimeCreated > CAST(CAST(GETDATE() AS date) AS datetime)  -- Today
     )
   , cte_Midpoint AS
     (
            SELECT
                   AVG(Interval) AS Interval
            FROM
                   cte_Raw
     )
SELECT AVG(CASE WHEN cte_Raw.Interval < cte_Midpoint.Interval THEN cte_Raw.[Interval] END) AS AverageEntryInterval,
       AVG(CASE WHEN cte_Raw.Interval > cte_Midpoint.Interval THEN cte_Raw.[Interval] END) AS AverageExitInterval
FROM cte_Raw CROSS JOIN cte_Midpoint

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

...