рассчитать среднюю разницу во времени между каждым этапом - PullRequest
0 голосов
/ 04 февраля 2019

Как рассчитать среднюю разницу во времени между каждым этапом.

Проблема с фактическим набором данных состоит в том, что не каждый идентификатор пройдет все этапы ... некоторые пропустят этапы, и дата не будет непрерывной для всех идентификаторов, как показано ниже.

id    date        status
1     1/1/18      requirement
1     1/8/18      analysis
1     ?           design
1     1/30/18     closed
2     2/1/18      requirement
2     2/18/18     closed
3     1/2/18      requirement
3     1/29/18     analysis
3     ?           accepted 
3     2/5/18      closed

? - у нас также отсутствуют даты

Expected output

id    date        status      time_spent
1     1/1/18      requirement   0
1     1/8/18      analysis      7
1     ?           design       
1     1/30/18     closed        22
2     2/1/18      requirement   0
2     2/18/18     closed         17
3     1/2/18      requirement    0
3     1/29/18     analysis       27
3     ?           accepted       
3     2/5/18      closed         24      

status         avg(timespent)
requirement     0
analysis        17
design    
closed          21

Ответы [ 3 ]

0 голосов
/ 04 февраля 2019

Я согласен с @MatthewMcPeak.Ваши требования кажутся немного странными: вы тратите ноль дней requirement стадии, но тратите в среднем 21 день на closed?Fnord.

Это решение обрабатывает представленную дату как start дату этапа и вычисляет разницу между ней и start_date следующей фазы.

with cte as (
    select status
           , lead(dd ignore nulls) over (partition by id order by dd) - dd as dt_diff
    from your_table)
select status, avg(dt_diff) as avg_ela
from cte
group by status
/
0 голосов
/ 04 февраля 2019

Если вы хотите включить все этапы для каждого d и оценить время, затраченное на каждый (с использованием линейной интерполяции), то вы можете создать подзапрос со всеми статусами и использовать PARTITION OUTER JOIN, чтобы присоединиться к ним, а затемиспользуйте LAG и LEAD, чтобы найти диапазон дат, в котором находится статус, и интерполируйте между:

Настройка Oracle :

CREATE TABLE data ( d, dt, status ) AS
SELECT 1, TO_DATE( '1/1/18', 'MM/DD/YY' ),  'requirement' FROM DUAL UNION ALL
SELECT 1, TO_DATE( '1/8/18', 'MM/DD/YY' ),  'analysis'    FROM DUAL UNION ALL
SELECT 1, NULL,                             'design'      FROM DUAL UNION ALL
SELECT 1, TO_DATE( '1/30/18', 'MM/DD/YY' ), 'closed'      FROM DUAL UNION ALL
SELECT 2, TO_DATE( '2/1/18', 'MM/DD/YY' ),  'requirement' FROM DUAL UNION ALL
SELECT 2, TO_DATE( '2/18/18', 'MM/DD/YY' ), 'closed'      FROM DUAL UNION ALL
SELECT 3, TO_DATE( '1/2/18', 'MM/DD/YY' ),  'requirement' FROM DUAL UNION ALL
SELECT 3, TO_DATE( '1/29/18', 'MM/DD/YY' ), 'analysis'    FROM DUAL UNION ALL
SELECT 3, NULL,                             'accepted'    FROM DUAL UNION ALL
SELECT 3, TO_DATE( '2/5/18', 'MM/DD/YY' ),  'closed'      FROM DUAL;

Запрос :

WITH statuses ( status, id ) AS (
  SELECT 'requirement', 1 FROM DUAL UNION ALL
  SELECT 'analysis',    2 FROM DUAL UNION ALL
  SELECT 'design',      3 FROM DUAL UNION ALL
  SELECT 'accepted',    4 FROM DUAL UNION ALL
  SELECT 'closed',      5 FROM DUAL
),
ranges ( d, dt, status, id, recent_dt, recent_id, next_dt, next_id ) AS (
  SELECT d.d,
         d.dt,
         s.status,
         s.id,
         NVL(
           d.dt,
           LAG( d.dt, 1 )
             IGNORE NULLS OVER ( PARTITION BY d.d ORDER BY s.id )
         ),
         NVL2(
           d.dt,
           s.id,
           LAG( CASE WHEN d.dt IS NOT NULL THEN s.id END, 1 )
             IGNORE NULLS OVER ( PARTITION BY d.d ORDER BY s.id )
         ),
         LEAD( d.dt, 1, d.dt )
           IGNORE NULLS OVER ( PARTITION BY d.d ORDER BY s.id ),
         LEAD( CASE WHEN d.dt IS NOT NULL THEN s.id END, 1, s.id + 1 )
           IGNORE NULLS OVER ( PARTITION BY d.d ORDER BY s.id )
  FROM   data d
         PARTITION BY ( d )
         RIGHT OUTER JOIN statuses s
         ON ( d.status = s.status )
)
SELECT d,
       dt,
       status,
       ( next_dt - recent_dt ) / (next_id - recent_id ) AS estimated_duration
