Сравните месячные данные при сохранении ежедневной детализации - PullRequest
2 голосов
/ 04 февраля 2020

У меня есть данные ниже, которые содержат месячные цели для набора идентификаторов. Цели указаны для каждого идентификатора, для каждого месяца в 2020 году. Таблица с именем targets. Столбец month указывает месяц года.

+-------+-------+----+--------+
| month | name  | id | target |
+-------+-------+----+--------+
| 1     | Comp1 | 1  | 6000   |
+-------+-------+----+--------+
| 2     | Comp1 | 1  | 6000   |
+-------+-------+----+--------+
| 3     | Comp1 | 1  | 6000   |
+-------+-------+----+--------+
| 1     | Comp2 | 2  | 6000   |
+-------+-------+----+--------+
| 2     | Comp2 | 2  | 6000   |
+-------+-------+----+--------+
| 3     | Comp2 | 2  | 6000   |
+-------+-------+----+--------+
| 1     | Comp3 | 3  | 6000   |
+-------+-------+----+--------+
| 2     | Comp3 | 3  | 6000   |
+-------+-------+----+--------+
| 3     | Comp3 | 3  | 6000   |
+-------+-------+----+--------+
| 1     | Comp4 | 4  | 6000   |
+-------+-------+----+--------+
| 2     | Comp4 | 4  | 6000   |
+-------+-------+----+--------+
| 3     | Comp4 | 4  | 6000   |
+-------+-------+----+--------+

Затем у меня есть вторая таблица, которая содержит ежедневные данные для набора идентификаторов и обновляется ежедневно. В моем фактическом наборе данных у меня есть данные с 2019-01-01 до текущего дня.

+------------+-------+----+--------+--------+
| yyyy_mm_dd | name  | id | actual | region |
+------------+-------+----+--------+--------+
| 2019-01-01 | Comp1 | 1  | 1000   | LATAM  |
+------------+-------+----+--------+--------+
| 2019-01-01 | Comp1 | 1  |   0    |  EU    |
+-------------------------------------------+
| 2019-01-02 | Comp1 | 1  | 2000   |  EU    |
+------------+-------+----+--------+--------+
| 2019-01-03 | Comp1 | 1  | 4000   |  EU    |
+------------+-------+----+--------+--------+
| 2019-01-01 | Comp2 | 2  | 1000   |  EU    |
+------------+-------+----+--------+--------+
| 2019-01-02 | Comp2 | 2  | 2000   |  EU    |
+------------+-------+----+--------+--------+
| 2019-01-03 | Comp2 | 2  | 3000   |  EU    |
+------------+-------+----+--------+--------+
| 2019-01-01 | Comp3 | 3  | 1000   |  EU    |
+------------+-------+----+--------+--------+
| 2019-01-02 | Comp3 | 3  | 2000   |  EU    |
+------------+-------+----+--------+--------+
| 2019-01-03 | Comp3 | 3  | 8000   |  EU    |
+------------+-------+----+--------+--------+
| 2019-01-01 | Comp4 | 4  | 1000   |  EU    |
+------------+-------+----+--------+--------+
| 2019-01-02 | Comp4 | 4  | 2000   |  EU    |
+------------+-------+----+--------+--------+
| 2019-02-03 | Comp4 | 4  | 3000   |  EU    |
+------------+-------+----+--------+--------+

Основываясь на вышеупомянутых двух таблицах, я хочу создать третью таблицу с некоторыми дополнительными логами c. В конечном итоге я хочу представить новый столбец под названием payment. Этот столбец всегда должен быть 0, если компания не достигла своей месячной цели. Если месячная цель достигнута / пройдена, выплата должна быть sum actual for that month - monthly target for that month * 1%.

Вот как могут выглядеть выходные данные:

