SQL-запрос типа GROUP BY с условием OR - PullRequest
8 голосов
/ 08 июня 2011

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

Пример 1 : Из реальной жизни вы можете представить себе Тома и Сандру, которые женаты. Том, который заказал 4 продукта, заполнил в нашей системе бронирования 3 разных адреса электронной почты и 2 разных телефонных номера, когда один из них поделился с Сандрой (в качестве домашнего телефона), поэтому я могу предположить, что они как-то связаны. Сандра, кроме этого общего номера телефона, также заполнила свой личный номер, и для обоих заказов она использовала только один адрес электронной почты. Для меня это означает считать все из следующих строк одним уникальным клиентом . Так что на самом деле этот уникальный клиент может вырасти во всю семью.

ID   E-mail              Phone          Comment
---- ------------------- -------------- ------------------------------
0    tom@email.com       +44 111 111    First row
1    tommy@email.com     +44 111 111    Same phone, different e-mail
2    thomas@email.com    +44 111 111    Same phone, different e-mail
3    thomas@email.com    +44 222 222    Same e-mail, different phone
4    sandra@email.com    +44 222 222    Same phone, different e-mail
5    sandra@email.com    +44 333 333    Same e-mail, different phone

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

Пример 2 : Вот пример того, что я хочу сделать.

Можно ли получить количество уникальных клиентов без использования рекурсии, например: используя курсор или что-то еще или необходима рекурсия?

ID   E-mail              Phone          Comment
---- ------------------- -------------- ------------------------------
0    linsey@email.com    +44 111 111    ─┐
1    louise@email.com    +44 111 111     ├─ 1. unique customer
2    louise@email.com    +44 222 222    ─┘
---- ------------------- -------------- ------------------------------
3    steven@email.com    +44 333 333    ─┐
4    steven@email.com    +44 444 444     ├─ 2. unique customer
5    sandra@email.com    +44 444 444    ─┘
---- ------------------- -------------- ------------------------------
6    george@email.com    +44 555 555    ─── 3. unique customer
---- ------------------- -------------- ------------------------------
7    xavier@email.com    +44 666 666    ─┐
8    xavier@email.com    +44 777 777     ├─ 4. unique customer
9    xavier@email.com    +44 888 888    ─┘
---- ------------------- -------------- ------------------------------
10   robert@email.com    +44 999 999    ─┐
11   miriam@email.com    +44 999 999     ├─ 5. unique customer
12   sherry@email.com    +44 999 999    ─┘
---- ------------------- -------------- ------------------------------
----------------------------------------------------------------------
Result                                  ∑ = 5 unique customers
----------------------------------------------------------------------

Я пробовал запрос с помощью GROUP BY, но я не знаю, как сгруппировать результат по первому или второму столбцу. Я ищу, скажем, что-то вроде

SELECT COUNT(*) FROM Customers
GROUP BY Email OR Phone

Еще раз спасибо за любые предложения

P.S. Я действительно ценю ответы на этот вопрос, прежде чем перефразировать. Теперь ответы здесь могут не соответствовать обновлению, поэтому, пожалуйста, не понижайте голос здесь, если вы собираетесь это сделать (кроме вопроса, конечно:) . Я полностью переписал этот пост.

Спасибо и извините за неправильный запуск.

Ответы [ 5 ]

1 голос
/ 13 июня 2011

Вот полное решение с использованием рекурсивного CTE.

;WITH Nodes AS
(
    SELECT DENSE_RANK() OVER (ORDER BY Part, PartRank) SetId
        , [ID]
    FROM
    (
        SELECT [ID], 1 Part, DENSE_RANK() OVER (ORDER BY [E-mail]) PartRank
        FROM dbo.Customer
        UNION ALL
        SELECT [ID], 2, DENSE_RANK() OVER (ORDER BY Phone) PartRank
        FROM dbo.Customer
    ) A
),
Links AS
(
    SELECT DISTINCT A.Id, B.Id LinkedId
    FROM Nodes A
    JOIN Nodes B ON B.SetId = A.SetId AND B.Id < A.Id
),
Routes AS
(
    SELECT DISTINCT Id, Id LinkedId
    FROM dbo.Customer

    UNION ALL

    SELECT DISTINCT Id, LinkedId
    FROM Links

    UNION ALL

    SELECT A.Id, B.LinkedId
    FROM Links A
    JOIN Routes B ON B.Id = A.LinkedId AND B.LinkedId < A.Id
),
TransitiveClosure AS
(
    SELECT Id, Id LinkedId
    FROM Links

    UNION

    SELECT LinkedId Id, LinkedId
    FROM Links

    UNION

    SELECT Id, LinkedId
    FROM Routes
),
UniqueCustomers AS
(
    SELECT Id, MIN(LinkedId) UniqueCustomerId
    FROM TransitiveClosure
    GROUP BY Id
)
SELECT A.Id, A.[E-mail], A.Phone, B.UniqueCustomerId
FROM dbo.Customer A
JOIN UniqueCustomers B ON B.Id = A.Id
1 голос
/ 08 июня 2011