FROM   ranges;

Вывод :

 D | DT        | STATUS      |                       ESTIMATED_DURATION
-: | :-------- | :---------- | ---------------------------------------:
 1 | 01-JAN-18 | requirement |                                        7
 1 | 08-JAN-18 | analysis    | 7.33333333333333333333333333333333333333
 1 | <em>null</em>      | design      | 7.33333333333333333333333333333333333333
 1 | <em>null</em>      | accepted    | 7.33333333333333333333333333333333333333
 1 | 30-JAN-18 | closed      |                                        0
 2 | 01-FEB-18 | requirement |                                     4.25
 2 | <em>null</em>      | analysis    |                                     4.25
 2 | <em>null</em>      | design      |                                     4.25
 2 | <em>null</em>      | accepted    |                                     4.25
 2 | 18-FEB-18 | closed      |                                        0
 3 | 02-JAN-18 | requirement |                                       27
 3 | 29-JAN-18 | analysis    | 2.33333333333333333333333333333333333333
 3 | <em>null</em>      | design      | 2.33333333333333333333333333333333333333
 3 | <em>null</em>      | accepted    | 2.33333333333333333333333333333333333333
 3 | 05-FEB-18 | closed      |                                        0

Запрос 2 :

Тогда вы можете легко изменитьчто взять среднее для каждого статуса:

WITH statuses ( status, id ) AS (
  SELECT 'requirement', 1 FROM DUAL UNION ALL
  SELECT 'analysis',    2 FROM DUAL UNION ALL
  SELECT 'design',      3 FROM DUAL UNION ALL
  SELECT 'accepted',    4 FROM DUAL UNION ALL
  SELECT 'closed',      5 FROM DUAL
),
ranges ( d, dt, status, id, recent_dt, recent_id, next_dt, next_id ) AS (
  SELECT d.d,
         d.dt,
         s.status,
         s.id,
         NVL(
           d.dt,
           LAG( d.dt, 1 )
             IGNORE NULLS OVER ( PARTITION BY d.d ORDER BY s.id )
         ),
         NVL2(
           d.dt,
           s.id,
           LAG( CASE WHEN d.dt IS NOT NULL THEN s.id END, 1 )
             IGNORE NULLS OVER ( PARTITION BY d.d ORDER BY s.id )
         ),
         LEAD( d.dt, 1, d.dt )
           IGNORE NULLS OVER ( PARTITION BY d.d ORDER BY s.id ),
         LEAD( CASE WHEN d.dt IS NOT NULL THEN s.id END, 1, s.id + 1 )
           IGNORE NULLS OVER ( PARTITION BY d.d ORDER BY s.id )
  FROM   data d
         PARTITION BY ( d )
         RIGHT OUTER JOIN statuses s
         ON ( d.status = s.status )
)
SELECT status,
       AVG( ( next_dt - recent_dt ) / (next_id - recent_id ) ) AS estimated_duration
FROM   ranges
GROUP BY status, id
ORDER BY id;

Результаты :

STATUS      |                       ESTIMATED_DURATION
:---------- | ---------------------------------------:
requirement |                                    12.75
analysis    | 4.63888888888888888888888888888888888889
design      | 4.63888888888888888888888888888888888889
accepted    | 4.63888888888888888888888888888888888889
closed      |                                        0

db <> Fiddle здесь

0 голосов
/ 04 февраля 2019

Вы можете использовать функции управления окнами LAG (или LEAD), чтобы получить данные предыдущего (или следующего) статуса для каждого идентификатора.Это позволит вам вычислить время, прошедшее на каждом этапе.Затем вычислите среднее время, прошедшее для каждой стадии.

