TSQL Gruppierung mit einem „OR“?
Frage
Diese Abfrage eine Liste in Frage kommender Duplikate für die Erstellung ist einfach genug:
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öge FName und (HPhone ODER E-Mail) ist - wie kann ich die GROUP BY einstellen damit arbeiten
Ich bin ziemlich sicher, ich werde mit einem UNION am Ende SELECT hier (dh tun FName, HPhone auf der einen und FName, EMail auf der anderen Seite und die Ergebnisse zusammenführen) - aber ich würde gerne wissen, ob jemand kennt einen einfacheren Weg, es zu tun.
Vielen Dank im Voraus für jede Hilfe.
Scott in Maine
Lösung
Keine dieser Antworten ist richtig. Quassnoi der ist ein anständiger Ansatz, aber Sie werden in der Ausdrücken „qo.id> dup.id“ und „di.chainid Das wesentliche Problem ist ein disjunctive Zustand mit einer Gruppierung, die auf die Möglichkeit, zwei Aufzeichnungen führt über einen Zwischen bezogen ist, obwohl sie nicht direkt zuordenbar. z erklärten Sie, diese Aufzeichnungen sollten alle gruppiert werden: (1) John 555-00-00 john@example.com (2) John 555-00-01 john@example.com (3) John 555-00-01 john-other@example.com Sie sehen, dass # 1 und # 2 zuordenbar sind, als # 2 und # 3, sind aber eindeutig # 1 und # 3 sind nicht direkt zuordenbar als Gruppe. Dies legt fest, dass eine rekursive oder iterative Lösung die einzig mögliche Lösung ist. Also, Rekursion ist nicht praktikabel, da Sie leicht in einer Looping-Situation enden können. Dies ist, was Quassnoi versucht, mit seiner ID Vergleichen zu vermeiden, aber er brach so den Algorithmus zu tun. Sie könnten versuchen, die Ebenen der Rekursion zu begrenzen, aber Sie können dann nicht alle Beziehungen vervollständigen, und Sie werden Schleifen noch potenziell auf sich selbst werden folgende zurück, um zu hohe Datengröße und unerschwinglich Ineffizienz führt. Die beste Lösung ist ITERATIVE: Starten Sie ein Ergebnis gesetzt, indem jede ID als eindeutige Gruppen-ID-Tagging, und dann drehen durch das Ergebnis eingestellt und aktualisieren Sie es, IDs in die gleiche einzigartige Gruppe kombiniert ID, wie sie auf dem disjunktiven Zustand entsprechen. Wiederholen Sie den Vorgang auf dem aktualisierten Satz jedes Mal, bis keine weiteren Aktualisierungen vorgenommen werden. Ich werde Beispielcode für diese erstellen bald.
Andere Tipps
Bevor ich etwas raten kann, muss ich die Antwort auf diese Frage wissen:
name phone email
John 555-00-00 john@example.com
John 555-00-01 john@example.com
John 555-00-01 john-other@example.com
Was COUNT(*)
Sie für diese Daten wollen?
Update:
Wenn Sie nur wissen wollen, dass ein Datensatz hat alle Duplikate, verwenden Sie diese:
WITH q AS (
SELECT 1 AS id, 'John' AS name, '555-00-00' AS phone, 'john@example.com' AS email
UNION ALL
SELECT 2 AS id, 'John', '555-00-01', 'john@example.com'
UNION ALL
SELECT 3 AS id, 'John', '555-00-01', 'john-other@example.com'
UNION ALL
SELECT 4 AS id, 'James', '555-00-00', 'james@example.com'
UNION ALL
SELECT 5 AS id, 'James', '555-00-01', 'james-other@example.com'
)
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 die doppelte Kette gestartet.
Diese Abfrage wählen Sie alle Einträge zusammen mit dem Spezialgebiet, chainid
, dass die doppelte Kette gestartet zeigt an, wo.
WITH q AS (
SELECT 1 AS id, 'John' AS name, '555-00-00' AS phone, 'john@example.com' AS email
UNION ALL
SELECT 2 AS id, 'John', '555-00-01', 'john@example.com'
UNION ALL
SELECT 3 AS id, 'John', '555-00-01', 'john-other@example.com'
UNION ALL
SELECT 4 AS id, 'James', '555-00-00', 'james@example.com'
UNION ALL
SELECT 5 AS id, 'James', '555-00-01', 'james-other@example.com'
),
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
GROUP BY nicht unterstützt oder - es ist implizit AND und muß jede nicht-Aggregator in der Auswahlliste enthält
.Ich nehme an, Sie auch eine eindeutige ID integer als Primärschlüssel für diese Tabelle haben. Wenn Sie dies nicht tun, ist es eine gute Idee, einen zu haben, für diesen Zweck und viele andere.
Finden diese Duplikate durch eine Selbstverknüpfung:
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 doppelter Kombinationen - wenn Sie nur eine Zählung wollen zusammen mit jeder einzigartigen Kombination, zählt die Zeilen von der „linken“ Seite gruppiert:
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 ein Union -. Aber ich denke, es ist eine gute Art und Weise veranschaulicht über Duplikate des Denkens
Projekt der gewünschte Transformation zuerst aus einer Tabelle abgeleitet, haben 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
Stellen Sie Ihre ‚ODER‘ Operation wie in der abgeleiteten Tabelle Projekt-Liste benötigt wird.
Ich weiß, diese Antwort wird für die Verwendung der temporären Tabelle kritisiert werden, aber es wird funktionieren 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,'') )
Versuchen Sie folgendes:
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