Recursive call or SQL Parent / Child join?

I can not find a suitable example.

I am trying to return a subset of a table, and for each row in this table I want to check how many children she has and return this number as part of the result set.

Columns of parent tables: PK_ID, Column1, Column2, FK1

For each FK1 in the result set, select count (*) from child_table.

Final result set

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

I struggle to refer to the column in the result set in another query, and then combine them again. Using T-sql

+5
source share
5 answers

Well, apparently, based on the priorities for another answer, this requires further explanation. Example (runs with MySQL, because it’s convenient for me, but the principle is universal for any SQL dialect):

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

Left attachment version:

SELECT a.ID, a.SomeText, COUNT(1)
FROM Blah a
JOIN Blah b ON a.ID= b.ParentID
GROUP BY a.ID, a.SomeText

Wrong. Ignores the case without children.

Left outer join:

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

Wrong, and the reason is a little subtle. COUNT(1)counts NULLlines, while COUNT(b.ID)not. So this is wrong, but it is correct:

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

Associated Subquery:

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

Also correct.

Ok, so what to use? Plans only tell you. The question of subqueries and left joins is old and there is no clear answer without comparing it. Therefore, we need some data:

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

, count . , . MySQL, , . Oracle. SQL Server .

, : SELECT COUNT(1) FROM ( ... ), - raw.

:

  • MySQL 5.0 PremiumSoft Navicat (LIMIT 10000 );
  • SQL Server Express 2005 Microsoft SQL Server Management Studio Express;
  • Oracle XE PL/SQL Developer 7 ( 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) , , , 10000

. , SomeText MySQL. 10000 MySQL ( 4-5 ). Oracle , PL/SQL Developer , 100- .

:

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
  • Oracle: 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 .

+13

, , :

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
+4

, @cletus .

-, .

-, .

:

:

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

:

    "Seq Scan on blah a  (cost=0.00..145180063607.45 rows=2773807 width=4)"
    "  SubPlan"
    "    ->  Aggregate  (cost=52339.61..52339.63 rows=1 width=0)"
    "          ->  Seq Scan on blah  (cost=0.00..52339.59 rows=10 width=0)"
    "                Filter: (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
    "Aggregate  (cost=52339.59..52339.60 rows=1 width=0)"
    "  ->  Seq Scan on blah a  (cost=0.00..45405.07 rows=2773807 width=0)"

?

, , . , ; : -).

, , .

№1:. . .

№ 1.1: , , .

2,7 .

- - 171 757 .

... , , 700K .

№ 2: - , ... .

+2

- MySQL. , . , , MySQL , . , 59 10-40 ( * 10000).

, SQL Server Oracle " ". , 1 4 .

+1
source

All of your queries assume that the input order of the parent child nodes is sequential. If a child from one of the first nodes is entered at the end, and its identifier or PC is higher, the request does not work.

0
source

All Articles