Как я могу выбрать наиболее близкое соответствие в SQL Server 2005? - PullRequest
1 голос
/ 28 августа 2009

В SQL Server 2005 у меня есть таблица входных данных об успешных продажах, а также множество таблиц с информацией об известных клиентах и ​​их деталях. Для каждой строки продаж мне нужно соответствовать 0 или 1 известным клиентам.

У нас есть следующая информация из таблицы продаж:
ServiceId, Адрес, Почтовый Индекс, Адрес электронной почты, Домашний телефон, Имя, LastName

Информация о клиентах включает в себя все это, а также дату «LastTransaction».

Любое из этих полей может быть привязано к 0 или более клиентам. Мы считаем совпадение в любое время, когда ServiceId, Address + ZipCode, EmailAddress или HomePhone в таблице продаж точно соответствуют клиенту.

Проблема в том, что у нас есть информация о многих клиентах, иногда о нескольких в одном домохозяйстве. Это означает, что у нас могут быть Джон Доу, Джейн Доу, Джим Доу и Боб Доу в одном доме. Все они будут соответствовать на Address + ZipCode и HomePhone - и, возможно, более одного также совпадут на ServiceId.

Мне нужен какой-то способ элегантно отслеживать в транзакции «лучшее» совпадение клиента. Если одно соответствует 6 полям, а остальные только 5, то этот клиент должен быть сохранен как соответствующий этой записи. В случае множественного совпадения 5 и ни одного совпадения больше следует сохранить самую последнюю дату LastTransaction.

Любые идеи будут весьма признательны.

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

Ответы [ 6 ]

3 голосов
/ 28 августа 2009

для SQL Server 2005 и выше попробуйте:

;WITH SalesScore AS (
SELECT
    s.PK_ID as S_PK
        ,c.PK_ID AS c_PK
        ,CASE 
             WHEN c.PK_ID IS NULL THEN 0
             ELSE CASE WHEN s.ServiceId=c.ServiceId THEN 1 ELSE 0 END
                  +CASE WHEN (s.Address=c.Address AND s.Zip=c.Zip) THEN 1 ELSE 0 END
                  +CASE WHEN s.EmailAddress=c.EmailAddress THEN 1 ELSE 0 END
                  +CASE WHEN s.HomePhone=c.HomePhone THEN 1 ELSE 0 END
         END AS Score
    FROM Sales s
        LEFT OUTER JOIN Customers c ON s.ServiceId=c.ServiceId
                                       OR (s.Address=c.Address AND s.Zip=c.Zip)
                                       OR s.EmailAddress=c.EmailAddress
                                       OR s.HomePhone=c.HomePhone 
)
SELECT 
    s.*,c.*
    FROM (SELECT
              S_PK,MAX(Score) AS Score
              FROM SalesScore 
              GROUP BY S_PK
         ) dt
        INNER JOIN Sales          s ON dt.s_PK=s.PK_ID 
        INNER JOIN SalesScore    ss ON dt.s_PK=s.PK_ID AND dt.Score=ss.Score
        LEFT OUTER JOIN Customers c ON ss.c_PK=c.PK_ID

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

;WITH SalesScore AS (
SELECT
    s.PK_ID as S_PK
        ,c.PK_ID AS c_PK
        ,CASE 
             WHEN c.PK_ID IS NULL THEN 0
             ELSE CASE WHEN s.ServiceId=c.ServiceId THEN 1 ELSE 0 END
                  +CASE WHEN (s.Address=c.Address AND s.Zip=c.Zip) THEN 1 ELSE 0 END
                  +CASE WHEN s.EmailAddress=c.EmailAddress THEN 1 ELSE 0 END
                  +CASE WHEN s.HomePhone=c.HomePhone THEN 1 ELSE 0 END
         END AS Score
    FROM Sales s
        LEFT OUTER JOIN Customers c ON s.ServiceId=c.ServiceId
                                       OR (s.Address=c.Address AND s.Zip=c.Zip)
                                       OR s.EmailAddress=c.EmailAddress
                                       OR s.HomePhone=c.HomePhone 
)
SELECT
    *
    FROM (SELECT 
              s.*,c.*,row_number() over(partition by s.PK_ID order by s.PK_ID ASC,c.LastTransaction DESC) AS RankValue
              FROM (SELECT
                        S_PK,MAX(Score) AS Score
                        FROM SalesScore 
                        GROUP BY S_PK
                   ) dt
                  INNER JOIN Sales          s ON dt.s_PK=s.PK_ID 
                  INNER JOIN SalesScore    ss ON dt.s_PK=s.PK_ID AND dt.Score=ss.Score
                  LEFT OUTER JOIN Customers c ON ss.c_PK=c.PK_ID
         ) dt2
    WHERE dt2.RankValue=1
1 голос
/ 28 августа 2009

Честно говоря, я бы вообще не хотел этого делать, так как у вас нет уникального идентификатора в ваших данных.

Джон Смит живет со своим сыном Джоном Смитом, и они оба используют один и тот же адрес электронной почты и домашний телефон. Это два человека, но вы бы сопоставили их как одного. Мы все время сталкиваемся с нашими данными и из-за этого не имеем решения для автоматического сопоставления. Мы выявляем возможные ошибки и на самом деле физически звоним и выясняем, являются ли они ошибками.

1 голос
/ 28 августа 2009

Вот довольно уродливый способ сделать это, используя код SQL Server. Предположения:
- Столбец CustomerId существует в таблице Customer для уникальной идентификации клиентов.
- Поддерживаются только точные совпадения (как подразумевается в вопросе).

SELECT top 1 CustomerId, LastTransaction, count(*) HowMany
 from (select Customerid, LastTransaction
        from Sales sa
         inner join Customers cu
          on cu.ServiceId = sa.ServiceId
       union all select Customerid, LastTransaction
        from Sales sa
         inner join Customers cu
          on cu.EmailAddress = sa.EmailAddress
       union all select Customerid, LastTransaction
        from Sales sa
         inner join Customers cu
          on cu.Address = sa.Address
           and cu.ZipCode = sa.ZipCode
       union all [etcetera -- repeat for each possible link]
      ) xx
 group by CustomerId, LastTransaction
 order by count(*) desc, LastTransaction desc

Мне не нравится использовать "top 1", но писать быстрее. (Альтернативой является использование функций ранжирования, для которых потребуется либо другой уровень подзапроса, либо использование его в качестве CTE.) Конечно, если ваши таблицы большие, они будут летать как корова, если у вас нет индексов по всем столбцам.

0 голосов
/ 28 августа 2009

Существует также алгоритм расстояния Левенштейна .

0 голосов
/ 28 августа 2009

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

Например, в Oracle есть функция UTL_MATCH.JARO_WINKLER_SIMILARITY:
http://www.psoug.org/reference/utl_match.html

0 голосов
/ 28 августа 2009

Я бы, вероятно, создал бы хранимую функцию для этого (в Oracle) и одер на самом высоком совпадении

SELECT * FROM (
 SELECT c.*, MATCH_CUSTOMER( Customer.Id, par1, par2, par3 ) matches FROM Customer c
) WHERE matches >0 ORDER BY matches desc

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

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