5

我似乎找不到相关的例子。

我试图返回一个表的子集,对于该表中的每一行,我想检查它有多少个孩子,并将该数字作为结果集的一部分返回。

父表列:PK_ID、Column1、Column2、FK1

对于结果集中的每个 FK1,从 child_table 中选择 count(*)。

最终结果集

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

我正在努力寻找在另一个查询中引用结果集中的列的最佳方法,然后再次将它们连接在一起。使用 T-sql

4

5 回答 5

13

好的,显然,基于对另一个答案的支持,这需要进一步解释。示例(使用 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 似乎也是如此。

因此,我将为每个数据库查询组合引用两个数字:第一个包含在 中SELECT COUNT(1) FROM ( ... ),第二个是原始的。

设置:

  • MySQL 5.0 使用 PremiumSoft Navicat(LIMIT 10000查询中);
  • SQL Server Express 2005 使用 Microsoft SQL Server Management Studio Express;
  • 使用 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.469s / 49.907s
  • SQL Server: 0 (1) / 9s (2)
  • Oracle XE: 1.297s / 2.656s

(1) 几乎是瞬时的(确认不同的执行路径)
(2) 令人印象深刻的是它返回所有行,而不是 10,000

只是去展示一个真实数据库的价值。此外,删除 SomeText 字段对 MySQL 的性能有重大影响。此外,10000 的限制与 MySQL 没有它之间没有太大区别(性能提高了 4-5 倍)。Oracle 之所以拥有它,是因为 PL/SQL Developer 在内存使用量达到 100M 时大吃一惊。

相关子查询:

SELECT ID, SomeText, (SELECT COUNT(1) FROM Blah WHERE ParentID= a.ID) ChildCount
FROM Blah a
  • MySQL: 8.844s / 11.10s
  • SQL Server: 0s / 6s
  • 甲骨文: 0.046s / 1.563s

所以 MySQL 要好 4 到 5 倍,Oracle 的速度大约是前者的两倍,而 SQL Server 可以说只快一点点。

重点仍然存在:相关子查询版本在所有情况下都更快。

相关子查询的另一个优点是它们在语法上更清晰且更易于扩展。我的意思是,如果您想在一堆其他表中进行计数,每个表都可以干净轻松地作为另一个选择项包含在内。例如:想象一下客户对发票的记录,其中这些发票要么未付,要么逾期,要么已付。使用简单的子查询:

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

结果:

    “在 blah a 上进行 Seq 扫描(成本=0.00..145180063607.45 行=2773807 宽度=4)”
    “子计划”
    “ -> 聚合(成本=52339.61..52339.63 行=1 宽度=0)”
    “ -> 对 blah 的 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)”
    “ -> 在 blah a 上进行 Seq 扫描(成本=0.00..45405.07 行=2773807 宽度=0)”

注意到区别了吗?

优化器足够聪明,可以看到它不需要执行子查询。所以并不是相关的子查询很快;就是不这样做很快:-)。

不幸的是,它不能对左外连接做同样的事情,因为结果的数量不是由第一次扫描预先确定的。

第 1 课: 查询计划告诉你很多。糟糕的实验设计会给你带来麻烦。

第 1.1 课:如果您不需要加入,请务必不要。

我创建了一个包含大约 270 万个查询的测试数据集。

左外连接(没有包装器)在我的笔记本电脑上运行了 171,757 毫秒。

相关子查询...完成后我会更新,我在 700K ms 并且它仍在运行。

第 2 课:当有人告诉您查看查询计划并声称它显示了算法顺序的差异时……查看查询计划。

于 2009-01-26T04:35:04.777 回答
1

您是否曾经尝试为 MySQL 的父 ID 添加索引。我很确定执行时间会大大改善。尚未测试,但我会说 MySQL 会遍历所有行来确定计数。这意味着它在这 59 秒内进行了 10 - 400 亿(表中的行数 * 10000)的查找。

假设 SQL Server 和 Oracle 即时创建索引。如果他们这样做,那将只有 1 到 400 万。

于 2010-09-03T05:50:02.723 回答
0

您的查询都假定输入父子节点的顺序是连续的。如果最后输入了来自第一个节点之一的子节点,并且其 ID 或 PK 较高,则查询不起作用。

于 2010-02-02T06:17:25.210 回答