Поиск и работа с дублированными пользователями - PullRequest
16 голосов
/ 03 марта 2012

В большой базе данных пользователей со следующим форматом и примерами данных мы пытаемся идентифицировать дублированных людей:

id   first_name    last_name   email
---------------------------------------------------
 1   chris         baker       
 2   chris         baker       chris@gmail.com
 3   chris         baker       chris@hotmail.com
 4   chris         baker       crayzyguy@crazy.com  
 5   carl          castle      castle@npr.org
 6   mike          rotch       fakeuser@sample.com  

Я использую следующий запрос:

SELECT 
    GROUP_CONCAT(id) AS "ids",
    CONCAT(UPPER(first_name), UPPER(last_name)) AS "name",
    COUNT(*) AS "duplicate_count" 
FROM 
    users 
GROUP BY 
    name 
HAVING 
    duplicate_count > 1

Это прекрасно работает; Я получаю список дубликатов с номерами идентификаторов соответствующих строк.

Мы переназначим любые связанные данные, связанные с дубликатом, фактическому человеку (set user_id = 2 where user_id = 3), затем удалим дублирующую строку пользователя.

Проблема возникает после того, как мы делаем этот отчет в первый раз, поскольку мы очищаем список после ручной проверки того, что они действительно являются дубликатами - некоторые НЕ являются дубликатами. Есть 2 Криса Бейкерса, которые являются законными пользователями.

Мы не хотим продолжать видеть Криса Бейкера в последующих повторяющихся отчетах до конца времени, поэтому я ищу способ пометить, что идентификатор пользователя 1 и идентификатор 4 НЕ являются дубликатами друг друга для будущих отчетов, но они могут дублироваться новыми пользователями, добавленными позже.

Что я пробовал

Я добавил поле is_not_duplicate в пользовательскую таблицу, но затем, если новый дубликат "Крис Бейкер" будет добавлен в базу данных, это приведет к тому, что эта ситуация не будет отображаться в дублирующем отчете; is_not_duplicate неправильно исключает один из аккаунтов. Мое утверждение HAVING не будет соответствовать порогу > 1 до тех пор, пока не появятся два дубликата Криса Бейкера, плюс "настоящий", помеченный is_not_duplicate.

Подытоженный вопрос

Как я могу встроить исключения в вышеупомянутый запрос без зацикливания результатов или нескольких запросов?

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

Ответы [ 16 ]

7 голосов
/ 22 августа 2012

Попробуйте добавить логическое поле is_not_duplicate и измените код следующим образом:

SELECT 
    GROUP_CONCAT(id) AS "ids",
    CONCAT(UPPER(first_name), UPPER(last_name)) AS "name",
    COUNT(*) AS "duplicate_count",
    SUM(is_not_duplicate) AS "real_count"
FROM 
    users 
GROUP BY 
    name 
HAVING 
    duplicate_count > 1
AND
    duplicate_count - real_count > 0

Вновь добавленные дубликаты будут иметь is_not_duplicate=0, поэтому real_count для этого имени будет меньше duplicate_count и строка будет отображаться

2 голосов
/ 21 августа 2012

Поскольку это в основном отношение «многие ко многим», я бы добавил новую таблицу not_duplicate с полями user1 и user2.

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

2 голосов
/ 03 марта 2012

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

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

  • Крис Бейкер с id 1 и 4 помечен как valid_duplicates
  • В системе 4 Криса Бейкера
  • Вы получаете количество действительных Крис Бейкер
  • Вы получаете счет всех Крис Бейкер
  • valid_count <> total_count, поэтому верните Криса Бейкера

* Вы, вероятно, даже можете изменить запрос так, чтобы он даже не перечислял дублирующиеся идентификаторы (даже если вы получаете дублирующую маркировку только 1 идентификатора). Вместо того, чтобы перепроверить, какие из них действительны. Это было бы немного сложнее. Без этого, по крайней мере, вы игнорируете Криса Бейкера, пока другой не войдет в систему

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

SELECT 
    GROUP_CONCAT(id) AS "ids",
    CONCAT(UPPER(first_name), UPPER(last_name)) AS "name",
    COUNT(*) AS "duplicate_count" 
FROM 
    users 
