MySQL count all children no matter how many there are

901 Views Asked by At

I am developing a simple system that has unlimited categories and items in each category. For instance there could be categories inside categories etc (Category 1 -> Category 2 -> Category 3) all containing items. I want to display the total items for each category and ALL of their subcategories. I am trying to figure out a loop to do this but basically am coming up short. I am building in PHP/MySQL. My category table schema is category(id, id_parent) id being auto increment and id_parent being whether it resides inside another category (0 is default). My items schema is items(id, id_category). Obviously there are other columns but these are the only ones that matter I believe. Can someone please help me develop a loop structure come up with a total amount of items for the category they are in (counting all subcategories items as well). Here is what I started although I feel it is very, very wrong.

function CountChildDownloads($id_category)
{
global $smcFunc;
$x = array();
$total = 0;

$request = $smcFunc['db_query']('', '
    SELECT COUNT(*) AS items FROM {db_prefix}xld_downloads
    WHERE id_category = '.$id_category.''
);

$total += $request['items'];

$request = $smcFunc['db_query']('', '
    SELECT id FROM {db_prefix}xld_categories
    WHERE id_parent = '.$id_category.''
);

if($smcFunc['db_num_rows']($request) > 0)  {
    while($row = $smcFunc['db_fetch_assoc']($request)) {
        $x[] = $row['id'];
    }
}

foreach ($x as $id)
{
    $y = array();
    $z = 0;

    $request = $smcFunc['db_query']('', '
        SELECT COUNT(*) AS items FROM {db_prefix}xld_downloads
        WHERE id_category = '.$id.''
    );

    $z += $request['items'];

    $request = $smcFunc['db_query']('', '
        SELECT id FROM {db_prefix}xld_categories
        WHERE id_parent = '.$id.''
    );

    if($smcFunc['db_num_rows']($request) > 0)  {
        while($row = $smcFunc['db_fetch_assoc']($request)) {
            $y[] = $row['id'];
        }
    }

    while (count($y) > 0)
    {
        $id_y = $y[0];

        $request = $smcFunc['db_query']('', '
            SELECT id FROM {db_prefix}xld_categories
            WHERE id_parent = '.$id_y.''
        );

        if($smcFunc['db_num_rows']($request) > 0)  {
            while($row = $smcFunc['db_fetch_assoc']($request)) {
                $y[] = $row['id'];
            }
        }

        $request = $smcFunc['db_query']('', '
            SELECT COUNT(*) AS items FROM {db_prefix}xld_downloads
            WHERE id_category = '.$id_y.''
        );

        $z += $request['items'];

        unset($y[0]);
        array_values($y);
    }

    $total += $z;
}

return $total;
}

The $smcFunc is just the systems way to do queries. It is a MySQL backend.

Working function (will need to update to standard MySQL calls if not using SMF) Thanks Lucas.

function CountChildDownloads($id_category)
{
global $smcFunc;
$x = array();
$total = array();
$total['downloads'] = 0;
$total['views'] = 0;

$request = $smcFunc['db_query']('', '
    SELECT views FROM {db_prefix}xld_downloads
    WHERE id_category = '.$id_category.''
);

if($smcFunc['db_num_rows']($request) > 0)  {
    while($row = $smcFunc['db_fetch_assoc']($request)) {
        $total['downloads']++;
        $total['views'] += $row['views'];
    }
}

$request = $smcFunc['db_query']('', '
    SELECT id FROM {db_prefix}xld_categories
    WHERE id_parent = '.$id_category.''
);

if($smcFunc['db_num_rows']($request) > 0)  {
    while($row = $smcFunc['db_fetch_assoc']($request)) {
        $x[] = $row['id'];
    }
}

foreach ($x as $id)
{
    $y = array();
    $z = 0;
    $w = 0;

    $request = $smcFunc['db_query']('', '
        SELECT views FROM {db_prefix}xld_downloads
        WHERE id_category = '.$id.''
    );

    if($smcFunc['db_num_rows']($request) > 0)  {
        while($row = $smcFunc['db_fetch_assoc']($request)) {
            $z++;
            $w += $row['views'];
        }
    }

    $request = $smcFunc['db_query']('', '
        SELECT id FROM {db_prefix}xld_categories
        WHERE id_parent = '.$id.''
    );

    if($smcFunc['db_num_rows']($request) > 0)  {
        while($row = $smcFunc['db_fetch_assoc']($request)) {
            $y[] = $row['id'];
        }
    }

    while (count($y) > 0)
    {
        $id_y = $y[0];

        if (!empty($id_y))
        {
            $request = $smcFunc['db_query']('', '
                SELECT id FROM {db_prefix}xld_categories
                WHERE id_parent = {int:id_parent}',
                array(
                    'id_parent' => $id_y,
                )
            );

            if($smcFunc['db_num_rows']($request) > 0)  {
                while($row = $smcFunc['db_fetch_assoc']($request)) {
                    $y[] = $row['id'];
                }
            }

            $request = $smcFunc['db_query']('', '
                SELECT views FROM {db_prefix}xld_downloads
                WHERE id_category = {int:id_category}',
                array(
                    'id_category' => $id_y,
                )
            );

            if($smcFunc['db_num_rows']($request) > 0)  {
                while($row = $smcFunc['db_fetch_assoc']($request)) {
                    $z++;
                    $w += $row['views'];
                }
            }
        }

        unset($y[0]);
        $y = array_values($y);

    }

    $total['downloads'] += $z;
    $total['views'] += $w;
}

return $total;
}
1

There are 1 best solutions below

5
On BEST ANSWER

(There are numerous approaches to nested loops / queries.. a structure change idea would be to have a separate table that lists all the children of each category.. and make sure that it not only has the immediate children, it also has the sub-children and sub-sub-children... like 1 has child 2, 2 has child 3, 1 has child 3, 3 has child 5, 1 has child 5.. etc..) BUT, for the current situation..

One loop structure could be :

Start resultset. || Query for all category ids where parent = 0. || Add each to array (X). || Close resultset.

For each id in array (X):

  • Establish a new counting variable (z).
  • Establish a new child id array (Y).

  • Start resultset. || Query count * for all items with category = current id x || Add to counting variable (z) || Close resultset.

  • Start resultset. || Query for all category ids where parent = current id x || Add all to child id array (Y). || Close resultset.

  • while child array (Y) length > 0

    • category id y = first item in array (Y)

    • Start resultset. || Query for all category ids where parent = current id y. || Add all to child id array (Y). || Close resultset.

    • Start resultset. || Query count * for all items with category = current id y || Add to counting variable (z) || Close resultset.

    • remove first item from array (Y)

  • continue while loop

  • At this point you have the final item count (z) for category id x... do something with it, then continue with the for loop

End for loop