Подскажите, как правильно обновить все записи, кроме последней?

Alibek B.
Дата: 30.09.2015 15:28:19
Есть сущность и дополнительные атрибуты этой сущности, которые являются историческими:
create table items
(
  item_id number not null,
  name varchar2 not null,
  ...
);

create table attributes
(
  attr_id number not null,
  item_id number not null,
  attribute varchar2 not null.
  value varchar2,
  date_beg date not null,
  date_end date
);


Вообще атрибут должен быть уникальным (то есть в таблице атрибутов не должно быть значений с повторяющимися item_id+attribute); если добавляется уже существующий атрибут, то действующий атрибут должен закрыться (date_end=sysdate) и в таблицу атрибутов должна добавиться новая запись:
attr_iditem_idattributevaluedate_begdate_end
11a1v12015-01-012015-01-10
21a1v22015-01-10

Но в информационной системе есть небольшая недоработка и в некоторых случаях предыдущий атрибут не закрывается:
attr_iditem_idattributevaluedate_begdate_end
11a1v12015-01-01
21a1v22015-01-10


Мне нужно найти такие дубли и закрыть их, оставив самую свежую запись.
Дубли выбираю так:
select ...
from items i
join attributes a on (a.item_id = i.item_id and a.date_beg <= sysdate and nvl(a.date_end,date'9999-01-01') > sysdate)
join attributes d on (d.item_id = i.item_id and d.date_beg <= sysdate and nvl(d.date_end,date'9999-01-01') > sysdate and d.attr_id != a.attr_id and d.attribute = a.attribute)


Как лучше будет закрыть все старые записи?
Добавить к вышеуказанному запросу группировку по item_id, получить max(date_beg), обернуть в подзапрос и сделать update attributes set date_beg = sysdate where attr_id not in (...)?
Или лучше прямо в этом запроса аналитические функции задействовать (partition by)?

________________________
Мы смотрим с оптимизмом...
...в оптический прицел.
stax..
Дата: 30.09.2015 15:41:31
Alibek B.,
merge

но я б не морочил голову и делал через pl/sql

и в свете "небольшая недоработка "
проверте нет ли пересечений и в исторических данных

ps
сдедущий date_end часто делают меньше предыдущего date_beg

....
stax
stax..
Дата: 30.09.2015 15:43:37
stax..
Alibek B.,
следущий date_end часто делают меньше предыдущего date_beg

....
stax


Предыдущий date_end часто делают меньше следующего date_beg
JDS
Дата: 30.09.2015 15:45:29
Если нет записей с равными date_start, можно попробовать примерно так, но внимательно проверить прежде, а лучше забэкапиться ):
UPDATE attributes t
   SET date_end = (SELECT MAX(date_start) - 1
                     FROM attributes 
                    WHERE item_id = t.item_id
                      AND date_end IS NULL)
 WHERE date_end IS NULL
   AND EXISTS (SELECT 1
                 FROM attributes
                WHERE item_id = t.item_id
                  AND date_start > t.date_start)
stax..
Дата: 30.09.2015 16:01:31
JDS,
ему надо поменять не на мах, а на предыдущую
с null может быть не одна запись

.....
stax
Alibek B.
Дата: 30.09.2015 16:04:36
stax..
merge

В каком смысле?
merge into attributes
using (select item_id, max(date_beg) as max_date ...) rs on (attributes.item_id = rs.item_id and attributes.date_beg < rs.max_date)
when matched then update set date_end = ...
Или имеется ввиду другое?

stax..
проверте нет ли пересечений и в исторических данных

Есть, 500 штук таких записей набралось.
Потому и думаю запускать по расписанию скрипт, который старые атрибуты будет закрывать.

JDS
Если нет записей с равными date_start, можно попробовать примерно так, но внимательно проверить прежде, а лучше забэкапиться ):

Таких записей в текущих данных нет.
Но теоретически такое возможно.
Я пока сам не решил, как именно буду такие ситуации решать.
Склоняюсь к использованию аналитических функций
max(a.attr_id) keep (dense_rank first order by a.date_beg) over (partition by r.item_id) as first_id
и update для attr_id != first_id.
123йй
Дата: 30.09.2015 16:07:17
Alibek B.,

lead ?
JDS
Дата: 30.09.2015 16:07:33
stax..
ему надо поменять не на мах, а на предыдущую
с null может быть не одна запись

Логично, тогда что-то вроде такого изврата ):
UPDATE attributes t
   SET date_end = (SELECT MAX(date_start)  KEEP(DENSE RANK LAST ORDER BY date_start DESC) - 1 -- хотя и MAX по идее, должно достать минимальную ближайшую дату )))
                     FROM attributes 
                    WHERE item_id = t.item_id
                      AND date_end IS NULL
                      AND date_start > t.date_start)
 WHERE date_end IS NULL
   AND EXISTS (SELECT 1
                 FROM attributes
                WHERE item_id = t.item_id
                  AND date_start > t.date_start)

Но проверять лучше на тестовых данных конечно )
JDS
Дата: 30.09.2015 16:16:06
Да desc лишнее конечно, ТС сам уже все написал )
UPDATE attributes t
   SET date_end = (SELECT MAX(date_start)  KEEP(DENSE_RANK FIRST ORDER BY date_start) - 1
                     FROM attributes 
                    WHERE item_id = t.item_id
                      AND date_end IS NULL
                      AND date_start > t.date_start)
 WHERE date_end IS NULL
   AND EXISTS (SELECT 1
                 FROM attributes
                WHERE item_id = t.item_id
                  AND date_start > t.date_start)
Alibek B.
Дата: 30.09.2015 16:41:12
123йй
lead ?

Спасибо, видимо да.
Оно гораздо короче, чем мой вариант.