2009-07-02 7 views
2

Diese Abfrage für eine Liste in Frage kommender Duplikate zu schaffen ist einfach genug:TSQL Group By mit einem "ODER"?

SELECT Count(*), Can_FName, Can_HPhone, Can_EMail 
FROM Can 
GROUP BY Can_FName, Can_HPhone, Can_EMail 
HAVING Count(*) > 1 

Aber wenn die tatsächliche Regel, die ich gegen überprüfen möchten FName und (HPhone ODER E-Mail) ist - wie kann ich die GROUP BY einstellen arbeiten mit diesem?

Ich bin ziemlich sicher, dass ich am Ende mit einer UNION SELECT hier enden werde (zB FName, HPhone auf einem und FName, EMail auf der anderen und kombinieren die Ergebnisse) - aber ich würde gerne wissen, ob Jeder kennt einen einfacheren Weg dazu.

Vielen Dank im Voraus für jede Hilfe.

Scott in Maine

+0

Sie wollen also "Bob/567/irgendetwas", "Bob/alles/[email protected]" und "Bob/567/[email protected]" als Duplikate zählen? Was ist, wenn es eine "Bob/234/[email protected]" gibt? Einige Datenbeispiele zur Verdeutlichung Ihrer Gruppierungsregel würden wirklich helfen. – AakashM

+0

Verständlich. Das tut mir leid. Quassnoi hat unten ein gutes Beispiel gegeben (und ich habe die Frage dort beantwortet). Vielen Dank. – Scott04073

+0

Möchten Sie tatsächlich Duplikate entfernen? –

Antwort

0

Keine dieser Antworten ist richtig. Quassnois ist ein anständiger Ansatz, aber Sie werden einen fatalen Fehler in den Ausdrücken "qo.id> dup.id" und "di.chainid < do.chainid" bemerken: Vergleiche, die durch ID gemacht wurden! Dies ist IMMER eine schlechte Übung, weil es von einer inhärenten Ordnung in den IDs abhängt. IDs sollten NIEMALS eine implizite Bedeutung erhalten und NUR an Gleichheits- oder Nulltests teilnehmen. Sie können Quassnois Lösung in diesem Beispiel einfach durch Neuordnen der IDs in den Daten unterbrechen. Das wesentliche Problem ist eine disjunktive Bedingung mit einer Gruppierung, die zu der Möglichkeit führt, dass zwei Datensätze durch ein Zwischenglied verknüpft sind, obwohl sie nicht direkt zuordenbar sind.

zB angegeben Sie diese Datensätze alle gruppiert werden sollen:

(1) John 555-00-00 [email protected]

(2) John 555-00-01 john @ example.

com

(3) John 555-00-01 [email protected]

können Sie sehen, dass # 1 und # 2 sind zuordenbar, so sind # 2 und # 3, aber deutlich # 1 und # 3 sind nicht direkt als Gruppe zuordenbar.

Dies legt fest, dass eine rekursive oder iterative Lösung die einzige mögliche Lösung ist.

Also, Rekursion ist nicht praktikabel, da Sie leicht in einer Schleife Situation landen können. Genau das versuchte Quassnoi mit seinen ID-Vergleichen zu vermeiden, brach dabei aber den Algorithmus. Sie könnten versuchen, die Rekursionsebenen zu begrenzen, aber Sie werden dann möglicherweise nicht alle Beziehungen abschließen, und Sie werden möglicherweise immer noch Schleifen auf sich selbst folgen, was zu übermäßiger Datengröße und prohibitiver Ineffizienz führt.

Die beste Lösung ist ITERATIVE: Starten Sie eine Ergebnismenge, indem Sie jede ID als eindeutige Gruppen-ID markieren und dann die Ergebnismenge durchlaufen und aktualisieren, indem Sie IDs mit derselben eindeutigen Gruppen-ID kombinieren, wie sie für die disjunktive Bedingung passen . Wiederholen Sie den Vorgang für den aktualisierten Satz jedes Mal, bis keine weiteren Aktualisierungen mehr möglich sind.

Ich werde bald Beispielcode für diese erstellen.

+0

Im Rahmen einer Abfrage sind die IDs stabil. Wenn sie nicht vergleichbar sind oder nicht existieren, können Sie sie durch eine generierte 'ROW_NUMBER' ersetzen. – Quassnoi

