どうやら、他の回答に対する賛成票に基づいて、これにはさらに説明が必要です。例 (MySQL は手元にあるので、MySQL で行いましたが、原則はどの SQL ダイアレクトにも共通です):
CREATE TABLE Blah (
ID INT PRIMARY KEY,
SomeText VARCHAR(30),
ParentID INT
)
INSERT INTO Blah VALUES (1, 'One', 0);
INSERT INTO Blah VALUES (2, 'Two', 0);
INSERT INTO Blah VALUES (3, 'Three', 1);
INSERT INTO Blah VALUES (4, 'Four', 1);
INSERT INTO Blah VALUES (5, 'Five', 4);
左結合バージョン:
SELECT a.ID, a.SomeText, COUNT(1)
FROM Blah a
JOIN Blah b ON a.ID= b.ParentID
GROUP BY a.ID, a.SomeText
違う。子がない場合は無視します。
左外部結合:
SELECT a.ID, a.SomeText, COUNT(1)
FROM Blah a
LEFT OUTER JOIN Blah b ON a.ID= b.ParentID
GROUP BY a.ID, a.SomeText
間違っており、その理由はやや微妙です。 行COUNT(1)
をカウントしますが、カウントしません。したがって、上記は間違っていますが、これは正しいです:NULL
COUNT(b.ID)
SELECT a.ID, a.SomeText, COUNT(b.ID)
FROM Blah a
LEFT OUTER JOIN Blah b ON a.ID= b.ParentID
GROUP BY a.ID, a.SomeText
相関サブクエリ:
SELECT ID, SomeText, (SELECT COUNT(1) FROM Blah WHERE ParentID= a.ID) ChildCount
FROM Blah a
また、正しい。
わかりました。では、どちらを使用しますか? 計画は多くのことを教えてくれます。サブクエリと左結合の問題は古い問題であり、ベンチマークしないと明確な答えはありません。したがって、いくつかのデータが必要です。
<?php
ini_set('max_execution_time', 180);
$start = microtime(true);
echo "<pre>\n";
mysql_connect('localhost', 'scratch', 'scratch');
if (mysql_error()) {
echo mysql_error();
exit();
}
mysql_select_db('scratch');
if (mysql_error()) {
echo mysql_error();
exit();
}
$count = 0;
$limit = 1000000;
$this_level = array(0);
$next_level = array();
while ($count < $limit) {
foreach ($this_level as $parent) {
$child_count = rand(0, 3);
for ($i=0; $i<$child_count; $i++) {
$count++;
query("INSERT INTO Blah (ID, SomeText, ParentID) VALUES ($count, 'Text $count', $parent)");
$next_level[] = $count;
}
}
$this_level = $next_level;
$next_level = array();
}
$stop = microtime(true);
$duration = $stop - $start;
$inserttime = $duration / $count;
echo "$count users added.\n";
echo "Program ran for $duration seconds.\n";
echo "Insert time $inserttime seconds.\n";
echo "</pre>\n";
function query($query) {
mysql_query($query);
if (mysql_error()) {
echo mysql_error();
exit();
}
}
?>
この実行中にメモリ (32M) を使い果たしたので、最終的に 876,109 レコードしかありませんでしたが、何とかなるでしょう。後で、Oracle と SQL Server をテストするときに、まったく同じデータ セットを取得し、それを Oracle XE と SQL Server Express 2005 にインポートします。
別の投稿者が、クエリの周りにカウント ラッパーを使用するという問題を提起しました。その場合、オプティマイザがサブクエリを実行しない可能性があることを彼は正しく指摘しました。MySQL はそれほどスマートではないようです。オラクルは。SQL Serverも同様のようです。
したがって、データベースとクエリの組み合わせごとに 2 つの図を引用しますSELECT COUNT(1) FROM ( ... )
。
設定:
- PremiumSoft Navicat を使用する MySQL 5.0 (
LIMIT 10000
クエリ内);
- Microsoft SQL Server Management Studio Express を使用する SQL Server Express 2005。
- PL/SQL Developer 7 を使用する Oracle XE (10,000 行に制限)。
左外部結合:
SELECT a.ID, a.SomeText, COUNT(b.ID)
FROM Blah a
LEFT OUTER JOIN Blah b ON a.ID= b.ParentID
GROUP BY a.ID, a.SomeText
- MySQL: 5.0: 51.469 秒 / 49.907 秒
- SQL サーバー: 0 (1) / 9 秒(2)
- オラクル XE: 1.297 秒 / 2.656 秒
(1) ほぼ瞬時 (異なる実行パスを確認)
(2) 10,000 行ではなく、すべての行を返すことを考えると印象的
実際のデータベースの価値を示すだけです。また、SomeText フィールドを削除すると、MySQL のパフォーマンスに大きな影響がありました。また、10000 の制限がある場合と、MySQL で制限がない場合との間に大きな違いはありませんでした (パフォーマンスが 4 ~ 5 倍向上します)。オラクルは、PL/SQL Developer が 100M のメモリ使用量に達したときに barfed したという理由だけでそれを持っていました。
相関サブクエリ:
SELECT ID, SomeText, (SELECT COUNT(1) FROM Blah WHERE ParentID= a.ID) ChildCount
FROM Blah a
- MySQL: 8.844 秒 / 11.10 秒
- SQL サーバー: 0 秒 / 6 秒
- オラクル: 0.046秒 / 1.563秒
したがって、MySQL は 4 ~ 5 倍優れており、Oracle は約 2 倍高速であり、SQL Server はほぼ間違いなくわずかに高速です。
ポイントはそのままです。相関サブクエリのバージョンは、すべてのケースで高速です。
相関サブクエリのもう 1 つの利点は、構文が簡潔で拡張が容易なことです。これは、他のテーブルの束でカウントを行いたい場合、それぞれを別の選択項目としてきれいに簡単に含めることができることを意味します。例: 請求書が未払い、期限切れ、または支払いのいずれかである請求書に対する顧客の記録を想像してみてください。簡単なサブクエリを使用すると、次のようになります。
SELECT id,
(SELECT COUNT(1) FROM invoices WHERE customer_id = c.id AND status = 'UNPAID') unpaid_invoices,
(SELECT COUNT(1) FROM invoices WHERE customer_id = c.id AND status = 'OVERDUE') overdue_invoices,
(SELECT COUNT(1) FROM invoices WHERE customer_id = c.id AND status = 'PAID') paid_invoices
FROM customers c
集約版はもっと醜いです。
ここで、サブクエリが常に集計結合よりも優れていると言っているわけではありませんが、多くの場合、サブクエリをテストする必要があります。データ、そのデータのサイズ、および RDBMS ベンダーによっては、その違いが非常に大きくなる可能性があります。