WHERE NOT EXISTS
    (
        SELECT 1 
        FROM
        (
            SELECT 
                CONCAT(UPPER(first_name), UPPER(last_name)) AS "name",
                COUNT(*) AS "valid_duplicate_count" 
            FROM 
                users
            WHERE 
                is_valid_duplicate = 1 --true
            GROUP BY 
               name 
            HAVING 
               valid_duplicate_count > 1 
        ) AS duplicate_users
        WHERE 
            duplicate_users.name = users.name 
                AND valid_duplicate_count = duplicate_count
    )    
GROUP BY 
    name 
HAVING 
    duplicate_count > 1

Ниже приведен запрос о том, что должен сделать то же, что и выше, но в окончательном списке будут напечатаны только идентификаторы, которых нет в допустимом списке. Это на самом деле оказалось намного проще, чем я думал. И, в основном, это то же самое, что и выше, но единственная причина, которую я оставил выше, - это оставить два варианта, и в случае, если я испортил вышеупомянутое ... это действительно сложно, так как это много вложенных запросов. Если вам доступны CTE или даже временные таблицы. Это может сделать запрос более выразительным, чтобы разбить его на временные таблицы :). Надеюсь, это поможет и то, что вы ищете

SELECT GROUP_CONCAT(id) AS "ids", 
    CONCAT(UPPER(first_name), UPPER(last_name)) AS "name",
    COUNT(*) AS "final_duplicate_count" 
    --This count could actually be 1 due to the nature of the query 
FROM 
    users
--get the list of duplicated user names
WHERE EXISTS
    (
        SELECT 
            CONCAT(UPPER(first_name), UPPER(last_name)) AS "name",
            COUNT(*) AS "total_duplicate_count"
        FROM 
            users AS total_dup_users
        --ignore valid_users whose count still matches
        WHERE NOT EXISTS
            (
                SELECT 1 
                FROM
                (
                    SELECT 
                        CONCAT(UPPER(first_name), UPPER(last_name)) AS "name",
                        COUNT(*) AS "valid_duplicate_count" 
                    FROM 
                        users AS valid_users
                    WHERE 
                        is_valid_duplicate = 1 --true
                    GROUP BY 
                        name 
                    HAVING 
                        valid_duplicate_count > 1 
                ) AS duplicate_users
                WHERE 
                    --join inner table to outer table
                    duplicate_users.name = total_dup_users.name  
                        --valid count check
                        AND valid_duplicate_count = total_duplicate_count
            )   
            --join inner table to outer table
            AND total_dup_users.Name = users.Name 
        GROUP BY 
            name 
        HAVING 
            duplicate_count > 1
    ) 
    --ignore users that are valid when doing the actual counts
    AND NOT EXISTS
    (
        SELECT 1
        FROM users AS valid
        WHERE 
            --join inner table to outer table
            users.name = 
                CONCAT(UPPER(valid.first_name), UPPER(valid.last_name))
            --only valid users
            AND valid.is_valid_duplicate = 1 --true
    )
GROUP BY 
    FinalDuplicates.Name
1 голос
/ 27 августа 2012

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

SELECT 
    GROUP_CONCAT(id) AS "ids",
    MAX(id) AS "max_id",
    CONCAT(UPPER(first_name), UPPER(last_name)) AS "name",
    COUNT(*) AS "duplicate_count" 
FROM 
    users 
GROUP BY 
    name 
HAVING 
    duplicate_count > 1
    AND
    max_id > MAX_ID_LAST_TIME_DUPLICATE_REPORT_WAS_GENERATED;
1 голос
/ 23 августа 2012

мне кажется, что столбец is_not_duplicate недостаточно сложен для хранения информации, которую вы хотите сохранить - насколько я понимаю, вы хотите вручную указать обнаружению, что два разных пользователя не являются дубликатами друг друга. так что либо вы создаете столбец, например is_not_duplicate_of = other-user-id, либо если вы хотите оставить открытой возможность, что один пользователь может быть определен вручную, а не дублировать более одного пользователя, вам нужна отдельная таблица с двумя столбцами идентификаторов пользователей.

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