Вот пример того, как это сделать:

with input_data (id, dte, status) as (
SELECT 1, TO_DATE('1/1/18','MM/DD/YY'), 'requirement' FROM DUAL UNION ALL
SELECT 1, TO_DATE('1/8/18','MM/DD/YY'), 'analysis' FROM DUAL UNION ALL
SELECT 1, NULL, 'design' FROM DUAL UNION ALL
SELECT 1, TO_DATE('1/30/18','MM/DD/YY'), 'closed' FROM DUAL UNION ALL
SELECT 2, TO_DATE('2/1/18','MM/DD/YY'), 'requirement' FROM DUAL UNION ALL
SELECT 2, TO_DATE('2/18/18','MM/DD/YY'), 'closed' FROM DUAL UNION ALL
SELECT 3, TO_DATE('1/2/18','MM/DD/YY'), 'requirement' FROM DUAL UNION ALL
SELECT 3, TO_DATE('1/29/18','MM/DD/YY'), 'analysis' FROM DUAL UNION ALL
SELECT 3, NULL, 'accepted' FROM DUAL UNION ALL
SELECT 3, TO_DATE('2/5/18','MM/DD/YY'), 'closed' FROM DUAL ),
----- Solution begins here
data_with_elapsed_days as (
SELECT id.*, dte-nvl(lag(dte ignore nulls) over ( partition by id order by dte ), dte) elapsed
from input_data id)
SELECT status, avg(elapsed)
FROM data_with_elapsed_days d
group by status
order by decode(status,'requirement',1,'analysis',2,'design',3,'accepted',4,'closed',5,99);


+-------------+-------------------------------------------+
|   STATUS    |               AVG(ELAPSED)                |
+-------------+-------------------------------------------+
| requirement |                                         0 |
| analysis    |                                        17 |
| design      |                                           |
| accepted    |                                           |
| closed      | 15.33333333333333333333333333333333333333 |
+-------------+-------------------------------------------+

Как я уже сказал в своем комментарии, эта логика вычисляет прошедшие дни каквремя до данного статуса из предыдущего статуса.Поскольку «требование» не имеет предварительного статуса, эта логика всегда будет показывать ноль дней, проведенных в требованиях.Вероятно, было бы лучше рассчитать время от данного статуса до статуса next .Для «закрытых» не было бы следующего статуса.Вы можете просто оставить это поле пустым или использовать SYSDATE в качестве данных следующего статуса.Вот пример этого:

with input_data (id, dte, status) as (
SELECT 1, TO_DATE('1/1/18','MM/DD/YY'), 'requirement' FROM DUAL UNION ALL
SELECT 1, TO_DATE('1/8/18','MM/DD/YY'), 'analysis' FROM DUAL UNION ALL
SELECT 1, NULL, 'design' FROM DUAL UNION ALL
SELECT 1, TO_DATE('1/30/18','MM/DD/YY'), 'closed' FROM DUAL UNION ALL
SELECT 2, TO_DATE('2/1/18','MM/DD/YY'), 'requirement' FROM DUAL UNION ALL
SELECT 2, TO_DATE('2/18/18','MM/DD/YY'), 'closed' FROM DUAL UNION ALL
SELECT 3, TO_DATE('1/2/18','MM/DD/YY'), 'requirement' FROM DUAL UNION ALL
SELECT 3, TO_DATE('1/29/18','MM/DD/YY'), 'analysis' FROM DUAL UNION ALL
SELECT 3, NULL, 'accepted' FROM DUAL UNION ALL
SELECT 3, TO_DATE('2/5/18','MM/DD/YY'), 'closed' FROM DUAL ),
----- Solution begins here
data_with_elapsed_days as (
SELECT id.*, nvl(lead(dte ignore nulls) over ( partition by id order by dte ), trunc(sysdate))-dte elapsed
from input_data id)
SELECT status, avg(elapsed)
FROM data_with_elapsed_days d
group by status
order by decode(status,'requirement',1,'analysis',2,'design',3,'accepted',4,'closed',5,99);



+-------------+------------------------------------------+
|   STATUS    |               AVG(ELAPSED)               |
+-------------+------------------------------------------+
| requirement |                                       17 |
| analysis    |                                     14.5 |
| design      |                                          |
| accepted    |                                          |
| closed      | 361.666666666666666666666666666666666667 |
+-------------+------------------------------------------+
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...