如何使用 php/mysql 预加载或类似方法优化 mysql 查询

How to optimize mysql query using php/mysql eager loading or similar approach

我有 2 个 table。 1 - "cpv" ; 2 - "club_offer_cpv".

在 table "cpv" 中,我正在存储 CPV 代码、它们的 ID 和名称。您可以看到 CPV 代码 here。 CPV代码有6个级别,访问link提供的即可看到。

在 CPV table 中有字段:"id" 和 "name"。

以下是一级 CPV 的几个示例(我们可以称它们为根 CPV):

id: 03000000
名称:农、农、渔、林及相关产品

id: 09000000
名称:石油产品、燃料、电力和其他能源

有 45 个根 CPV,他们有自己的 children。从我提供给您的 link 可以看出,他们 ID 的层次结构非常严格。例如,根 CPV 03000000 具有二级 children:

03100000 : 农产品和园艺产品
03200000 : 谷物、土豆、蔬菜tables、水果和坚果
03300000 : 农业、狩猎和渔业产品
03400000 : 林业和伐木产品

现在,对于每个 CPV,都有公司在该领域开展业务(公司正在为该领域提供报价),例如,

03100000 : 农产品和园艺产品

很少有公司的业务是创造食物,这就是它们与此 cpv 相关联的原因。那些公司的报价存储在不同的 table "company_offer",但现在这并不重要。重要的是有 table "company_offer_cpv" 正在连接 company_offer 和 cpv tables。

"company_offer_cpv" table 有这些字段:

"id"、"company_offer_id"、"cpv_id"。这就是我如何将公司与其领域的 cpv 代码联系起来,因为他们可以分配给他们许多代码。

我想做的是计算每个 CPV 组的 company_offer_cpv table 中使用的所有 CPV,并在组旁边显示这些数字(当我说组时,我指的是根 cpv)名称.像这样:

03000000:农业、农业、渔业、林业及相关产品:240
09000000 : 石油产品、燃料、电力和其他能源:130
14000000:采矿、基础金属及相关产品:72

等等...

我用的是yii2php框架,如果你不会用也没关系,你可以帮我mysql/php代码。

为了计算 table company_offer_cpv 中使用的所有 CPV 的数量,并如上所述显示它们,我正在使用此代码:

$total = ClubOfferCpv::find()->groupBy( ['LEFT(cpv_id, 2)'] )
                             ->select( ['LEFT(cpv_id, 2) AS cpv_id', 'COUNT(cpv_id) AS count'])
                             ->all();

foreach ($total as $data) 
{
    $id = $data->cpv_id."000000";

    $cpvName = Cpv::find()->select('name')->where(['id' => $id])->one();

    echo $cpvName->name. " " . $data->count;
    echo "<br>";
}

但是我的代码中有一个缺陷(或更多 :))。如果您查看 foreach 循环内部,我正在执行这行代码以获取所有根 cpv 名称,以便我可以在计数旁边显示它们:

$cpvName = Cpv::find()->select('name')->where(['id' => $id])->one() ;

由于有 45 个根 CPV,此行将导致执行 45 个查询。我总共需要进行 1 次或 2 次查询,但我不知道如何进行。我知道 Yii2 提供 Eager Loading 但我不知道如何使用它。有谁知道我如何使用 yii2 或简单的 sql/php 来解决这个问题?

这是sql第一行代码执行的:

SELECT LEFT(cpv_id, 2) AS cpv_id, COUNT(cpv_id) AS count FROM `club_offer_cpv` GROUP BY LEFT(cpv_id, 2)

这是 foreach 中由行执行的 SQL 之一:

SELECT `name` FROM `cpv` WHERE `id`='50000000'

提前致谢


这里更新是为了很好地回复 JC Sama 的帮助:

如果我像你说的那样尝试使用 yii2 预加载,我不会得到任何结果,因为我需要从我的 company_offer_cpv table trim cpv_id ].以下是 eager 执行的查询:

SELECT LEFT(cpv_id, 2) AS cpv_id, COUNT(cpv_id) AS count FROM `club_offer_cpv` GROUP BY LEFT(cpv_id, 2) ;   

第二个:

SELECT * FROM `cpv` WHERE `id` IN ('03', '09', '14', '15', '16', '18', '19', '22', '24', '30', '31', '32', '33', '34', '35', '37', '38', '39', '41', '42', '43', '44', '45', '48', '50', '51', '55', '60', '63', '64', '65', '66', '70', '71', '72', '73', '75', '76', '77', '79', '80', '85', '90', '92', '98')

非常感谢 JC Sama!

这是完整的解决方案:

$total = ClubOfferCpv::find()->groupBy( ['LEFT(cpv_id, 2)'] )
                             ->select( ['LEFT(cpv_id, 2) AS cpv', 'CONCAT(LEFT(cpv_id, 2), "000000") AS cpv_id', 'COUNT(cpv_id) AS count'])
                             ->with('cpv')
                             ->all();

foreach ($total as $data) 
{
    echo $data->cpv->name. " " . $data->count;
    echo "<br>";
}

我不熟悉 Yii,但根据文档,您可以尝试这样的操作:

$total = ClubOfferCpv::find()->groupBy( ['LEFT(cpv_id, 2)'] )
                             ->select( ['LEFT(cpv_id, 2) AS cpv', 'CONCAT(LEFT(cpv_id, 2), "000000") AS cpv_id', 'COUNT(cpv_id) AS count'])
                             ->with('cpv')
                             ->all();

或者您可以像这样对您的第二个查询使用 WHERE IN() 语句,因为使用预加载的想法是相同的:

$ids = array();
foreach ($total as $data) 
{
    $id[] = $data->cpv_id."000000";
}

然后:

// I'm not sure about WhereIn() Yii stuff :)
$cpvNames = Cpv::find()->select('name, id')->where(['id' => $ids])->all();
// Create an associative array, not sure how it's done with Yii
$cpvNames = CHtml::listData( $cpvNames, 'id' , 'name'); 

最后:

foreach ($total as $data) {

    $id = $data->cpv_id."000000";
    echo $cpvNames[$id]. " " . $data->count;
    echo "<br>";
}

回顾: 您最终只会得到 2 个查询:

SELECT LEFT(cpv_id, 2) AS cpv_id, COUNT(cpv_id) AS count FROM `club_offer_cpv` GROUP BY LEFT(cpv_id, 2)

第二个使用 where in 而不是 equal

SELECT `name` FROM `cpv` WHERE `id` in ('50000000', 800000, ...)