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

È stato utile?

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

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a dba.stackexchange
scroll top