SQL-запрос для устранения аналогичных записей - PullRequest
2 голосов
/ 19 августа 2011

Я работаю над проблемой в SQL Server 2008

У меня есть таблица с шестью столбцами:

PK INT
dOne SmallINT
dTwo SmallINT
dThree SmallINT
dFour SmallINT
dFiveSmallINT
dSix SmallINT

Таблица содержит около миллиона записей.Вероятно, стоит отметить это значение в столбце n + 1> значение в столбце n, то есть 97, 98, 99, 120, 135. Я пытаюсь удалить все строки, которые имеют 5 общих цифр (игнорируя PK), то есть:

76, 89, 99, 102, 155, 122
11, 89, 99, 102, 155, 122
89, 99, 102, 155, 122, 130

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

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

Я довольно плохо знаком с SQL, но это то, что я придумал (я придумал несколько решений, но ни одно из них не было адекватным ... это последняя попытка):

(я не буду включать весь код, но я объясню метод, я могу вставить больше, если это поможет)

Сохранить цифры записи n в переменных.ВЫБЕРИТЕ все записи, у которых есть одна общая цифра с записью ИЗ большой таблицы.

Вставьте все выбранные цифры в #oneMatch и включите [MatchOne] с совпадающей цифрой.

Выберите все записи, которые имеют одну общую цифру с записью n ИЗ таблицы временных значений, ГДЕ 'цифра вобщие '! = [соответствие].ВСТАВЬТЕ все выбранные цифры в #twoMatch и включите [MatchOne] И [MatchTwo] ...

Повторяйте до вставки в #fiveMatch.Удалите #fiveMatch из largeTable и перейдите к записи n + 1

У меня проблема с реализацией этого решения.Как я могу назначить соответствующую переменную в зависимости от предложения WHERE?

-- SELECT all records with ONE matching field:
INSERT INTO #oneMatch (ID_pk, dOne, dTwo, dThree, dFour, dFive, dSix, mOne)
SELECT ID_pk, dOne, dTwo, dThree, dFour, dFive, dSix
FROM dbo.BaseCombinationsExtended
WHERE  ( [dOne] IN (@dOne, @dTwo, @dThree, @dFour, @dFive, @dSix) **mOne = dOne?
      OR [dTwo] IN (@dOne, @dTwo, @dThree, @dFour, @dFive, @dSix) **mOne = dTwo?
      OR [dTwo] IN (@dOne, @dTwo, @dThree, @dFour, @dFive, @dSix) **mOne = dThree?
...
      OR [dSix] IN (@dOne, @dTwo, @dThree, @dFour, @dFive, @dSix) **mOne = dSix?
    )

Я могу «подделать» вышеуказанное, используя шесть запросов, но это слишком неэффективно ...

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

Ответы [ 3 ]

2 голосов
/ 20 августа 2011

Если я что-то упустил, это должно дать правильный результат.

declare @T table 
(
  PK INT identity primary key,
  dOne SmallINT,
  dTwo SmallINT,
  dThree SmallINT,
  dFour SmallINT,
  dFive SmallINT,
  dSix SmallINT
)

insert into @T values
(76, 89, 99, 102, 155, 122),
(11, 89, 99, 102, 155, 122),
(89, 99, 102, 155, 122, 130)

;with q1(PK, d1, d2, d3, d4, d5) as
(
  select PK, dTwo, dThree, dFour, dFive, dSix
  from @T
  union all
  select PK, dOne, dThree, dFour, dFive, dSix
  from @T
  union all
  select PK, dOne, dTwo, dFour, dFive, dSix
  from @T
  union all
  select PK, dOne, dTwo, dThree, dFive, dSix
  from @T
  union all
  select PK, dOne, dTwo, dThree, dFour, dSix
  from @T
  union all
  select PK, dOne, dTwo, dThree, dFour, dFive
  from @T
),
q2 as
(
  select PK,
         row_number() over(partition by d1, d2, d3, d4, d5 order by PK) as rn
  from q1
),
q3 as
(
  select PK
  from q2
  where rn = 1
  group by PK
  having count(*) = 6   
)
select T.*
from @T as T
  inner join q3 as Q
    on T.PK = Q.PK  
