Domanda
Ho un tavolo con due colonne, diciamo FirstName e lastName. Ho bisogno di prendere un altro tavolo, che per ogni coppia di firstname del primo contiene un conteggio del comune lastname.
è anche questo fattibile da fare in SQL?
Ci sono molti più un lastname unici rispetto a quello del nome, se ciò influisce sull'efficienza della query.
Un esempio di giocattolo, ingresso:
FirstName, LastName
John, Smith
John, Doe
Jane, Doe
.
Uscita:
FirstName1, FirstName2, CommonLastNames
John, John, 2
John, Jane, 1
Jane, Jane, 1
Jane, John, 1
.
Poiché questa relazione è riflessiva e simmetrica, va bene se il risultato è solo uno dei triangoli (ad esempio, quello sopra la diagonale).
Soluzione
Ho intenzione di utilizzare MS SQL Server per farlo da quando ho una copia in mano.Credo che la maggior parte delle major lo farebbe allo stesso modo.
Prima una tabella di esempio, con i dati.Io uso una variabile da tavolo ma è la stessa per qualsiasi sapore del tavolo.
declare @t table (FirstName char(10), LastName char(10));
insert @t(FirstName,LastName)
values ('John','Smith'),('John','Doe'),('Jane','Doe');
.
Puoi ottenere tutte le coppie facendo un self-join:
select
a.FirstName, a.LastName, b.FirstName, b.LastName
from @t as a
cross apply @t as b;
.
L'utilizzo di CROSS APPLY
evita di dover passare attraverso i cerchi per trovare una condizione di join per una clausola ON
.
Avanti hai bisogno di qualcosa da contare.È qui che arriva l'affermazione CASE
. Il caso restituisce un valore intero per coppia di nomi, che è ciò che viene contato.(Se sto leggendo correttamente la tua domanda tu vuoi dove si abbinano gli oculari, quindi è il confronto che ho. Speriamo che sia ovvio come modificarlo se sbaglio.)
select
...
case
when a.LastName = b.LastName then 1
else 0
end
...etc.
.
Aggiungi in un SUM()
e GROUP BY
e ottieni la risposta:
select
a.FirstName,
b.FirstName,
sum(
case
when a.LastName = b.LastName then 1
else 0
end
) as CommonLastNames
from @t as a
cross apply @t as b
group by a.FirstName, b.FirstName;
. Altri suggerimenti
Devo ammettere che la mia domanda era un po 'imperfetta. Quello che avevo davvero bisogno non era "per ogni coppia di firstname dal primo contiene un conteggio del comune lastname".In effetti, non mi interessa le coppie con conteggi zero.
Quando la domanda è corretta, la soluzione diventa molto più veloce.
Dato l'ingresso:
create local temp table t (FirstName char(10), LastName char(10)) ON COMMIT PRESERVE ROWS;
insert into t(FirstName,LastName) values ('John','Smith');
insert into t(FirstName,LastName) values ('John','Doe');
insert into t(FirstName,LastName) values ('Jane','Doe');
.
Per la domanda originale, la soluzione è o (n ^ 2) (perché la domanda insiste su "ogni coppia"):
select a.FirstName, b.FirstName,
sum(case when a.LastName = b.LastName then 1 else 0 end) CommonNames
from t a, t b group by 1, 2;
.
Se è OK saltare i conteggi zero, allora un auto-joy-join sul lastname funziona molto più velocemente (supponendo che i dati siano sufficientemente sparse):
select a.FirstName, b.FirstName,
count(*) CommonNames from t a
join t b using (LastName) group by 1, 2;
.
Mi chiedo ancora come ho perso questa soluzione banale.
doh!Ecco un modo migliore:
SELECT city_a, city_b, COUNT(*)
FROM (
SELECT a.city city_a,
a.state,
b.city city_b
FROM us a
CROSS JOIN us b
WHERE a.state = b.state
AND a.city < b.city
) x
GROUP BY city_a, city_b
ORDER BY 3 DESC;
.
Uscita:
+-----------+-------------+----------+
| city_a | city_b | COUNT(*) |
+-----------+-------------+----------+
| Lebanon | Springfield | 5 |
| Bedford | Franklin | 4 | -- as shown in previous 'answer'
| Franklin | Lebanon | 4 |
| Franklin | Hudson | 4 |
| Franklin | Salem | 4 |
| Hudson | Salem | 4 |
| Salem | Springfield | 4 |
| Clinton | Columbia | 4 |
| Auburn | Fairfield | 3 |
| Auburn | Madison | 3 |
...
(2.63 sec) -- for all 4175 cities in `us`.
.
Verifica sanitaria al primo articolo:
mysql> SELECT city, state FROM us WHERE city IN ('Lebanon', 'Springfield');
+-------------+-------+
| city | state |
+-------------+-------+
| Springfield | FL |
| Springfield | IL |
| Lebanon | IN |
| Springfield | MA |
| Lebanon | ME |
| Lebanon | MO |
| Springfield | MO |
| Lebanon | NH |
| Springfield | NJ |
| Lebanon | OH |
| Springfield | OH |
| Lebanon | OR |
| Springfield | OR |
| Lebanon | PA |
| Springfield | PA |
| Lebanon | TN |
| Springfield | TN |
| Springfield | VA |
| Springfield | VT |
+-------------+-------+
19 rows in set (0.00 sec)
.
I valori di stato del gestore principale% mostrano che ha fatto un sacco di lavoro, ma non del tutto o (n * n) (probabilmente perché la croce join è solo uno stato alla volta):
| Handler_read_key | 4176 |
| Handler_read_next | 667294 |
| Handler_read_rnd | 1742 |
| Handler_read_rnd_next | 701964 |
| Handler_update | 1731 |
| Handler_write | 703693 |
.
Extrapolating a milioni di righe - probabilmente ci vorrà giorni.
Questa è stata una sfida interessante.Utilizzando un elenco di città statunitensi, ho inventato questa soluzione (in MySQL):
SELECT city_a, city_b,
COUNT(DISTINCT state)
FROM (
( SELECT a.city city_a,
b.city city_b,
a.state -- This line differs
FROM us a
CROSS JOIN us b
WHERE a.state = b.state
AND a.city != b.city -- Added (to avoid noise)
AND a.city < 'M' -- to speed up test
AND b.city < 'M'
)
UNION ALL
( SELECT a.city city_a,
b.city city_b,
b.state -- This line differs
FROM us a
CROSS JOIN us b
WHERE a.state = b.state
AND a.city != b.city -- Added (to avoid noise)
AND a.city < 'M' -- to speed up test
AND b.city < 'M'
)
) ab
GROUP BY 1, 2
HAVING COUNT(DISTINCT state) > 1
ORDER BY COUNT(DISTINCT state) desc
.
INDEX(state, city)
aiuta con le prestazioni.
I risultati:
+----------+------------+-----------------------+
| city_a | city_b | COUNT(DISTINCT state) |
+----------+------------+-----------------------+
| Franklin | Bedford | 4 |
| Lebanon | Franklin | 4 |
| Franklin | Lebanon | 4 |
| Hudson | Franklin | 4 |
| Columbia | Clinton | 4 |
| Clinton | Columbia | 4 |
| Franklin | Hudson | 4 |
| Bedford | Franklin | 4 |
| Lebanon | Farmington | 3 |
| Hanover | Kingston | 3 |
...
(25.17 sec)
.
Potrebbe aver preso 4 volte più a lungo per includere l'intero alfabeto.C'erano solo righe 4K nel tavolo, quindi questo è non un compito veloce.
"Prova" dei risultati: mysql> Seleziona città, stato da noi dove city in ('Franklin', 'Bedford');
+----------+-------+
| city | state |
+----------+-------+
| Bedford | IN |
| Franklin | IN |
| Bedford | MA |
| Franklin | MA |
| Bedford | NH |
| Franklin | NH |
| Bedford | OH |
| Franklin | OH |
| Franklin | TN |
| Bedford | TX |
| Franklin | WI |
+----------+-------+
11 rows in set (0.00 sec)
.