SELECT u1.* FROM users u1
INNER JOIN users u2
ON u1.id <> u2.id
AND u2.name = u1.name
WHERE NOT EXISTS (
  SELECT *
  FROM users_non_dups un
  WHERE (un.id1 = u1.id AND un.id2 = u2.id)
  OR (un.id1 = u2.id AND un.id2 = u1.id)
)
0 голосов
/ 27 августа 2012

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

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

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

Чтобы включить, вам понадобятся четыре новых столбца:

  • Status, который будет либо Open, Merged, Split
  • RelatedId, который будет содержать значениеиз которых запись была объединена с
  • ChainId, новый идентификатор записи
  • DateStatusChanged, достаточно очевидный

Открыть будет статусом по умолчанию Слияние будет, когда запись объединена (фактически закрыта и заменена) Разделить будет, если слияние было отменено

Так, например, пройдитесь по всемзаписи, которые, например, имеют одно и то же имя.Объедините их в пары.Поэтому, если у вас есть три Криса Бейкерса, записи 1, 2 и 3, объедините 1 и 2, чтобы сделать запись 4, а затем 3 и 4, чтобы сделать запись 5. Ваша таблица в итоге будет выглядеть примерно так:

ID  NAME        STATUS  RELATEDID  CHAINID DATESTATUSCHANGED [other rows omitted]
 1  Chris Baker MERGED          2        4       27-AUG-2012
 2  Chris Baker MERGED          1        4       27-AUG-2012
 3  Chris Baker MERGED          4        5       28-AUG-2012
 4  Chris Baker MERGED          3        5       28-AUG-2012
 5  Chris Baker   OPEN

Таким образом, у вас есть полная запись того, что произошло с вашими данными, чтобы отменить любые изменения путем объединения, если, например, контакты 1 и 2 не совпадают, вы отменяете слияние 3 и 4, отменяете слияние 1 и 2,в конечном итоге вы получите следующее:

ID  NAME        STATUS  RELATEDID  CHAINID DATESTATUSCHANGED
 1  Chris Baker  SPLIT          2        4       29-AUG-2012
 2  Chris Baker  SPLIT          1        4       29-AUG-2012
 3  Chris Baker  SPLIT          4        5       29-AUG-2012
 4  Chris Baker CLOSED          3        5       29-AUG-2012
 5  Chris Baker CLOSED                           29-AUG-2012

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

0 голосов
/ 26 августа 2012

Почему в этом случае вы не сделаете столбец электронной почты уникальным идентификатором, и после однократной очистки ваших записей вы не разрешите дублировать их далее?

0 голосов
/ 26 августа 2012

Я дал Джастину Пихони +1 в качестве 1-го, чтобы предложить сравнить число дубликатов с количеством не дубликатов, и Гранту Хачатряну +1 за то, что он был 1-м, чтобы показать эффективный способ сделать это.немного другой метод, плюс некоторое переименование, чтобы сделать все немного более понятным, плюс некоторые дополнительные столбцы в запросе, чтобы было понятно, какие записи нужно сравнивать как потенциальные дубликаты.

Я бы назвал новый столбец«CONFIRMED_UNIQUE» вместо «IS_NOT_DUPLICATE».Как и в случае с Грантом, я бы сделал его логическим (tinyint (1) с 0 = ЛОЖЬ и 1 = ИСТИНА).

Потенциал_произведения - это максимальное количество записей, которое необходимо удалить.*

0 голосов
/ 25 августа 2012

Добавьте is_not_duplicate по типу бита в вашу таблицу и используйте следующий запрос после установки is_not_duplicate значения данных:

SELECT  GROUP_CONCAT(id) AS "ids",
        CONCAT(UPPER(first_name), UPPER(last_name)) AS "name"
FROM users 
GROUP BY name 
HAVING COUNT(*) > SUM(CAST(is_not_duplicate AS INT))

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

0 голосов
/ 24 августа 2012

Я бы посоветовал вам создать пару вещей:

  1. Логический столбец для отметки подтвержденных пользователей
  2. Строковый столбец для сохранения идентификаторов
  3. Триггерэто проверит, есть ли уже имя и фамилия, чтобы заполнить флаг, и сохранит в строковом столбце все идентификаторы, для которых этот возможный дубликат.

И затем создайте отчеткоторый ищет дублированное значение true и декодирует строковое поле, чтобы найти возможное дублированное значение

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