Возник вопрос. Есть таблицы profiles (1M записей), mailings_links_profiles (1M записей), mailings (несколько записей). В таблице с линками (стержневой) используются просто поля связей между profiles и mailings, как profile_id, mailing_id.
В модели Mailing я использую (как в документации) получение моделей Profile через стержневую таблицу с viaTable(). Фактически любые операции с данным ActiveQuery (count, one, etc) убивают приложение, и даже 512М памяти не хватает. Все потому что viaTable() фактически выгружает стержневую таблицу (зависит от количества подходящих строк) в память, чтобы сделать из нее массив и уже нужные подходящие ключи. В моем случае это допустим 1М записей. Почему не используются стандартные JOIN ... ?
Код: Выделить всё
/**
* @return \app\components\yiiExt\ActiveQuery
*/
public function getProfiles()
{
return $this->hasMany(Profile::className(), ['id' => 'profile_id'])->viaTable('{{%mailings_news_links_profiles}}', ['mailing_id' => 'id']);
}
Код: Выделить всё
namespace app\components\yiiExt;
use yii\db\Expression;
/**
* Class ActiveQuery
* @package app\components\yiiExt
*/
class ActiveQuery extends \yii\db\ActiveQuery
{
public $found_rows = null;
public function populate( $rows )
{
if ($this->selectOption == 'SQL_CALC_FOUND_ROWS') {
/* @var $modelClass ActiveRecord */
$modelClass = $this->modelClass;
$this->found_rows = $modelClass::getDb()->createCommand( "SELECT FOUND_ROWS()" )->queryScalar();
}
return parent::populate( $rows );
}
public function one( $db = null )
{
// Yii не выставляет лимит при запросе `one`,
// это выливается в огромное расходование памяти при запросах без условий (или общих условиях)
// на больших таблицах, так как вся таблица загружается в память, чтобы взять оттуда только первую строку.
$this->limit( 1 );
return parent::one( $db );
}
public function viaTableUseJoin( $tableName, $link, callable $callable = null )
{
/** @var ActiveRecord $targetClass */
$targetClass = $this->modelClass;
$pivotTbl = $tableName;
$targetTbl = $targetClass::tableName();
$primaryTbl = $this->primaryModel->tableName();
/*
SELECT `ss_profiles`.* FROM `ss_profiles`
LEFT OUTER JOIN `ss_mailings_news_links_profiles` ON `ss_mailings_news_links_profiles`.`profile_id` = `ss_profiles`.`id`
LEFT OUTER JOIN `ss_mailings_news` ON `ss_mailings_news`.`id` = `ss_mailings_news_links_profiles`.`mailing_id`
WHERE `ss_mailings_news`.`id` = 3;
*/
$query = new ActiveQuery( $this->modelClass );
$query->select( $targetTbl . '.*' );
// Соединение pivot таблицы с таблицей целевой модели
$query->leftJoin(
$pivotTbl,
[
$pivotTbl . '.[[' . array_values( $this->link )[ 0 ] . ']]' => new Expression(
$targetTbl . '.[[' . array_keys( $this->link )[ 0 ] . ']]'
)
]
);
// Соединение pivot таблицы с таблицей модели, откуда идет запрос
$query->leftJoin(
$primaryTbl,
[
$primaryTbl . '.[[' . array_values( $link )[ 0 ] . ']]' => new Expression(
$pivotTbl . '.[[' . array_keys( $link )[ 0 ] . ']]'
)
]
);
// Ставим условия по PK модели вызова
$query->where( [ $primaryTbl . '.[[' . array_values( $link )[ 0 ] . ']]' => $this->primaryModel->{array_values( $link )[ 0 ]} ] );
// Ставим условия на целевую модель, чтобы строки были
$query->andWhere( $targetTbl . '.[[' . array_keys( $this->link )[ 0 ] . ']] IS NOT NULL' );
$query->multiple = true;
$query->from( $targetTbl );
if ($callable !== null) {
call_user_func( $callable, $query );
}
return $query;
}
}