+0

Grundsätzlich was ich getan habe. Ich habe beide Aspekte gruppiert - dann habe ich einen vollständigen Vergleich über die DB-Funktion gemacht. – Scott04073

0

GROUP BY nicht unterstützt oder - es ist implizit AND und müssen jede nicht-Aggregator in der Auswahlliste enthalten.

+0

Sie können möglicherweise etwas zusammen mit einer benutzerdefinierten Funktion hacken, z. B. UserOR (Hphone, E-Mail), dann in der GROUP BY –

+0

BTW, es müsste in der Auswahlliste und in der Gruppierung nach Klausel sein. Und die Funktion müsste deterministisch sein, aber OR ist deterministisch, also ... –

+0

@Arnshea: ein OR kann nur drei Ergebnisse geben, also wird er höchstens drei Gruppen bekommen. – Quassnoi

3

Bevor ich etwas raten kann, muss ich die Antwort auf diese Frage wissen:

name phone  email 

John 555-00-00 [email protected] 
John 555-00-01 [email protected] 
John 555-00-01 [email protected] 

Was COUNT(*) Sie für diese Daten wollen?

Update:

Wenn Sie wollen einfach nur wissen, dass ein Datensatz keine Duplikate hat, verwenden Sie diese:

WITH q AS (
     SELECT 1 AS id, 'John' AS name, '555-00-00' AS phone, '[email protected]' AS email 
     UNION ALL 
     SELECT 2 AS id, 'John', '555-00-01', '[email protected]' 
     UNION ALL 
     SELECT 3 AS id, 'John', '555-00-01', '[email protected]' 
     UNION ALL 
     SELECT 4 AS id, 'James', '555-00-00', '[email protected]' 
     UNION ALL 
     SELECT 5 AS id, 'James', '555-00-01', '[email protected]' 
     ) 
SELECT * 
FROM q qo 
WHERE EXISTS 
     (
     SELECT NULL 
     FROM q qi 
     WHERE qi.id <> qo.id 
       AND qi.name = qo.name 
       AND (qi.phone = qo.phone OR qi.email = qo.email) 
     ) 

Es ist effizienter, aber nicht sagen, wo das Duplikat Kette begann.

Diese Abfrage wählt alle Einträge zusammen mit dem speziellen Feld chainid aus, das angibt, wo die doppelte Kette gestartet wurde.

WITH q AS (
     SELECT 1 AS id, 'John' AS name, '555-00-00' AS phone, '[email protected]' AS email 
     UNION ALL 
     SELECT 2 AS id, 'John', '555-00-01', '[email protected]' 
     UNION ALL 
     SELECT 3 AS id, 'John', '555-00-01', '[email protected]' 
     UNION ALL 
     SELECT 4 AS id, 'James', '555-00-00', '[email protected]' 
     UNION ALL 
     SELECT 5 AS id, 'James', '555-00-01', '[email protected]' 
     ), 
     dup AS (
     SELECT id AS chainid, id, name, phone, email, 1 as d 
     FROM q 
     UNION ALL 
     SELECT chainid, qo.id, qo.name, qo.phone, qo.email, d + 1 
     FROM dup 
     JOIN q qo 
     ON  qo.name = dup.name 
       AND (qo.phone = dup.phone OR qo.email = dup.email) 
       AND qo.id > dup.id 
     ), 
     chains AS 
     (
     SELECT * 
     FROM dup do 
     WHERE chainid NOT IN 
       (
       SELECT id 
       FROM dup di 
       WHERE di.chainid < do.chainid 
       ) 
     ) 
SELECT * 
FROM chains 
ORDER BY 
     chainid 
+0

Nach der Definition - das ist ein Count (*) von 3. Daher die Komplexität. Vielen Dank. – Scott04073

+0

Eigentlich - lass mich das nochmal anschauen. Ich komme gleich wieder. – Scott04073

+0

OK - definitiv 3. Musste das nochmal überprüfen. – Scott04073

0

Ich nehme an, Sie haben auch eine eindeutige ID-Integer als Primärschlüssel für diese Tabelle. Wenn Sie dies nicht tun, ist es eine gute Idee, einen für diesen Zweck und viele andere zu haben.

finden diese Duplikate durch ein Self-Join:

