Oracle: обновить значения в таблице с помощью агрегированных значений из той же таблицы - PullRequest
0 голосов
/ 09 мая 2019

Я ищу, возможно, лучший подход к этому.

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

create global temporary table temp_foo (
    DT                  timestamp(6), --only the date part will be used in this example but for later things I will need the time
    Something           varchar2(100), 
    Customer            varchar2(100), 
    MinDate             timestamp(6), 
    MaxDate             timestamp(6),
    Filecount           int, 
    Errorcount          int,
    AvgFilecount        int,
    constraint PK_foo primary key (DT, Customer)
) on commit preserve rows;

Затем я сначала вставляю некоторые фиксированные значения для всего, кроме AvgFilecount.AvgFilecount должен содержать среднее значение для Filecount для 3 предыдущих записей (по дате DT).Неважно, что результат будет преобразован в int, мне не нужны десятичные разряды

DT           | Customer | Filecount | AvgFilecount
2019-04-30   | x        | 10        | avg(2+3+9)
2019-04-29   | x        | 2         | based on values before this
2019-04-28   | x        | 3         | based on values before this
2019-04-27   | x        | 9         | based on values before this

Я думал об использовании обычного оператора UPDATE, поскольку это должно быть быстрее, чем циклически перебирать значения,Я должен отметить, что в поле DT нет пробелов, но, очевидно, есть первый, где я не найду никаких предыдущих записей.Если бы я прошел цикл, я мог бы легко вычислить AvgFilecount с (the record before previous record/2 + previous record)/3, что я не могу с UPDATE, поскольку я не могу гарантировать порядок их выполнения.Так что я в порядке, просто взяв последние 3 записи (по DT) и посчитав их оттуда.

То, что я думаю, будет легким обновлением, вызывает у меня головную боль.Я в основном делаю SQL Server, где я просто присоединяюсь к 3 другим записям, но в Oracle это выглядит немного иначе.Я нашел https://stackoverflow.com/a/2446834/4040068 и хотел использовать второй подход в ответе.

update 
(select curr.DT, curr.temp_foo, curr.Filecount, curr.AvgFilecount as OLD, (coalesce(Minus1.Filecount, 0) + coalesce(Minus2.Filecount, 0) + coalesce(Minus3.Filecount, 0)) / 3 as NEW
 from temp_foo curr
 left join temp_foo Minus1 ON Minus1.Customer = curr.Customer and trunc(Minus1.DT) = trunc(curr.DT-1)
 left join temp_foo Minus2 ON Minus2.Customer = curr.Customer and trunc(Minus2.DT) = trunc(curr.DT-2)
 left join temp_foo Minus3 ON Minus3.Customer = curr.Customer and trunc(Minus3.DT) = curr.DT-3
 order by 1, 2
)
set OLD = NEW;

, который дает мне

ORA-01779: невозможно изменить столбецкоторая сопоставляется с таблицей без сохранения ключа 01779. 00000 - «не может изменить столбец, который сопоставляется с таблицей без сохранения ключа» * Причина: была предпринята попытка вставить или обновить столбцы представления объединения, которые сопоставляются с таблицей без сохранения ключа.сохранившийся под ключ стол.* Действие: измените базовые базовые таблицы напрямую.

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

Другие вещи, которые я думал о попытке:

  • с использованием вложенного подвыбора (вложенного, потому что Oracle не знает top (n) и мне нужно отсортировать подвыбор), чтобы выбрать предыдущий3 записи, заказанные DT, а затем он выбирает внешне с помощью rownum <= 3, и тогда я могу просто использовать <code>AVG().Тем не менее, мне сказали, что отбор может быть довольно медленным, а соединения лучше в плане производительности Oracle.Не знаю, если это действительно так, не проводил никаких тестов

Редактировать: Моя вставка сейчас выглядит следующим образом.Я уже собираю количество файлов за день, так как может быть несколько записей на DT на Customer на Something.

insert into temp_foo (DT, Something, Customer, Filecount)
select dates.DT, tbl1.Something, tbl1.Customer, coalesce(sum(tbl3.Filecount),0)
from table(Function_Returning_Daterange(NULL, NULL)) dates
cross join
    (SELECT Something, 
        Code, 
        Value
    FROM Table2 tbl2
    WHERE (Something = 'Value')) tbl1
left outer join Table3 tbl3
    on      tbl3.Customer = tbl1.Customer
    and     trunc(tbl3.MinDate) = trunc(dates.DT)
group by dates.DT, tbl1.Something, tbl1.Customer;

1 Ответ

2 голосов
/ 09 мая 2019

Вы можете использовать аналитическое среднее с предложением окна:

select dt, customer, filecount,
  avg(filecount) over (partition by customer order by dt
    rows between 3 preceding and 1 preceding) as avgfilecount
from tmp_foo
order by dt desc;

DT         CUSTOMER  FILECOUNT AVGFILECOUNT
---------- -------- ---------- ------------
2019-04-30 x                10   4.66666667
2019-04-29 x                 2            6
2019-04-28 x                 3            9
2019-04-27 x                 9             

, а затем выполнить обновление с помощью оператора слияния:

merge into tmp_foo t
using (
  select dt, customer,
    avg(filecount) over (partition by customer order by dt
      rows between 3 preceding and 1 preceding) as avgfilecount
  from tmp_foo
) s
on (s.dt = t.dt and s.customer = t.customer)
when matched then update set t.avgfilecount = s.avgfilecount;

4 rows merged.

select dt, customer, filecount, avgfilecount
from tmp_foo
order by dt desc;

DT         CUSTOMER  FILECOUNT AVGFILECOUNT
---------- -------- ---------- ------------
2019-04-30 x                10   4.66666667
2019-04-29 x                 2            6
2019-04-28 x                 3            9
2019-04-27 x                 9             

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

Кроме того, если вы хотите, чтобы первые два значения даты вычислялись так, как если бы «отсутствующие» дополнительные дни до того, как они имели ноль, были равны нулю.считая, вы можете использовать sum и деление вместо avg:

select dt, customer, filecount,
  sum(filecount) over (partition by customer order by dt
    rows between 3 preceding and 1 preceding)/3 as avgfilecount
from tmp_foo
order by dt desc;

DT         CUSTOMER  FILECOUNT AVGFILECOUNT
---------- -------- ---------- ------------
2019-04-30 x                10   4.66666667
2019-04-29 x                 2            4
2019-04-28 x                 3            3
2019-04-27 x                 9             

Это зависит от того, что вы ожидаете, что последними вычисленными значениями будут.

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