2010-01-08 3 views
0

У меня есть таблица с ~ 17 миллионами строк в ней. Мне нужно удалить дубликаты строк в таблице. В обычных условиях это не будет проблемой, однако это не нормальное обстоятельство. Обычно «повторяющиеся строки» определяются как две или более строк, содержащих одинаковые значения для всех столбцов. В этом случае «повторяющиеся строки» определяются как две или более строк, которые имеют одинаковые значения, но также находятся в пределах 20 секунд друг от друга. Я написал скрипт, который все еще работает через 19.5 часов, это неприемлемо, но я не уверен, как это сделать. Вот сценарий:удаление дубликатов строк в таблице sql server 2005

begin 
create table ##dupes (ID int) 
declare curOriginals cursor for 
select ID, AssociatedEntityID, AssociatedEntityType, [Timestamp] from tblTable 

declare @ID int 
declare @AssocEntity int 
declare @AssocType int 
declare @Timestamp datetime 
declare @Count int 

open curOriginals 
fetch next from curOriginals into @ID, @AssocEntity, @AssocType, @Timestamp 
while @@FETCH_STATUS = 0 
begin 
select @Count = COUNT(*) from tblTable where AssociatedEntityID = @AssocEntity and AssociatedEntityType = @AssocType 
and [Timestamp] >= DATEADD(ss, -20, @Timestamp) 
and [Timestamp] <= DATEADD(ss, 20, @Timestamp) 
and ID <> @ID 
if (@Count > 0) 
begin 
insert into ##dupes (ID) 
(select ID from tblHBMLog where AssociatedEntityID = @AssocEntity and AssociatedEntityType = @AssocType 
and [Timestamp] >= DATEADD(ss, -20, @Timestamp) 
and [Timestamp] <= DATEADD(ss, 20, @Timestamp) 
and ID <> @ID) 
print @ID 
end 
delete from tblHBMLog where ID = @ID or ID in (select ID from ##dupes) 
fetch next from curOriginals into @ID, @AssocEntity, @AssocType, @Timestamp 
end 

close curOriginals 
deallocate curOriginals 

select * from ##dupes 
drop table ##dupes 
end 

Любая помощь была бы принята с благодарностью.

+0

Не хотите ли вы определить, что означает «de-duplicate»? –

+1

Что происходит, когда у вас есть три записи, a, b, c. a = 00 secs b = 19 secs c = 39 secs Все ли они считаются одними и теми же? (a находится в пределах 20 секунд от b, b находится в пределах 20 секунд от c) –

+2

Курсор против 19,5 миллионов строк - ouch – MartW

ответ

0

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

  1. Создать новую таблицу с аналогичной структурой
  2. Скопируйте все данные по выберите с отчетливой этой темпом таблицы
  3. Очистить оригинальные таблица (вам следует удалить некоторые ограничения до этого)
  4. Копировать данные обратно в исходную таблицу

Вместо 3 и 4 шагов вы можете переименовать исходную таблицу drop и переименовать временную папку.

0

Отложив дифференциал времени в сторону, первое, что я хотел бы сделать, это сбить этот список с гораздо меньшим подмножеством потенциальных дубликатов. Например, если у вас есть 17 миллионов строк, но только, скажем, 10 миллионов имеют каждое полевое совпадение, но время, то вы просто отрубили большую часть своей обработки.

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

При взгляде на курсор я вижу много относительно тяжелых вызовов функций, которые объяснят ваше замедление. Там также много активности данных, и я не был бы удивлен, если бы вы не были раздавлены узким местом ввода-вывода.

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

Предположим, что у вас есть 10 миллионов потенциалов, и каждый временной диапазон имеет около 20 записей или так, что нужно проработать с логикой даты, вы сокращаете гораздо меньшее количество запросов к базе данных и некоторый быстрый код - и из опыта, выбирая сравнения datetime и т. д. вне SQL, как правило, намного быстрее.

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

Надеюсь, что это поможет!

-Bob

1

Быстрый твик, который должен получить некоторую скорость будет заменить раздел неприятный COUNT с некоторыми СУЩЕСТВУЕТ материал:

IF EXISTS(SELECT 1 FROM tblTable WHERE AssociatedEntityID = @AssocEntity 
    AND AssociatedEntityType = @AssocType AND [Timestamp] >= DATEADD(ss, -20, @Timestamp) 
    AND [Timestamp] <= DATEADD(ss, 20, @Timestamp) 
    AND ID <> @ID) //if there are any matching rows... 
BEGIN 
    DELETE FROM tblHBMLog 
    OUTPUT deleted.ID INTO ##dupes 
    WHERE AssociatedEntityID = @AssocEntity AND AssociatedEntityType = @AssocType 
     AND [Timestamp] >= DATEADD(ss, -20, @Timestamp) 
     AND [Timestamp] <= DATEADD(ss, 20, @Timestamp) //I think this is supposed to be within the block, not outside it 
END 

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

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

0

В ответ на вопрос Павла:

Что происходит, когда у вас есть три записи, а, Ь, с. a = 00 секунд b = 19 секунд c = 39 секунд> Все ли они считаются одинаковыми? (А в течение 20 секунд Ь, Ь в пределах 20> секунд С)

Если другие сравнения равны (AssociatedEntityid и AssociatedEntityType), то да, они считаются то же самое, в противном случае нет.


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

Я работал с некоторыми ответами, которые вы мне дали, и есть одна проблема: вы используете только один ключевой столбец (AssociatedEntityid), когда есть два (AssociatedEntityID и AssociatedEntityType). Ваши предложения отлично подойдут для одного ключевого столбца.

То, что я сделал до сих пор является:

Шаг 1: Определите, какие AssociatedEntityID и AssociatedEntityType пары имеют дубликаты и вставить их в временную таблицу:

create table ##stage1 (ID int, AssociatedEntityID  int, AssociatedEntityType int, [Timestamp] datetime) 

insert into ##stage1 (AssociatedEntityID, AssociatedEntityType) 
    (select AssociatedEntityID, AssociatedEntityType from tblHBMLog group by AssociatedEntityID, AssociatedEntityType having COUNT(*) > 1) 

Шаг 2: Получить идентификатор ранние происходящие строки с заданным AssociatedEntityID и AssociatedEntityType парой:

declare curStage1 cursor for 
    select AssociatedEntityID, AssociatedEntityType from ##stage1 

open curStage1 
fetch next from curStage1 into @AssocEntity, @AssocType 
while @@FETCH_STATUS = 0 
begin 
    select top 1 @ID = ID, @Timestamp = [Timestamp] from tblHBMLog where AssociatedEntityID = @AssocEntity and AssociatedEntityType = @AssocType order by [Timestamp] asc 
    update ##stage1 set ID = @ID, [Timestamp] = @Timestamp where AssociatedEntityID = @AssocEntity and AssociatedEntityType = @AssocType 
end 

и это где вещи замедлить снова. Теперь, предоставленный, результирующий набор был уменьшен с ~ 17 миллионов до чуть менее 400 000, но до сих пор требуется довольно много времени.

Я думаю, что еще один вопрос, который я должен задать: Если я продолжу писать это в SQL, это займет довольно много времени? Должен ли я писать это на C#? Или я просто глуп и не вижу леса для деревьев этого решения?


Ну, после большого топора ног и скрежещения зубов, я придумал решение. Это простое, быстрое и грязное приложение командной строки C#, но оно быстрее, чем sql-скрипт, и он выполняет эту работу.

Я благодарю всех вас за помощь, в конце концов, скрипт sql просто занимал слишком много времени для выполнения, а C# намного лучше подходит для циклирования.

+0

, вы должны ** обновить ** свой исходный вопрос - не отвечать на него с помощью дополнительной информации ... –

1

В принципе, я согласен с Бобом. Прежде всего, у вас слишком много вещей, сделанных в вашем коде, чтобы повторить 17 миллионов раз. 2nd, вы можете обрезать свой набор до абсолютных дубликатов. Третье, было бы лучше, если бы у вас было достаточно памяти (что вам нужно), и попытайтесь решить эту проблему на выбранном вами языке программирования.

В любом случае, для ответа hardcoded ответ, и поскольку ваш запрос все еще может работать, я попытаюсь дать рабочий сценарий, который, как я думаю (?), Делает то, что вы хотите.

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

Тогда см ниже скрипт, который выполняет следующие действия:

  1. сбрасывает все дубликаты в ## простофили, не обращая внимания на 20 секунд править
  2. сортирует их (по AssociatedEntityID, Timestamp) и начинается простейшая прямая петля, которую он может сделать.
  3. проверяет дубликат AssociatedEntityID и метку времени за интервал в 20 секунд. , если все верно, затем вставляет идентификатор в таблицу ## dupes_to_be_deleted.

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

Вот сценарий, он может быть вам полезен, хотя не было времени его протестировать

CREATE TABLE ##dupes 
      (
          ID     INT , 
          AssociatedEntityID INT , 
          [Timestamp]  DATETIME 
      ) 
CREATE TABLE ##dupes_to_be_deleted 
      (
          ID INT 
      ) 

-- collect all dupes, ignoring for now the rule of 20 secs 
INSERT 
INTO ##dupes 
SELECT ID     , 
     AssociatedEntityID , 
     [Timestamp] 
FROM tblTable 
WHERE AssociatedEntityID IN 
     (SELECT AssociatedEntityID 
     FROM  tblTable 
     GROUP BY AssociatedEntityID 
     HAVING COUNT(*) > 1 
     ) 

-- then sort and loop on all of them 
-- using a cursor 
DECLARE c CURSOR FOR 
SELECT ID     , 
     AssociatedEntityID , 
     [Timestamp] 
FROM  ##dupes 
ORDER BY AssociatedEntityID, 
     [Timestamp] 

-- declarations 
DECLARE @id      INT, 
     @AssociatedEntityID  INT, 
     @ts      DATETIME, 
     @old_AssociatedEntityID INT, 
     @old_ts     DATETIME 

-- initialisation 
SELECT @old_AssociatedEntityID = 0, 
     @old_ts     = '1900-01-01' 

-- start loop 
OPEN c 
FETCH NEXT 
FROM c 
INTO @id    , 
     @AssociatedEntityID, 
     @ts 
WHILE @@fetch_status = 0 
BEGIN 
     -- check for dupe AssociatedEntityID 
     IF @AssociatedEntityID = @old_AssociatedEntityID 
     BEGIN 
       -- check for time interval 
       IF @ts <= DATEADD(ss, 20, @old_ts) 
       BEGIN 
         -- yes! it is a duplicate 
         -- store it in ##dupes_to_be_deleted 
         INSERT 
         INTO ##dupes_to_be_deleted 
           (
             id 
           ) 
           VALUES 
           (
             @id 
           ) 
       END 
       ELSE 
       BEGIN 
         -- IS THIS OK?: 
         -- put last timestamp for comparison 
         -- with the next timestamp 
         -- only if the previous one is not going to be deleted. 
         -- this way we delete all duplicates 
         -- 20 secs away from the first of the set of duplicates 
         -- and the next one remaining will be a duplicate 
         -- but after the 20 secs interval. 
         -- and so on ... 
         SET @old_ts = @ts 
       END 
     END 

     -- prepare vars for next iteration 
     SELECT @old_AssociatedEntityID = @AssociatedEntityID 
     FETCH NEXT 
     FROM c 
     INTO @id    , 
       @AssociatedEntityID, 
       @ts 
END 
CLOSE c 
DEALLOCATE c 


-- now you have all the ids that are duplicates and in the 20 sec interval of the first duplicate in the ##dupes_to_be_deleted 
DELETE 
FROM  <wherever> -- replace <wherever> with tblHBMLog? 
WHERE id IN 
     (SELECT id 
     FROM ##dupes_to_be_deleted 
     ) 
DROP TABLE ##dupes_to_be_deleted 
DROP TABLE ##dupes 

Вы можете попробовать и оставить его на пару часов. Надеюсь, поможет.

Смежные вопросы