Поиск групп, которые имеют только один телефон:

SELECT
    ID
  , Name
  , Phone
  , DENSE_RANK() OVER (ORDER BY Phone) AS GroupPhone
FROM 
    MyTable
ORDER BY
    GroupPhone
  , ID

Поиск групп с одинаковыми именами:

SELECT
    ID
  , Name
  , Phone
  , DENSE_RANK() OVER (ORDER BY Name) AS GroupName
FROM 
    MyTable
ORDER BY
    GroupName
  , ID

Теперь, для (сложного) запроса, который вы описываете, скажем, у нас есть такая таблица:

ID   Name          Phone
---- ------------- -------------
0    Kate          +44 333 333
1    Sandra        +44 000 000
2    Thomas        +44 222 222
3    Robert        +44 000 000
4    Thomas        +44 444 444
5    George        +44 222 222
6    Kate          +44 000 000
7    Robert        +44 444 444
--------------------------------

Должны ли все они быть в одной группе? Поскольку они все делятся именем или телефоном с кем-то еще, образуя «цепочку» родственников:

0-6   same name
6-1-3 same phone
3-7   same name
7-4   same-phone
4-2   same name
2-5   bame phone
0 голосов
/ 08 июня 2011

Вот мое решение:

SELECT p.LastName, P.FirstName, P.HomePhone,
CASE 
    WHEN ph.PhoneCount=1 THEN       
        CASE 
            WHEN n.NameCount=1 THEN 'unique name and phone'
            ELSE 'common name'
        END

    ELSE        
        CASE 
            WHEN n.NameCount=1 THEN 'common phone'
            ELSE 'common phone and name'        
        END             
END
FROM Contacts p
INNER JOIN 
(SELECT HomePhone, count(LastName) as PhoneCount
FROM Contacts
GROUP BY HomePhone) ph ON ph.HomePhone = p.HomePhone

INNER JOIN 
(SELECT FirstName, count(LastName) as NameCount
FROM Contacts
GROUP BY FirstName) n ON n.FirstName = p.FirstName


LastN       FirstN  Phone       Comment
Hoover      Brenda  8138282334  unique name and phone
Washington  Brian   9044563211  common name
Roosevelt   Brian   7737653279  common name
Reagan      Charles 7734567869  unique name and phone
0 голосов
/ 08 июня 2011

Для набора данных в примере вы можете написать что-то вроде этого:

;WITH Temp AS (
    SELECT Name, Phone,
        DENSE_RANK() OVER (ORDER BY Name) AS NameGroup,
        DENSE_RANK() OVER (ORDER BY Phone) AS PhoneGroup
    FROM MyTable)
SELECT MAX(Phone), MAX(Name), COUNT(*)
FROM Temp
GROUP BY NameGroup, PhoneGroup
0 голосов
/ 08 июня 2011

Я не знаю, является ли это лучшим решением, но вот оно:

SELECT
  MyTable.ID, MyTable.Name, MyTable.Phone,
  CASE WHEN N.No = 1 AND P.No = 1 THEN 1
       WHEN N.No = 1 AND P.No > 1 THEN 2
       WHEN N.No > 1 OR P.No > 1  THEN 3
  END as GroupRes
FROM
  MyTable 
  JOIN (SELECT Name, count(Name) No FROM MyTable GROUP BY Name) N on MyTable.Name = N.Name
  JOIN (SELECT Phone, count(Phone) No FROM MyTable GROUP BY Phone) P on MyTable.Phone = P.Phone

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

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