How to get those nested categories who have the products - in single query?

时间:2017-08-05 10:54:23

标签: php mysql sql yii2

I have tables like this.

Categories (id[PK], name, parentid);
Product(prid[PK], product_name, product_price);
ProductCategories(id[PK], prid[FK], catid[FK]);

One product belongs to multiple categories.

I have a scenario, where I will get one catid from a user and I have to get the products belong to that category. Also at the same time, I have to get the subcategories of that category (if any) and get the products of sub categories too. Getting categories and its subcategories is easy task - with self join.

But I have to check that those categories having the products tagged or not. (Means if there is no product tagged under that category/subcategories then neglect that category)

e.g.

Automobile (0 products)
    Two Wheelers (0 products)
        Mopeds (2 products)
        Bikes (5 products)
        Sport Bikes (0 products)

    Four Wheelers (0 products)
        Convertible (0 products)
        SUV (4 products)
        TUV (2 products)

    Tyres (0 products)

So I want the result like (those categories/subcategories don't have products I have to remove those).

Automobile
    Two Wheelers
        Mopeds
        Bikes
    Four Wheelers
        SUV
        TUV

I am doing this thing by looping over the categories. Can I do this in a single query?

Some code :

$rows = (new \yii\db\Query())
    ->select(["COUNT( * ) AS prodcount",'c1.parentid', "GROUP_CONCAT(c1.id, ':', c1.name) as catid"])
    ->from('category c1')
    ->join('inner join','category c2','c1.id=c2.id')
    ->where(['not in','c1.parentid','0'])
    ->andWhere(['!=','c1.parentid',1])
    ->andWhere(array('c1.status'=>1))
    ->andWhere(array('c2.status'=>1))
    ->groupBy('c1.parentid')
    ->orderBy('prodcount DESC')
    ->all();

$result=array();

foreach ($rows as $r)
{    
    $cats= explode(":",$r['catid']);

    if( $this->hasProducts($cats[0]))
    {
        if($r['parentid']!=1)
        {                  
              $pnm=  \backend\models\Category::find()->select('name')->where(['id'=>$r['parentid']])->one();                                    
              $result['parent']=$r['parentid'].":".$pnm['name'];
        }
        else{                  
               $result['parent']=$r['parentid'].":".'Main';
        }

        $result['catid']=$r['catid'];   
        $this->cat[$result['parent']]=$result['catid'];
    }
}

Here I am checking that category has at least a product or not?

public function hasProducts($catid)
{
    $hasProducts=false;
    $allCats= array();
    $allCats = $this->getAllChildren($catid);

    if($allCats!== NULL && !empty($allCats) && sizeOf($allCats)>0)
    {
        $cats = implode(",",$allCats);
        $prodcatquery = (new \yii\db\Query())
                        ->from('product_categories pc')
                        ->where("pc.catid in ($cats)");
        $products= $prodcatquery->all(); 

        if (sizeOf($products)>0)
        {
            $hasProducts=true;
        }
    }

    return $hasProducts;
}

Getting all subcategories of that category

public function getAllChildren($catid)
{
    $cats=$catid;
    $allcats=array();
    $currentcats=array();
    array_push($allcats, $catid);
    $intialquery = (new \yii\db\Query())
                    ->select(['id'])
                    ->from('category')
                    ->where("parentid in ($cats)");
    $catidreturned = $intialquery->all();              

    $i=0;        
    while(sizeOf($catidreturned ) > 0 && $i <=3 )
    {

        foreach ($catidreturned as $categoryid )
        {
           array_push( $allcats,$categoryid['id']);
           array_push( $currentcats,$categoryid['id']);
        }
        $cats= implode(',', $currentcats);
        $intialquery1 = (new \yii\db\Query())
                    ->select(['id'])
                    ->from('category')
                     ->where("parentid in ($cats)");
        $catidreturned = $intialquery1->all(); 
        $currentcats=array();       
        $i++;
    }

    return $allcats;
}

Question: I am doing this thing by looping over the categories. Can I do this in a single query?

2 个答案:

答案 0 :(得分:0)

select products from categories where category=1 or subcategory in(select subcategory from sub_categories where id=1)

modify this depending on what is your tables looks like

答案 1 :(得分:0)

SELECT *
FROM (
  SELECT
      CASE
         WHEN c2_id IS NULL THEN 1
         WHEN c3_id IS NULL THEN 2
         ELSE 3
      END AS level,
      CASE
          WHEN c2_id IS NULL THEN c1_id
          WHEN c3_id IS NULL THEN c2_id
          ELSE c3_id
      END AS id,
      CASE
        WHEN c2_id IS NULL THEN c1_name
        WHEN c3_id IS NULL THEN c2_name
        ELSE c3_name
      END AS name,
      CASE
          WHEN c2_id IS NULL THEN c1_own_products_count
          WHEN c3_id IS NULL THEN c2_own_products_count
          ELSE c3_own_products_count
      END AS own_products_count,
      CASE
          WHEN c2_id IS NULL THEN c1_nested_products_count
          WHEN c3_id IS NULL THEN c2_nested_products_count
          ELSE c3_nested_products_count
      END AS nested_products_count
  FROM (
      SELECT
         -- Level 1
         c1.id AS c1_id,
         c1.name AS c1_name,
         COUNT(DISTINCT c1p.id) AS c1_own_products_count,
         COUNT(DISTINCT c1p.id)+
         COUNT(DISTINCT c2p.id)+
         COUNT(DISTINCT c3p.id) AS c1_nested_products_count,
         -- Level 2
         c2.id AS c2_id,
         c2.name AS c2_name,
         COUNT(DISTINCT c2p.id) AS c2_own_products_count,
         COUNT(DISTINCT c2p.id)+
         COUNT(DISTINCT c3p.id) AS c2_nested_products_count,
         -- Level 3
         c3.id AS c3_id,
         c3.name AS c3_name,
         COUNT(DISTINCT c3p.id) AS c3_own_products_count,
         COUNT(DISTINCT c3p.id) AS c3_nested_products_count
      FROM Categories c1
      LEFT JOIN Categories c2 ON(c2.parentid = c1.id)
      LEFT JOIN Categories c3 ON(c3.parentid = c2.id)
      LEFT JOIN ProductCategories c1p ON(c1p.catid=c1.id)
      LEFT JOIN ProductCategories c2p ON(c2p.catid=c2.id)
      LEFT JOIN ProductCategories c3p ON(c3p.catid=c3.id)
      GROUP BY c1.id, c2.id, c3.id
      WITH ROLLUP -- This will generate subtotals for level 1 and 2
  ) AS tree
  WHERE c1_id IS NOT NULL -- Skip the row with total product count.
) AS list
WHERE nested_products_count = 0 -- Skip categories with no nested products