Эффективная проверка возможных дублирующих объектов - PullRequest
1 голос
/ 05 мая 2009

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

Существует 7 критериев, по которым мы должны проверять наличие дубликатов, и если хотя бы 3 совпадения совпадают, мы должны пометить это пользователю. Все критерии будут совпадать по идентификатору, поэтому нет необходимости в нечетком сопоставлении строк, но моя проблема заключается в том, что существует множество возможных способов (99 способов, если я выполнил свои суммы по центру), чтобы как минимум 3 элемента соответствовали из список из 7 возможных.

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

У кого-нибудь есть хоть какой-нибудь хороший эффективный способ сделать это? Я рассматривал простой запрос ИЛИ, чтобы получить записи, в которых хотя бы одно поле совпадает с БД, а затем выполнил некоторую обработку на клиенте, чтобы отфильтровать его еще немного, но некоторые из полей имеют очень низкую мощность и фактически не уменьшат цифры на огромную сумму.

Спасибо Jon

Ответы [ 5 ]

3 голосов
/ 05 мая 2009
Суммирование

OR и CASE будет работать, но весьма неэффективно, так как они не используют индексы.

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

Если пользователь вводит name, phone, email и address в базу данных, и вы хотите проверить все записи, которые соответствуют хотя бы 3 из этих полей, вы вводите:

SELECT  i.*
FROM    (
        SELECT  id, COUNT(*)
        FROM    (
                SELECT  id
                FROM    t_info t
                WHERE   name  = 'Eve Chianese'
                UNION ALL
                SELECT  id
                FROM    t_info t
                WHERE   phone = '+15558000042'
                UNION ALL
                SELECT  id
                FROM    t_info t
                WHERE   email = '42@example.com'
                UNION ALL
                SELECT  id
                FROM    t_info t
                WHERE   address = '42 North Lane'
                ) q
        GROUP BY
                id
        HAVING  COUNT(*) >= 3
        ) dq
JOIN    t_info i
ON      i.id = dq.id

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

См. Эту статью в моем блоге для деталей:

  • Соответствие 3 из 4 : как сопоставить запись, которая соответствует как минимум 3 из 4 возможных условий

См. Также вопрос , на котором основана статья.

Если вы хотите иметь список DISTINCT значений в существующих данных, вы просто заключаете этот запрос в подзапрос:

SELECT  i.*
FROM    t_info i1
WHERE   EXISTS
        (
        SELECT  1
        FROM    (
                SELECT  id
                FROM    t_info t
                WHERE   name  = i1.name
                UNION ALL
                SELECT  id
                FROM    t_info t
                WHERE   phone = i1.phone
                UNION ALL
                SELECT  id
                FROM    t_info t
                WHERE   email = i1.email
                UNION ALL
                SELECT  id
                FROM    t_info t
                WHERE   address = i1.address
                ) q
        GROUP BY
                id
        HAVING  COUNT(*) >= 3
        )

Обратите внимание, что это DISTINCT не является транзитивным: если A соответствует B и B соответствует C, это не означает, что A соответствует C.

2 голосов
/ 05 мая 2009

Возможно, вы захотите что-то вроде следующего:

SELECT id
FROM 
    (select id, CASE fld1 WHEN input1 THEN 1 ELSE 0 "rule1",
        CASE fld2 when input2 THEN 1 ELSE 0 "rule2",
        ...,
        CASE fld7 when input7 THEN 1 ELSE 0 "rule2",
    FROM table)
WHERE rule1+rule2+rule3+...+rule4 >= 3

Это не проверено, но показывает способ решения этой проблемы.

0 голосов
/ 05 мая 2009

Я предполагаю, что ваша система пытается сопоставить идентификаторы тегов определенного сообщения или что-то подобное. Это отношение «несколько ко многим», и для его обработки необходимо иметь три таблицы. Один для поста, один для тегов и один для постов и отношений тегов.

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

SELECT postid, count(tagid) as common_tag_count
FROM posts_to_tags
WHERE tagid IN (tag1, tag2, tag3, ...)
GROUP BY postid
HAVING count(tagid) > 3;
0 голосов
/ 05 мая 2009

Рассматривали ли вы использование хранимой процедуры с курсором? Затем вы можете выполнить запрос «ИЛИ», а затем пошагово просмотреть записи в поисках совпадений. Использование хранимой процедуры позволит вам выполнить все проверки на сервере.

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

0 голосов
/ 05 мая 2009

Какие DBS вы используете? Некоторые поддерживают использование таких ограничений с помощью кода на стороне сервера.

...