0 голосов
/ 19 августа 2011

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

CREATE TABLE dbo.Test_Sets_Normalized (my_id INT NOT NULL, c SMALLINT NOT NULL)
GO

INSERT INTO dbo.Test_Sets_Normalized (my_id, c)
SELECT my_id, c1 FROM dbo.Test_Sets UNION ALL
SELECT my_id, c2 FROM dbo.Test_Sets UNION ALL
SELECT my_id, c3 FROM dbo.Test_Sets UNION ALL
SELECT my_id, c4 FROM dbo.Test_Sets UNION ALL
SELECT my_id, c5 FROM dbo.Test_Sets UNION ALL
SELECT my_id, c6 FROM dbo.Test_Sets
GO

SELECT DISTINCT
    T2.my_id
FROM
    (SELECT DISTINCT my_id FROM dbo.Test_Sets_Normalized) T1
INNER JOIN (SELECT DISTINCT my_id FROM dbo.Test_Sets_Normalized) T2 ON T2.my_id > T1.my_id
WHERE
    (
    SELECT
        COUNT(*)
    FROM
        dbo.Test_Sets_Normalized T3
    INNER JOIN dbo.Test_Sets_Normalized T4 ON
        T4.my_id = T2.my_id AND
        T4.c = T3.c
    WHERE
        T3.my_id = T1.my_id) >= 5

Это должно дать вам необходимые вам идентификаторы. Как только вы подтвердите, что он делает то, что вы хотите, вы можете JOIN вернуться к исходной таблице и удалить по идентификаторам.

Возможно, где-то возможно улучшение, которое не требует DISTINCT. Я еще немного подумаю.

0 голосов
/ 19 августа 2011

Изменить - следующий подход может быть лучше, чем N в квадрате производительности, в зависимости от оптимизатора. Если все 5 столбцов проиндексированы, то для каждой строки требуется только 6 запросов индекса, что по-прежнему равно N * logN. Хотя это выглядит немного глупо.

Вы можете сгенерировать код условия where на основе всех перестановок из 5 совпадений: таким образом, удаляемые записи будут иметь вид:

SELECT * FROM SillyTable ToDelete WHERE EXISTS
(
    SELECT PK From SillyTable Duplicate 
    WHERE (   (
            (Duplicate.dOne=ToDelete.dOne) 
            AND (Duplicate.dTwo=ToDelete.dTwo) 
            AND (Duplicate.dThree=ToDelete.dThree)
            AND (Duplicate.dFour=ToDelete.dFour)
            AND (Duplicate.dFive=ToDelete.dFive)
        ) OR (
            (Duplicate.dOne=ToDelete.dTwo) 
            AND (Duplicate.dTwo=ToDelete.dThree) 
            AND (Duplicate.dThree=ToDelete.dFour)
            AND (Duplicate.dFour=ToDelete.dFive)
            AND (Duplicate.dFive=ToDelete.dSix)
        ) OR (
            (Duplicate.dTwo=ToDelete.dOne) 
            AND (Duplicate.dThree=ToDelete.dTwo) 
            AND (Duplicate.dFour=ToDelete.dThree)
            AND (Duplicate.dFive=ToDelete.dFour)
            AND (Duplicate.dSix=ToDelete.dFive)
        ) OR (
            (Duplicate.dTwo=ToDelete.dTwo) 
            AND (Duplicate.dThree=ToDelete.dThree) 
            AND (Duplicate.dFour=ToDelete.dFour)
            AND (Duplicate.dFive=ToDelete.dFive)
            AND (Duplicate.dSix=ToDelete.dSix)
        ) ...                       

Это распространяется на все 36 комбинаций (по 6 несоответствующих столбцов на каждой стороне объединения, поэтому 6 * 6 дает вам все возможные варианты). Я бы сгенерировал код, потому что это много печатает, и что если вы хотите 4 из 6 совпадений завтра, но вы могли бы написать код, я думаю.

...