+------------+-------+----+--------+--------+
| yyyy_mm_dd | name  | id | actual | payout |
+------------+-------+----+--------+--------+
| 2020-01-01 | Comp1 | 1  | 1000   | 0      |
+------------+-------+----+--------+--------+
| 2020-01-02 | Comp1 | 1  | 2000   | 0      |
+------------+-------+----+--------+--------+
| 2020-01-03 | Comp1 | 1  | 4000   | 10     |
+------------+-------+----+--------+--------+
| 2020-01-01 | Comp2 | 2  | 1000   | 0      |
+------------+-------+----+--------+--------+
| 2020-01-02 | Comp2 | 2  | 2000   | 0      |
+------------+-------+----+--------+--------+
| 2020-01-03 | Comp2 | 2  | 3000   | 0      |
+------------+-------+----+--------+--------+
| 2020-01-01 | Comp3 | 3  | 1000   | 0      |
+------------+-------+----+--------+--------+
| 2020-01-02 | Comp3 | 3  | 2000   | 0      |
+------------+-------+----+--------+--------+
| 2020-01-03 | Comp3 | 3  | 8000   | 50     |
+------------+-------+----+--------+--------+
| 2020-01-01 | Comp4 | 4  | 1000   | 0      |
+------------+-------+----+--------+--------+
| 2020-01-02 | Comp4 | 4  | 2000   | 0      |
+------------+-------+----+--------+--------+
| 2020-02-03 | Comp4 | 4  | 3000   | 0      |
+------------+-------+----+--------+--------+

Все имена / идентификаторы в указанном наборе данных имейте target в месяц 6000. Таким образом, payout должно быть только тогда, когда имя / идентификатор проходит эту цель в течение месяца. Comp1 и Comp3 оба прошли месячный целевой показатель в третий день января, поэтому они получают выплату с этого дня и до конца месяца. Затем он сбрасывается в феврале, так как это новый месяц с новой целью, и мы будем получать новые ежедневные данные в течение месяца.


То, что я пробовал:

SELECT
    agg.yyyy_mm_dd,
    agg.name,
    agg.id,
    CASE WHEN agg.actual >= targets.target THEN ((agg.actual-targets.target)/100) * 1 ELSE 0 END AS payout
FROM(
    SELECT
        sum(x.actual) AS actual,
        x.yyyy_mm_dd,
        x.name,
        x.id
    FROM(
        SELECT
            yyyy_mm_dd,
            name,
            id,
            cast(actual as int) as actual
        FROM
            schema.daily_data
        WHERE
            yyyy_mm_dd >= '2020-01-01' AND (name = 'Comp1' OR name = 'Comp2')
    ) x
    GROUP BY
        2,3,4
) agg
INNER JOIN(
    SELECT
      id,
      month,
      target
    FROM
        schema.targets
) targets ON targets.id = agg.id
GROUP BY
    1,2,3,4

Тем не менее, вышеприведенное выводит несколько строк на name. Это результат того, что на ежедневном столе одна и та же компания встречается несколько раз в день (ожидается). Я думал, что моя группа справилась бы с этим. Кроме того, я не думаю, что это самое простое решение в целом, и я, вероятно, думаю, что оно может / может быть сделано более эффективно.

Ответы [ 4 ]

1 голос
/ 04 февраля 2020

Похоже, вы хотите сравнить совокупную сумму actua на компанию и месяц с target. Вы можете сделать это с помощью функции соединения и окна:

select 
    d.yyyy_mm_dd, 
    case when sum(d.actual) over(partition by d.name, t.month order by d.yyyy_mm_dd) > t.target
        then (sum(d.actual) over(partition by d.name, t.month order by d.yyyy_mm_dd) - t.target) / 100.0
        else 0
    end payout
from schema.targets t
inner join schema.daily_data d
    on  month(d.yyyy_mm_dd) = t.month
    and d.name = t.name
where
    d.yyyy_mm_dd >= '2020-01-01' 
    and d.name in ('Comp1', 'Comp2')
0 голосов
/ 04 февраля 2020

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

SELECT
    x.yyyy_mm_dd,
    x.id,
    x.name,
    x.actual,
    x.target,
    x.actual_to_date,
    CASE WHEN x.actual_to_date > x.target THEN ((x.actual_to_date - x.target) /100) * 1 ELSE 0 END AS payout
FROM(
    SELECT
        daily.yyyy_mm_dd,
        daily.id,
        daily.name,
        daily.actual,
        t.target,
        SUM(daily.actual) OVER (PARTITION BY MONTH(daily.yyyy_mm_dd), daily.id ORDER BY daily.yyyy_mm_dd RANGE UNBOUNDED PRECEDING) AS actual_to_date
    FROM(
        SELECT
            yyyy_mm_dd,
            id,
            name,
            sum(cast(actual as int)) as actual
        FROM
            daily_data_table
        WHERE
            yyyy_mm_dd >= '2020-01-01'
        GROUP BY
            1,2,3
    ) daily
    INNER JOIN
        monthly_target_table t
        ON t.id = daily.id AND t.month = month(daily.yyyy_mm_dd)
    WHERE
        daily.name = 'Comp1'
) x
0 голосов
/ 04 февраля 2020

