2つの列を持つテーブルがあります。たとえば、FirstNameとLastNameです。別のテーブルを取得する必要があります。最初のテーブルからFirstNameのペアごとに、共通のLastNameのカウントが含まれています。
これはSQLでも実行できますか?
これがクエリの効率に影響を与える場合、FirstNameよりもはるかにユニークなLastNameがあります。
おもちゃの例、入力:
FirstName, LastName
John, Smith
John, Doe
Jane, Doe
出力:
FirstName1, FirstName2, CommonLastNames
John, John, 2
John, Jane, 1
Jane, Jane, 1
Jane, John, 1
この関係は再帰的で対称的であるため、結果が三角形の1つ(たとえば、対角線の上)である場合は問題ありません。
手元にコピーがあるので、MS SQL Serverを使用してこれを行います。私は、ほとんどすべての専攻が同じようにそれをするだろうと信じています。
最初に、データを含むサンプルテーブル。私はテーブル変数を使用していますが、どのテーブルでも同じです。
_declare @t table (FirstName char(10), LastName char(10));
insert @t(FirstName,LastName)
values ('John','Smith'),('John','Doe'),('Jane','Doe');
_
自己結合を行うと、すべてのペアを取得できます。
_select
a.FirstName, a.LastName, b.FirstName, b.LastName
from @t as a
cross apply @t as b;
_
_CROSS APPLY
_を使用すると、ON
句の結合条件を見つけるためにフープをジャンプする必要がなくなります。
次に、数えるものが必要です。これがCASE
ステートメントの出番です。このケースでは、名のペアごとに整数値が返されます。これがカウントされます。 (私があなたの質問を正しく読んでいる場合、LastNamesがどこで一致するかを知りたいので、それが私の比較です。うまくいけば、私が間違っている場合にこれを変更する方法は明らかです。)
_select
...
case
when a.LastName = b.LastName then 1
else 0
end
...etc.
_
SUM()
と_GROUP BY
_を追加すると、答えが得られます。
_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;
_
私の質問は少し欠陥があったことを認めなければなりません。私が本当に必要だったのは、「最初のFirstNameのすべてのペアに共通のLastNameの数が含まれている」ということではありませんでした。実際、カウントがゼロのペアは気にしません。
質問が修正されると、解決策ははるかに速くなります。
入力が与えられた場合:
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');
元の質問の場合、解決策はO(N ^ 2)です(質問は「すべてのペア」を要求するため)。
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;
ゼロカウントをスキップしても問題がない場合、LastNameでの自己結合は非常に高速に機能します(データが十分にまばらであると想定しています)。
select a.FirstName, b.FirstName,
count(*) CommonNames from t a
join t b using (LastName) group by 1, 2;
どうしてこの簡単な解決策を見逃したのか、私はまだ疑問に思っています。
どー!ここに良い方法があります:
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;
出力:
+-----------+-------------+----------+
| 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`.
最初のアイテムの健全性チェック:
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)
メインのHandler%STATUS値は、それが多くの作業を行ったが、O(N * N)ではないことを示しています(おそらく、CROSS JOINは一度に1つの状態にすぎないためです)。
| Handler_read_key | 4176 |
| Handler_read_next | 667294 |
| Handler_read_rnd | 1742 |
| Handler_read_rnd_next | 701964 |
| Handler_update | 1731 |
| Handler_write | 703693 |
数百万行に外挿-おそらく数日かかります。