2011-06-08 23 views
7

Cercherò di descrivere la situazione reale. Nella nostra azienda abbiamo un sistema di prenotazione con un tavolo, chiamiamolo clienti, in cui e-mail e contatti telefonici vengono salvati con ogni ordine in arrivo - questa è la parte di un sistema che non posso cambiare. Sto affrontando il problema su come ottenere il conteggio dei unici clienti. Con il unico cliente intendo un gruppo di persone che ha la stessa e-mail o lo stesso numero di telefono.query SQL come GROUP BY con condizione OR

Esempio 1: Dalla vita reale puoi immaginare Tom e Sandra che sono sposati. Tom, che ha ordinato 4 prodotti, ha inserito nel nostro sistema di prenotazione 3 diversi indirizzi e-mail e 2 numeri di telefono diversi quando uno di loro condivide con Sandra (come homephone), quindi posso presumere che siano collegati in qualche modo. Sandra tranne questo numero di telefono condiviso ha riempito anche il suo numero privato e per entrambi gli ordini ha utilizzato un solo indirizzo e-mail. Per me questo significa contare tutte delle righe seguenti come uno cliente unico. In effetti, questo cliente unico può crescere in tutta la famiglia.

ID E-mail    Phone   Comment 
---- ------------------- -------------- ------------------------------ 
0 [email protected]  +44 111 111 First row 
1 [email protected]  +44 111 111 Same phone, different e-mail 
2 [email protected] +44 111 111 Same phone, different e-mail 
3 [email protected] +44 222 222 Same e-mail, different phone 
4 [email protected] +44 222 222 Same phone, different e-mail 
5 [email protected] +44 333 333 Same e-mail, different phone 

Come ypercube detto probabilmente avrò bisogno di una ricorsione per contare tutti questi clienti unici.

Esempio 2: Ecco l'esempio di ciò che voglio fare.

È possibile ottenere il conteggio di clienti unici senza ricorrere alla ricorsione, ad esempio utilizzando il cursore o qualcosa del genere o è necessaria la ricorsione?

ID E-mail    Phone   Comment 
---- ------------------- -------------- ------------------------------ 
0 [email protected] +44 111 111 ─┐ 
1 [email protected] +44 111 111  ├─ 1. unique customer 
2 [email protected] +44 222 222 ─┘ 
---- ------------------- -------------- ------------------------------ 
3 [email protected] +44 333 333 ─┐ 
4 [email protected] +44 444 444  ├─ 2. unique customer 
5 [email protected] +44 444 444 ─┘ 
---- ------------------- -------------- ------------------------------ 
6 [email protected] +44 555 555 ─── 3. unique customer 
---- ------------------- -------------- ------------------------------ 
7 [email protected] +44 666 666 ─┐ 
8 [email protected] +44 777 777  ├─ 4. unique customer 
9 [email protected] +44 888 888 ─┘ 
---- ------------------- -------------- ------------------------------ 
10 [email protected] +44 999 999 ─┐ 
11 [email protected] +44 999 999  ├─ 5. unique customer 
12 [email protected] +44 999 999 ─┘ 
---- ------------------- -------------- ------------------------------ 
---------------------------------------------------------------------- 
Result         ∑ = 5 unique customers 
---------------------------------------------------------------------- 

ho provato una query con GROUP BY, ma non so come gruppo il risultato per primo o seconda colonna. Sto cercando diciamo qualcosa come

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

Grazie ancora per eventuali suggerimenti

P.S. Apprezzo molto le risposte a questa domanda prima della riformulazione completa. Ora le risposte qui non possono corrispondere a l'aggiornamento quindi per favore non downvote qui se avete intenzione di farlo (ad eccezione della domanda ovviamente :). Ho completamente riscritto questo post.

Grazie e scusa per il mio inizio sbagliato.

+0

Effettua una ricerca per '' sopra' e PARTITION BY' o 'finestra functions' –

+0

Perché George ha un conteggio di 3 e perché la sua Id va dal 5 al 7 –

+0

Forse non è chiaro dalla mia domanda, ma sto cercando solo un numero così netto di clienti unici. Aggiornerò la mia domanda, mi dispiace per i partecipanti e grazie per lo sforzo. –

risposta

0

Ecco una soluzione completa utilizzando una CTE ricorsiva.

;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 
+0

+1 e accetta; grazie; questo funziona bene, ma è anche estremamente lento. Ora sto pensando di raggruppare il trigger di inserimento in una colonna separata (questo aumenterà la velocità dato che userò INT come tipo di dati per esso). In ogni caso congratulazioni per il tuo prossimo 2k :) –

+1

Grazie a @emonemon. Vedo che stai raggiungendo anche 2k :-). Hai ragione, è lento. È possibile riscriverlo come una funzione di tabella a più righe. Usa una variabile di tabella. Sostituisci la parte ricorsiva con un ciclo WHILE, che rimuove i duplicati superflui su ogni iterazione. Questo dovrebbe ridurre l'esplosione di file temporanei e, si spera, accelerare le cose. –

0

Non so se questa è la soluzione migliore, ma qui è:

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 

Il problema è che qui ci sono alcuni unisce fatta su VARCHAR e potrebbe finire per aumentare il tempo di esecuzione.

+0

ID: 7, Nome: George termina con un conteggio di 2, ma probabilmente è corretto. Dovresti notare che ti manca 'Group by' sulle tue viste in linea e probabilmente non sarebbe male aggiungere 'Order by GroupRes' –

+0

@Conrad Frix: Ups, dimenticato di mettere GROUP BY.Grazie – CristiC

0

Per il set di dati nel esempio si potrebbe scrivere qualcosa del genere:

;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 
1

Trovare gruppi che hanno solo lo stesso Telefono:

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

Trovare gruppi che hanno solo lo stesso nome:

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

Ora, per la (complessa) query che si descrive, diciamo che abbiamo un tavolo come questo, invece:

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 
-------------------------------- 

Dovrebbe essere tutto questo in un unico gruppo? Come tutti nome della condivisione o telefono con qualcun altro, formando una "catena" di persone relative:

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

sì, esattamente; la tabella che hai menzionato potrebbe restituire count = 1 e come hai detto vorrei creare una 'chain'. Nella vita reale il nome è e-mail, che è unico per chiunque nel mondo, ma puoi avere 2 numeri di telefono con la stessa e-mail o lo stesso telefono con e-mail differenti. Ritengo che questa sia una query molto complicata. –

+0

È complicato, sì. Avrà bisogno di una ricorsione, ma sono sicuro che qualcuno troverà il modo di scriverlo. –

0

Ecco la mia soluzione:

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