Другой вариант - использовать оконную функцию SUM для создания промежуточного итога, а затем использовать ее в операторе CASE для получения значений столбцов.

SELECT d.yyyy_mm_dd
    ,d.name
    ,d.id
    ,d.actual
    ,CASE 
        WHEN 
      SUM(d.actual) 
        OVER (PARTITION BY d.id ORDER BY d.yyyy_mm_dd ROWS UNBOUNDED PRECEDING) <= t.target
            THEN 0
        ELSE 
      (
        SUM(d.actual) 
          OVER (PARTITION BY d.id ORDER BY d.yyyy_mm_dd ROWS UNBOUNDED PRECEDING) - t.target
            ) * 0.01
        END AS payout
FROM dailies AS d
JOIN targets AS t 
    ON d.month = MONTH(d.yyyy_mm_dd)
    AND d.id = d.id;

Я не 100 % уверен в синтаксисе Hive, но это довольно близко. В частности, ROWS UNBOUNDED PRECEDING может быть недостаточно. Вам может понадобиться пункт FOLLOWING, чтобы получить правильные итоги.

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

Ваш запрос на выполнение (частичной) суммы фактов легко решается оконными функциями. К сожалению, я не использую Hive, поэтому вот мое Postgres рабочее решение

with t (month, name, id, target) as (values
  (1 , 'Comp1', 1 , 6000 ),
  (2 , 'Comp1', 1 , 6000 ),
  (3 , 'Comp1', 1 , 6000 ),
  (1 , 'Comp2', 2 , 6000 ),
  (2 , 'Comp2', 2 , 6000 ),
  (3 , 'Comp2', 2 , 6000 ),
  (1 , 'Comp3', 3 , 6000 ),
  (2 , 'Comp3', 3 , 6000 ),
  (3 , 'Comp3', 3 , 6000 ),
  (1 , 'Comp4', 4 , 6000 ),
  (2 , 'Comp4', 4 , 6000 ),
  (3 , 'Comp4', 4 , 6000 )
), d (yyyy_mm_dd, name, id, actual, region) as (values
 ( date '2019-01-01' , 'Comp1' , 1  , 1000 , 'LATAM' ),
 ( date '2019-01-01' , 'Comp1' , 1  ,    0 , 'EU' ),
 ( date '2019-01-02' , 'Comp1' , 1  , 2000 , 'EU' ),
 ( date '2019-01-03' , 'Comp1' , 1  , 4000 , 'EU' ),
 ( date '2019-01-01' , 'Comp2' , 2  , 1000 , 'EU' ),
 ( date '2019-01-02' , 'Comp2' , 2  , 2000 , 'EU' ),
 ( date '2019-01-03' , 'Comp2' , 2  , 3000 , 'EU' ),
 ( date '2019-01-01' , 'Comp3' , 3  , 1000 , 'EU' ),
 ( date '2019-01-02' , 'Comp3' , 3  , 2000 , 'EU' ),
 ( date '2019-01-03' , 'Comp3' , 3  , 8000 , 'EU' ),
 ( date '2019-01-01' , 'Comp4' , 4  , 1000 , 'EU' ),
 ( date '2019-01-02' , 'Comp4' , 4  , 2000 , 'EU' ),
 ( date '2019-02-03' , 'Comp4' , 4  , 3000 , 'EU' )
)
select dr.yyyy_mm_dd, dr.name, dr.id, dr.actual,
       case when dr.running_sum < t.target then 0 else (dr.running_sum - t.target) / 100 end as payment
from t
join (
  select dg.*, sum(actual) over (partition by name order by yyyy_mm_dd) as running_sum
  from (
     select yyyy_mm_dd, name, id, sum(actual) as actual
     from d
     group by yyyy_mm_dd, name, id
  ) dg
) dr on dr.name = t.name
     and month(dr.yyyy_mm_dd) = t.month -- edited to hive equivalent of postgres' extract(month from dr.yyyy_mm_dd) = t.month

Извлечение месяца из даты может быть сделано иначе, но я надеюсь, что вы поняли.

...