дедупликация строк в таблице SQL Server 2005 - PullRequest
0 голосов
/ 08 января 2010

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

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

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

Ответы [ 5 ]

1 голос
/ 08 января 2010

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

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

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

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

  1. сбрасывает все дубликаты в ## dupes, игнорируя правило 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

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

1 голос
/ 08 января 2010

Быстрая настройка, которая должна набрать некоторую скорость, заключается в замене неприятного раздела COUNT на некоторые элементы EXISTS:

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 голосов
/ 08 января 2010

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

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

Если другие сравнения равны (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 голосов
/ 08 января 2010

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

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

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

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

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

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

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

-Bob

0 голосов
/ 08 января 2010

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

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

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

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