5

そこに関連する例が見つからないようです。

テーブルのサブセットを返そうとしています。そのテーブルの各行について、子の数を確認し、その数を結果セットの一部として返したいと考えています。

親テーブルの列: PK_ID、Column1、Column2、FK1

結果セット内の FK1 ごとに、child_table から count(*) を選択します。

最終結果セット

3, col1text, col2text, 1(子)
5, col1texta, col2texta, 2(子)
6, col1textb, col2textb, 0(子)
9, col1textc, col2textc, 4(子)

別のクエリで結果セットの列を参照し、それらを再度結合する最良の方法に苦労しています。T-sql の使用

4

5 に答える 5

13

どうやら、他の回答に対する賛成票に基づいて、これにはさらに説明が必要です。例 (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)をカウントしますが、カウントしません。したがって、上記は間違っていますが、これは正しいです:NULLCOUNT(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 ベンダーによっては、その違いが非常に大きくなる可能性があります。

于 2009-01-26T02:06:16.310 に答える
4

これがあなたがやろうとしていることだと思います:

SELECT P.PK_ID, P.Column1, P.Column2, COUNT(C.PK_ID)
FROM
    Parent P
    LEFT JOIN Child C ON C.PK_ID = P.FK1
GROUP BY
    P.PK_ID, P.Column1, P.Column2
于 2009-01-26T02:06:23.033 に答える
2

@cletus が間違っている理由の説明。

まず、研究を行うための小道具。

第二に、あなたはそれを間違っています。

説明:

元のクエリ:

EXPLAIN
SELECT ID, (SELECT COUNT(1) FROM Blah WHERE ParentID= a.ID) as ChildCount
FROM Blah a

結果:

    「何とかaのSeqスキャン(コスト= 0.00..145180063607.45行= 2773807幅= 4)」
    「サブプラン」
    " -> 集約 (コスト=52339.61..52339.63 行=1 幅=0)"
    " -> 何とか Seq スキャン (コスト = 0.00..52339.59 行 = 10 幅 = 0)"
    「フィルター: (parentid = $0)」

"select count(1)" でラップするとどうなるか:

EXPLAIN SELECT count(1) FROM (
SELECT ID, (SELECT COUNT(1) FROM Blah WHERE ParentID= a.ID) as ChildCount
FROM Blah a) as bar
    「集計 (コスト=52339.59..52339.60 行=1 幅=0)」
    " - >何とかaのSeqスキャン(コスト= 0.00..45405.07行= 2773807幅= 0)"

違いに気づきましたか?

オプティマイザは、サブクエリを実行する必要がないことを確認できるほどスマートです。したがって、相関サブクエリが高速というわけではありません。それは、それらを行わないことが速いということです:-)。

残念ながら、最初のスキャンでは結果の数が事前に決定されないため、左外部結合に対して同じことを行うことはできません。

教訓 #1: クエリ プランは非常に多くのことを教えてくれます。実験計画が不十分だと、問題が発生します。

教訓 #1.1:結合を行う必要がない場合は、絶対にしないでください。

約 270 万件のクエリのテスト データセットを作成しました。

左外部結合 (ラッパーなし) は、ラップトップで 171,757 ミリ秒実行されました。

相関サブクエリ...完了したら更新します。700K ミリ秒で、まだ実行中です。

教訓 2:誰かがクエリ プランを見るように言い、それがアルゴリズムの順序の違いを示していると主張するときは、クエリ プランを見てください。

于 2009-01-26T04:35:04.777 に答える
1

MySQL の親 ID にインデックスを追加しようとしたことがありますか。実行時間が大幅に改善されると確信しています。テストはしていませんが、MySQL はすべての行を調べてカウントを決定すると思います。つまり、59 秒間で 100 億から 400 億 (テーブル内の行数 * 10000) のルックアップが行われます。

SQL Server と Oracle がその場でインデックスを作成するとします。もしそうなら、それはわずか100万から400万です。

于 2010-09-03T05:50:02.723 に答える
0

すべてのクエリは、親子ノードが入力される順序が連続していることを前提としています。最初のノードの 1 つからの子が最後に入力され、その ID または PK がより高い場合、クエリは機能しません。

于 2010-02-02T06:17:25.210 に答える