select 
    c1.ID 
, c1.Can_FName 
, c1.Can_HPhone 
, c1.Can_Email 
, c2.ID 
, c2.Can_FName 
, c2.Can_HPhone 
, c2.Can_Email 
from 
(
    select 
     min(ID), 
     Can_FName, 
     Can_HPhone, 
     Can_Email 
    from Can 
    group by 
     Can_FName, 
     Can_HPhone, 
     Can_Email 
) c1 
inner join Can c2 on c1.ID < c2.ID 
where 
    c1.Can_FName = c2.Can_FName 
and (c1.Can_HPhone = c2.Can_HPhone OR c1.Can_Email = c2.Can_Email) 
order by 
    c1.ID 

Die Abfrage gibt Ihnen N-1 Zeilen für jede N doppelte Kombinationen - wenn Sie zusammen mit jeder einzigartige Kombination nur eine Zählung wollen, sind die Reihen zählen gruppiert von der „linken“ Seite:

select count(1) + 1, 
, c1.Can_FName 
, c1.Can_HPhone 
, c1.Can_Email 
from 
(
    select 
     min(ID), 
     Can_FName, 
     Can_HPhone, 
     Can_Email 
    from Can 
    group by 
     Can_FName, 
     Can_HPhone, 
     Can_Email 
) c1 
inner join Can c2 on c1.ID < c2.ID 
where 
    c1.Can_FName = c2.Can_FName 
and (c1.Can_HPhone = c2.Can_HPhone OR c1.Can_Email = c2.Can_Email) 
group by 
    c1.Can_FName 
, c1.Can_HPhone 
, c1.Can_Email 

Zugegeben, das ist komplizierter als eine Union - aber ich denke, es ist darüber nachzudenken, Duplikate eine gute Art und Weise veranschaulicht.

0

Projekt der gewünschte Transformation zunächst aus einer Tabelle abgeleitet, tut dann die Aggregation:

SELECT COUNT(*) 
    , CAN_FName 
    , Can_HPhoneOrEMail 
    FROM (
     SELECT Can_FName 
      , ISNULL(Can_HPhone,'') + ISNULL(Can_EMail,'') AS Can_HPhoneOrEMail 
     FROM Can) AS Can_Transformed 
    GROUP BY Can_FName, Can_HPhoneOrEMail 
    HAVING Count(*) > 1 

Ihren Betrieb ‚OR‘ einstellen, wie in der abgeleiteten Tabelle Projektliste benötigt.

+0

Dies scheint eher eine UND-Situation zu sein, die Sie beschreiben, als ein "ODER" - Danke für die Mühe. – Scott04073

0

Ich weiß, diese Antwort wird für die Verwendung der temporären Tabelle kritisiert werden, aber es funktioniert trotzdem:

-- create temp table to give the table a unique key 
create table #tmp(
ID int identity, 
can_Fname varchar(200) null, -- real type and len here 
can_HPhone varchar(200) null, -- real type and len here 
can_Email varchar(200) null, -- real type and len here 
) 

-- just copy the rows where a duplicate fname exits 
-- (better performance specially for a big table) 
insert into #tmp 
select can_fname,can_hphone,can_email 
from Can 
where can_fname exists in (select can_fname from Can 
group by can_fname having count(*)>1) 

-- select the rows that have the same fname and 
-- at least the same phone or email 
select can_Fname, can_Hphone, can_Email 
from #tmp a where exists 
(select * from #tmp b where 
a.ID<>b.ID and A.can_fname = b.can_fname 
and (isnull(a.can_HPhone,'')=isnull(b.can_HPhone,'') 
or (isnull(a.can_email,'')=isnull(b.can_email,'')) 
+0

Manchmal sind temporäre Tabellen die beste oder sogar (selten) einzige Möglichkeit, ein Problem zu lösen .... – RolandTumble

0

Try this:

SELECT Can_FName, COUNT(*) 
FROM (
SELECT 
rank() over(partition by Can_FName order by Can_FName,Can_HPhone) rnk_p, 
rank() over(partition by Can_FName order by Can_FName,Can_EMail) rnk_m, 
Can_FName 
FROM Can 
) X 
WHERE rnk_p=1 or rnk_m =1 
GROUP BY Can_FName 
HAVING COUNT(*)>1