Выбор самой последней и конкретной версии в каждой группе записей для нескольких групп - PullRequest
21 голосов
/ 24 февраля 2012

Проблема:
У меня есть таблица, которая записывает строки данных в foo. Каждый раз, когда строка обновляется, новая строка вставляется вместе с номером редакции. Таблица выглядит так:

id  rev field
1   1   test1
2   1   fsdfs
3   1   jfds
1   2   test2

Обратите внимание, что в таблице последняя запись является более новой версией первой строки.

Кто-нибудь знает эффективный способ запроса последней версии строк и конкретной версии записей? Например, запрос для rev=2 будет возвращать 2, 3 и 4-ю строку (но не замененную 1-ую строку), тогда как запрос для rev=1 возвращает эти строки с rev <= 1, а в случае дублированных идентификаторов один с более высоким номером ревизии выбран (запись: 1, 2, 3). </p>

На самом деле я не уверен, возможно ли это даже в SQL Server ...

Я бы не хотел возвращать результат итеративным способом.

Ответы [ 7 ]

36 голосов
/ 24 февраля 2012

Чтобы получить только последние ревизии:

SELECT * from t t1
WHERE t1.rev = 
  (SELECT max(rev) FROM t t2 WHERE t2.id = t1.id)

Чтобы получить конкретную ревизию, в данном случае 1 (и если у элемента еще нет ревизии следующей наименьшей ревизии):

SELECT * from foo t1
WHERE t1.rev = 
  (SELECT max(rev) 
   FROM foo t2 
   WHERE t2.id = t1.id
   AND t2.rev <= 1)

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

6 голосов
/ 24 февраля 2012

Вот как бы я это сделал. ROW_NUMBER() требует SQL Server 2005 или более поздней версии

Пример данных:

DECLARE @foo TABLE (
    id int,
    rev int,
    field nvarchar(10)
)

INSERT @foo VALUES
    ( 1, 1, 'test1' ),
    ( 2, 1, 'fdsfs' ),
    ( 3, 1, 'jfds' ),
    ( 1, 2, 'test2' )

Запрос:

DECLARE @desiredRev int

SET @desiredRev = 2

SELECT * FROM (
SELECT 
    id,
    rev,
    field,
    ROW_NUMBER() OVER (PARTITION BY id ORDER BY rev DESC) rn
FROM @foo WHERE rev <= @desiredRev 
) numbered
WHERE rn = 1

Внутренний SELECT возвращает все соответствующие записи, и в каждой группе id (это PARTITION BY) вычисляет номер строки, если упорядочен по убыванию rev.

Внешний SELECT просто выбирает первого члена (то есть члена с наибольшим rev) из каждой группы id.

Вывод при @desiredRev = 2:

id          rev         field      rn
----------- ----------- ---------- --------------------
1           2           test2      1
2           1           fdsfs      1
3           1           jfds       1

Вывод при @desiredRev = 1:

id          rev         field      rn
----------- ----------- ---------- --------------------
1           1           test1      1
2           1           fdsfs      1
3           1           jfds       1
4 голосов
/ 28 мая 2018

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

Старение

  • Заменить столбец rev на столбец age
  • Создать представление текущих последних данных с фильтром: age = 0
  • Чтобы создать новую версию ваших данных ...
    • INSERT: новые строки с age = -1 - это был мой медленный длительный пакетный процесс.
    • ОБНОВЛЕНИЕ: UPDATE table-name SET age = age + 1 для всех строк в подмножестве. Это переключает представление на новые последние данные (строка = 0), а также устаревает старые данные в одной транзакции.
    • УДАЛИТЬ: строки, имеющие age > N в подмножестве - При желании удалить старые данные

Индексация

  • Создайте составной индекс с помощью age, а затем id, чтобы представление было красивым и быстрым, и его также можно использовать для поиска по id. Хотя этот ключ по сути уникален, он временно не уникален при старении строк (в течение UPDATE SET age=age+1), поэтому вам нужно сделать его неуникальным и в идеале кластеризованным индексом. Если вам нужно найти все версии данного id, вам может понадобиться дополнительный индекс на id.

Наконец ... Допустим, у вас плохой день и пакетная обработка прерывается. Вы можете быстро вернуться к предыдущей версии набора данных, выполнив:

  • UPDATE table-name SET age = age - 1 - Откатить версию
  • DELETE table-name WHERE age < 0 - Очистить плохие вещи

Примечание: я рекомендую назвать имя столбца RowAge вместо age, чтобы указать, что этот шаблон используется, так как более понятно, что это значение, связанное с базой данных, и оно дополняет соглашение об именовании RowVersion в SQL Server. Это также не будет конфликтовать с колонкой или представлением, которые должны возвращать возраст человека.

В отличие от других решений, этот шаблон работает для баз данных, отличных от SQL Server.

4 голосов
/ 24 февраля 2012

Если вам нужны все последние версии каждого поля, вы можете использовать

SELECT C.rev, C.fields FROM (
  SELECT MAX(A.rev) AS rev, A.id
  FROM yourtable A
  GROUP BY A.id) 
AS B
INNER JOIN yourtable C
ON B.id = C.id AND B.rev = C.rev

В случае вашего примера это вернет

 rev field
 1   fsdfs   
 1   jfds   
 2   test2
2 голосов
/ 24 февраля 2012
SELECT
  MaxRevs.id,
  revision.field
FROM
  (SELECT
     id,
     MAX(rev) AS MaxRev
   FROM revision
   GROUP BY id
  ) MaxRevs
  INNER JOIN revision 
    ON MaxRevs.id = revision.id AND MaxRevs.MaxRev = revision.rev
1 голос
/ 24 февраля 2012
SELECT foo.* from foo 
left join foo as later 
on foo.id=later.id and later.rev>foo.rev 
where later.id is null;
0 голосов
/ 18 мая 2018

Как насчет этого?

select id, max(rev), field from foo group by id

Для запроса конкретной ревизии, например, ревизии 1,

select id, max(rev), field from foo where rev <= 1 group by id
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...