поиск последовательных пар дат в SQL - PullRequest
3 голосов
/ 23 августа 2011

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

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

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

id     user_id effective_date important_value useless_value
1      1       1/3/2007       3               0
2      1       1/4/2007       3               1
3      1       1/6/2007       NULL            1
4      1       2/1/2007       3               0
5      2       1/5/2007       12              1
6      3       1/1/1899       7               0

С этим набором образцов мы будем рассматривать две последовательные копии строк, если user_id и important_value одинаковы.Из этого набора образцов мы удалили только строку с id = 2, сохранив информацию за 1-3-2007, показав, что important_value изменился за 1-6-2007, а затем снова показав соответствующее изменение 2-1-2007.

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

WHILE (@@fetch_status <> -1)
BEGIN
  SELECT * FROM History INTO #history WHERE user_id = @UserId

  --return entries to delete
  SELECT h2.id
  INTO #delete_history_ids
  FROM #history h1
  JOIN #history h2 ON
    h1.effective_date < h2.effective_date
    AND dbo.UDF_SameOrNull(h1.important_value, h2.important_value)=1
  WHERE NOT EXISTS (SELECT 1 FROM #history hx WHERE hx.effective_date > h1.effective_date and hx.effective_date < h2.effective_date)

  DELETE h1
  FROM History h1
  JOIN #delete_history_ids dh ON
    h1.id = dh.id 

  FETCH NEXT FROM UserCursor INTO @UserId
END 

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

К сожалению, я должен использовать SQL Server 2000 для этой задачи, и я почти уверен, что он не поддерживает ROW_NUMBER () для более элегантного способа поиска последовательных записей.

Спасибо за чтение.Я прошу прощения за любые ненужные предысторию или ошибки в псевдокоде.

Ответы [ 2 ]

1 голос
/ 23 августа 2011

ОК, думаю, я понял это, отличный вопрос!

Сначала я предположил, что столбец effective_date не будет дублироваться для user_id. Я думаю, что это может быть изменено, чтобы работать, если это не так - поэтому дайте мне знать, если мы должны учитывать это.

Процесс в основном принимает таблицу значений и самосоединения равными user_id и important_value и предшествующим effective_date. Затем мы делаем еще 1 самосоединение для user_id, которое эффективно проверяет, являются ли 2 вышеупомянутые объединенные записи последовательными, проверяя, что между этими двумя записями нет записи effective_date.

Пока это просто оператор выбора - он должен выбирать все записи, которые должны быть удалены. Поэтому, если вы убедитесь, что он возвращает правильные данные, просто измените select * на delete tcheck.

Дайте мне знать, если у вас есть вопросы.

select 
    * 
from 
    History tcheck
    inner join History tprev
        on  tprev.[user_id] = tcheck.[user_id]
            and tprev.important_value = tcheck.important_value
            and tprev.effective_date < tcheck.effective_date
    left join History checkbtwn
        on  tcheck.[user_id] = checkbtwn.[user_id]
            and checkbtwn.effective_date < tcheck.effective_date
            and checkbtwn.effective_date > tprev.effective_date
where
    checkbtwn.[user_id] is null
0 голосов
/ 24 августа 2011

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

Меня вдохновили другие результаты, в которых говорится, что используется ROW_NUMBER(), и я использовал очень похожий подход, но со столбцом идентификации.

--create table with identity column
CREATE TABLE #history (
  id int, 
  user_id int, 
  effective_date datetime, 
  important_value int, 
  useless_value int,
  idx int IDENTITY(1,1)
)

--insert rows ordered by effective_date and now indexed in order
INSERT INTO #history
SELECT * FROM History 
WHERE user_id = @user_id
ORDER BY effective_date

--get pairs where consecutive values match
SELECT * 
FROM #history h1
JOIN #history h2 ON
  h1.idx+1 = h2.idx
WHERE h1.important_value = h2.important_value

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

...