Dashboard: optimized group by (done by MySQL) + proto of a group by on 2 dimensions

SVN:trunk[2041]
This commit is contained in:
Romain Quetiez
2012-05-25 16:04:18 +00:00
parent 2c00c115d6
commit e4c113e412
6 changed files with 452 additions and 138 deletions

View File

@@ -951,12 +951,14 @@ class QueryBuilderExpressions
{
protected $m_oConditionExpr;
protected $m_aSelectExpr;
protected $m_aGroupByExpr;
protected $m_aJoinFields;
public function __construct($oCondition)
public function __construct($oCondition, $aGroupByExpr = null)
{
$this->m_oConditionExpr = $oCondition;
$this->m_aSelectExpr = array();
$this->m_aGroupByExpr = $aGroupByExpr;
$this->m_aJoinFields = array();
}
@@ -965,6 +967,11 @@ class QueryBuilderExpressions
return $this->m_aSelectExpr;
}
public function GetGroupBy()
{
return $this->m_aGroupByExpr;
}
public function GetCondition()
{
return $this->m_oConditionExpr;
@@ -998,6 +1005,13 @@ class QueryBuilderExpressions
{
$oExpr->GetUnresolvedFields($sAlias, $aUnresolved);
}
if ($this->m_aGroupByExpr)
{
foreach($this->m_aGroupByExpr as $sColAlias => $oExpr)
{
$oExpr->GetUnresolvedFields($sAlias, $aUnresolved);
}
}
foreach($this->m_aJoinFields as $oExpression)
{
$oExpression->GetUnresolvedFields($sAlias, $aUnresolved);
@@ -1011,6 +1025,13 @@ class QueryBuilderExpressions
{
$this->m_aSelectExpr[$sColAlias] = $oExpr->Translate($aTranslationData, $bMatchAll, $bMarkFieldsAsResolved);
}
if ($this->m_aGroupByExpr)
{
foreach($this->m_aGroupByExpr as $sColAlias => $oExpr)
{
$this->m_aGroupByExpr[$sColAlias] = $oExpr->Translate($aTranslationData, $bMatchAll, $bMarkFieldsAsResolved);
}
}
foreach($this->m_aJoinFields as $index => $oExpression)
{
$this->m_aJoinFields[$index] = $oExpression->Translate($aTranslationData, $bMatchAll, $bMarkFieldsAsResolved);
@@ -1024,6 +1045,13 @@ class QueryBuilderExpressions
{
$this->m_aSelectExpr[$sColAlias] = $oExpr->RenameParam($sOldName, $sNewName);
}
if ($this->m_aGroupByExpr)
{
foreach($this->m_aGroupByExpr as $sColAlias => $oExpr)
{
$this->m_aGroupByExpr[$sColAlias] = $oExpr->RenameParam($sOldName, $sNewName);
}
}
foreach($this->m_aJoinFields as $index => $oExpression)
{
$this->m_aJoinFields[$index] = $oExpression->RenameParam($sOldName, $sNewName);

View File

@@ -1933,7 +1933,81 @@ abstract class MetaModel
return $aScalarArgs;
}
public static function MakeGroupByQuery(DBObjectSearch $oFilter, $aArgs, $aGroupByExpr)
{
$aAttToLoad = array();
$oSelect = self::MakeSelectStructure($oFilter, array(), $aArgs, $aAttToLoad, null, 0, 0, false, $aGroupByExpr);
$aScalarArgs = array_merge(self::PrepareQueryArguments($aArgs), $oFilter->GetInternalParams());
try
{
$sRes = $oSelect->RenderGroupBy($aScalarArgs);
}
catch (MissingQueryArgument $e)
{
// Add some information...
$e->addInfo('OQL', $sOqlQuery);
throw $e;
}
return $sRes;
}
public static function MakeSelectQuery(DBObjectSearch $oFilter, $aOrderBy = array(), $aArgs = array(), $aAttToLoad = null, $aExtendedDataSpec = null, $iLimitCount = 0, $iLimitStart = 0, $bGetCount = false)
{
// Check the order by specification, and prefix with the class alias
// and make sure that the ordering columns are going to be selected
//
$aOrderSpec = array();
foreach ($aOrderBy as $sFieldAlias => $bAscending)
{
if ($sFieldAlias != 'id')
{
MyHelpers::CheckValueInArray('field name in ORDER BY spec', $sFieldAlias, self::GetAttributesList($oFilter->GetFirstJoinedClass()));
}
if (!is_bool($bAscending))
{
throw new CoreException("Wrong direction in ORDER BY spec, found '$bAscending' and expecting a boolean value");
}
$sFirstClassAlias = $oFilter->GetFirstJoinedClassAlias();
if (self::IsValidAttCode($oFilter->GetClass(), $sFieldAlias))
{
$oAttDef = self::GetAttributeDef($oFilter->GetClass(), $sFieldAlias);
foreach($oAttDef->GetOrderBySQLExpressions($sFirstClassAlias) as $sSQLExpression)
{
$aOrderSpec[$sSQLExpression] = $bAscending;
}
}
else
{
$aOrderSpec['`'.$sFirstClassAlias.$sFieldAlias.'`'] = $bAscending;
}
// Make sure that the columns used for sorting are present in the loaded columns
if (!is_null($aAttToLoad) && !isset($aAttToLoad[$sFirstClassAlias][$sFieldAlias]))
{
$aAttToLoad[$sFirstClassAlias][$sFieldAlias] = MetaModel::GetAttributeDef($oFilter->GetFirstJoinedClass(), $sFieldAlias);
}
}
$oSelect = self::MakeSelectStructure($oFilter, $aOrderBy, $aArgs, $aAttToLoad, $aExtendedDataSpec, $iLimitCount, $iLimitStart, $bGetCount);
$aScalarArgs = array_merge(self::PrepareQueryArguments($aArgs), $oFilter->GetInternalParams());
try
{
$sRes = $oSelect->RenderSelect($aOrderSpec, $aScalarArgs, $iLimitCount, $iLimitStart, $bGetCount);
}
catch (MissingQueryArgument $e)
{
// Add some information...
$e->addInfo('OQL', $sOqlQuery);
throw $e;
}
return $sRes;
}
protected static function MakeSelectStructure(DBObjectSearch $oFilter, $aOrderBy, $aArgs, $aAttToLoad, $aExtendedDataSpec, $iLimitCount, $iLimitStart, $bGetCount, $aGroupByExpr = null)
{
// Hide objects that are not visible to the current user
//
@@ -1984,6 +2058,13 @@ abstract class MetaModel
$sRawId = $sOqlQuery.'|'.implode(',', array_keys($aAttributes));
}
}
if (!is_null($aGroupByExpr))
{
foreach($aGroupByExpr as $sAlias => $oExpr)
{
$sRawId = 'g:'.$sAlias.'!'.$oExpr->Render();
}
}
$sOqlId = md5($sRawId);
}
else
@@ -2029,48 +2110,25 @@ abstract class MetaModel
}
}
// Check the order by specification, and prefix with the class alias
// and make sure that the ordering columns are going to be selected
//
$aOrderSpec = array();
foreach ($aOrderBy as $sFieldAlias => $bAscending)
{
if ($sFieldAlias != 'id')
{
MyHelpers::CheckValueInArray('field name in ORDER BY spec', $sFieldAlias, self::GetAttributesList($oFilter->GetFirstJoinedClass()));
}
if (!is_bool($bAscending))
{
throw new CoreException("Wrong direction in ORDER BY spec, found '$bAscending' and expecting a boolean value");
}
$sFirstClassAlias = $oFilter->GetFirstJoinedClassAlias();
if (self::IsValidAttCode($oFilter->GetClass(), $sFieldAlias))
{
$oAttDef = self::GetAttributeDef($oFilter->GetClass(), $sFieldAlias);
foreach($oAttDef->GetOrderBySQLExpressions($sFirstClassAlias) as $sSQLExpression)
{
$aOrderSpec[$sSQLExpression] = $bAscending;
}
}
else
{
$aOrderSpec['`'.$sFirstClassAlias.$sFieldAlias.'`'] = $bAscending;
}
// Make sure that the columns used for sorting are present in the loaded columns
if (!is_null($aAttToLoad) && !isset($aAttToLoad[$sFirstClassAlias][$sFieldAlias]))
{
$aAttToLoad[$sFirstClassAlias][$sFieldAlias] = MetaModel::GetAttributeDef($oFilter->GetFirstJoinedClass(), $sFieldAlias);
}
}
if (!isset($oSelect))
{
$oBuild = new QueryBuilderContext($oFilter, $aModifierProperties);
$oBuild = new QueryBuilderContext($oFilter, $aModifierProperties, $aGroupByExpr);
$oKPI = new ExecutionKPI();
$oSelect = self::MakeQuery($oBuild, $oFilter, $aAttToLoad, array(), true /* main query */);
$oSelect->SetCondition($oBuild->m_oQBExpressions->GetCondition());
$oSelect->SetSourceOQL($sOqlQuery);
if ($aGroupByExpr)
{
$aCols = $oBuild->m_oQBExpressions->GetGroupBy();
$oSelect->SetGroupBy($aCols);
$oSelect->SetSelect($aCols);
}
else
{
$oSelect->SetSelect($oBuild->m_oQBExpressions->GetSelect());
}
$oKPI->ComputeStats('MakeQuery (select)', $sOqlQuery);
if (self::$m_bQueryCacheEnabled)
@@ -2101,22 +2159,6 @@ abstract class MetaModel
$oSelect->AddInnerJoin($oSelectExt, 'id', $aExtendedDataSpec['join_key'] /*, $sTableAlias*/);
}
// Go
//
$aScalarArgs = array_merge(self::PrepareQueryArguments($aArgs), $oFilter->GetInternalParams());
try
{
$sRes = $oSelect->RenderSelect($aOrderSpec, $aScalarArgs, $iLimitCount, $iLimitStart, $bGetCount);
//echo "<p>MakeQuery: $sRes</p>";
}
catch (MissingQueryArgument $e)
{
// Add some information...
$e->addInfo('OQL', $sOqlQuery);
throw $e;
}
if (self::$m_bTraceQueries)
{
$sQueryId = md5($sRes);
@@ -2140,7 +2182,7 @@ abstract class MetaModel
}
}
return $sRes;
return $oSelect;
}
public static function ShowQueryTrace()
@@ -2205,6 +2247,8 @@ abstract class MetaModel
$aModifierProperties = self::MakeModifierProperties($oFilter);
$oBuild = new QueryBuilderContext($oFilter, $aModifierProperties);
$oSelect = self::MakeQuery($oBuild, $oFilter, null, array(), true /* main query */);
$oSelect->SetCondition($oBuild->m_oQBExpressions->GetCondition());
$oSelect->SetSelect($oBuild->m_oQBExpressions->GetSelect());
$aScalarArgs = array_merge(self::PrepareQueryArguments($aArgs), $oFilter->GetInternalParams());
return $oSelect->RenderDelete($aScalarArgs);
}
@@ -2215,11 +2259,13 @@ abstract class MetaModel
$aModifierProperties = self::MakeModifierProperties($oFilter);
$oBuild = new QueryBuilderContext($oFilter, $aModifierProperties);
$oSelect = self::MakeQuery($oBuild, $oFilter, null, $aValues, true /* main query */);
$oSelect->SetCondition($oBuild->m_oQBExpressions->GetCondition());
$oSelect->SetSelect($oBuild->m_oQBExpressions->GetSelect());
$aScalarArgs = array_merge(self::PrepareQueryArguments($aArgs), $oFilter->GetInternalParams());
return $oSelect->RenderUpdate($aScalarArgs);
}
private static function MakeQuery(&$oBuild, DBObjectSearch $oFilter, $aAttToLoad = null, $aValues = array(), $bIsMainQuery = false)
private static function MakeQuery(&$oBuild, DBObjectSearch $oFilter, $aAttToLoad = null, $aValues = array(), $bIsMainQueryUNUSED = false)
{
// Note: query class might be different than the class of the filter
// -> this occurs when we are linking our class to an external class (referenced by, or pointing to)
@@ -2442,14 +2488,6 @@ abstract class MetaModel
}
}
// Translate the conditions... and go
//
if ($bIsMainQuery)
{
$oSelectBase->SetCondition($oBuild->m_oQBExpressions->GetCondition());
$oSelectBase->SetSelect($oBuild->m_oQBExpressions->GetSelect());
}
// That's all... cross fingers and we'll get some working query
//MyHelpers::var_dump_html($oSelectBase, true);

View File

@@ -32,10 +32,10 @@ class QueryBuilderContext
public $m_oQBExpressions;
public function __construct($oFilter, $aModifierProperties)
public function __construct($oFilter, $aModifierProperties, $aGroupByExpr = null)
{
$this->m_oRootFilter = $oFilter;
$this->m_oQBExpressions = new QueryBuilderExpressions($oFilter->GetCriteria());
$this->m_oQBExpressions = new QueryBuilderExpressions($oFilter->GetCriteria(), $aGroupByExpr);
$this->m_aClassAliases = $oFilter->GetJoinedClasses();
$this->m_aTableAliases = array();

View File

@@ -41,6 +41,7 @@ class SQLQuery
private $m_sTable = '';
private $m_sTableAlias = '';
private $m_aFields = array();
private $m_aGroupBy = array();
private $m_oConditionExpr = null;
private $m_bToDelete = true; // The current table must be listed for deletion ?
private $m_aValues = array(); // Values to set in case of an update query
@@ -62,6 +63,7 @@ class SQLQuery
$this->m_sTable = $sTable;
$this->m_sTableAlias = $sTableAlias;
$this->m_aFields = $aFields;
$this->m_aGroupBy = null;
$this->m_oConditionExpr = null;
$this->m_bToDelete = $bToDelete;
$this->m_aValues = $aValues;
@@ -125,11 +127,12 @@ class SQLQuery
}
$aFrom = array();
$aFields = array();
$aGroupBy = array();
$oCondition = null;
$aDelTables = array();
$aSetValues = array();
$aSelectedIdFields = array();
$this->privRender($aFrom, $aFields, $oCondition, $aDelTables, $aSetValues, $aSelectedIdFields);
$this->privRender($aFrom, $aFields, $aGroupBy, $oCondition, $aDelTables, $aSetValues, $aSelectedIdFields);
echo "From ...<br/>\n";
echo "<pre style=\"font-size: smaller;\">\n";
print_r($aFrom);
@@ -141,6 +144,11 @@ class SQLQuery
$this->m_aFields = $aExpressions;
}
public function SetGroupBy($aExpressions)
{
$this->m_aGroupBy = $aExpressions;
}
public function SetCondition($oConditionExpr)
{
$this->m_oConditionExpr = $oConditionExpr;
@@ -235,11 +243,12 @@ class SQLQuery
// The goal will be to complete the list as we build the Joins
$aFrom = array();
$aFields = array();
$aGroupBy = arry();
$oCondition = null;
$aDelTables = array();
$aSetValues = array();
$aSelectedIdFields = array();
$this->privRender($aFrom, $aFields, $oCondition, $aDelTables, $aSetValues, $aSelectedIdFields);
$this->privRender($aFrom, $aFields, $aGroupBy, $oCondition, $aDelTables, $aSetValues, $aSelectedIdFields);
// Target: DELETE myAlias1, myAlias2 FROM t1 as myAlias1, t2 as myAlias2, t3 as topreserve WHERE ...
@@ -270,11 +279,12 @@ class SQLQuery
// The goal will be to complete the list as we build the Joins
$aFrom = array();
$aFields = array();
$aGroupBy = array();
$oCondition = null;
$aDelTables = array();
$aSetValues = array();
$aSelectedIdFields = array();
$this->privRender($aFrom, $aFields, $oCondition, $aDelTables, $aSetValues, $aSelectedIdFields);
$this->privRender($aFrom, $aFields, $aGroupBy, $oCondition, $aDelTables, $aSetValues, $aSelectedIdFields);
$sFrom = self::ClauseFrom($aFrom);
$sValues = self::ClauseValues($aSetValues);
$sWhere = self::ClauseWhere($oCondition, $aArgs);
@@ -287,11 +297,12 @@ class SQLQuery
// The goal will be to complete the lists as we build the Joins
$aFrom = array();
$aFields = array();
$aGroupBy = array();
$oCondition = null;
$aDelTables = array();
$aSetValues = array();
$aSelectedIdFields = array();
$this->privRender($aFrom, $aFields, $oCondition, $aDelTables, $aSetValues, $aSelectedIdFields);
$this->privRender($aFrom, $aFields, $aGroupBy, $oCondition, $aDelTables, $aSetValues, $aSelectedIdFields);
$sFrom = self::ClauseFrom($aFrom);
$sWhere = self::ClauseWhere($oCondition, $aArgs);
@@ -328,6 +339,27 @@ class SQLQuery
return $sSQL;
}
// Interface, build the SQL query
public function RenderGroupBy($aArgs = array())
{
// The goal will be to complete the lists as we build the Joins
$aFrom = array();
$aFields = array();
$aGroupBy = array();
$oCondition = null;
$aDelTables = array();
$aSetValues = array();
$aSelectedIdFields = array();
$this->privRender($aFrom, $aFields, $aGroupBy, $oCondition, $aDelTables, $aSetValues, $aSelectedIdFields);
$sSelect = self::ClauseSelect($aFields);
$sFrom = self::ClauseFrom($aFrom);
$sWhere = self::ClauseWhere($oCondition, $aArgs);
$sGroupBy = self::ClauseGroupBy($aGroupBy);
$sSQL = "SELECT $sSelect, COUNT(*) AS _itop_count_ FROM $sFrom WHERE $sWhere GROUP BY $sGroupBy";
return $sSQL;
}
private static function ClauseSelect($aFields)
{
$aSelect = array();
@@ -339,6 +371,12 @@ class SQLQuery
return $sSelect;
}
private static function ClauseGroupBy($aGroupBy)
{
$sRes = implode(', ', $aGroupBy);
return $sRes;
}
private static function ClauseDelete($aDelTableAliases)
{
$aDelTables = array();
@@ -415,14 +453,14 @@ class SQLQuery
}
// Purpose: prepare the query data, once for all
private function privRender(&$aFrom, &$aFields, &$oCondition, &$aDelTables, &$aSetValues, &$aSelectedIdFields)
private function privRender(&$aFrom, &$aFields, &$aGroupBy, &$oCondition, &$aDelTables, &$aSetValues, &$aSelectedIdFields)
{
$sTableAlias = $this->privRenderSingleTable($aFrom, $aFields, $aDelTables, $aSetValues, $aSelectedIdFields, '', array('jointype' => 'first'));
$sTableAlias = $this->privRenderSingleTable($aFrom, $aFields, $aGroupBy, $aDelTables, $aSetValues, $aSelectedIdFields, '', array('jointype' => 'first'));
$oCondition = $this->m_oConditionExpr;
return $sTableAlias;
}
private function privRenderSingleTable(&$aFrom, &$aFields, &$aDelTables, &$aSetValues, &$aSelectedIdFields, $sCallerAlias = '', $aJoinData)
private function privRenderSingleTable(&$aFrom, &$aFields, &$aGroupBy, &$aDelTables, &$aSetValues, &$aSelectedIdFields, $sCallerAlias = '', $aJoinData)
{
$aActualTableFields = CMDBSource::GetTableFieldsList($this->m_sTable);
@@ -506,6 +544,13 @@ class SQLQuery
{
$aFields["`$sAlias`"] = $oExpression->Render();
}
if ($this->m_aGroupBy)
{
foreach($this->m_aGroupBy as $sAlias => $oExpression)
{
$aGroupBy["`$sAlias`"] = $oExpression->Render();
}
}
if ($this->m_bToDelete)
{
$aDelTables[] = "`{$this->m_sTableAlias}`";
@@ -528,7 +573,7 @@ class SQLQuery
{
$oRightSelect = $aJoinData["select"];
$sJoinTableAlias = $oRightSelect->privRenderSingleTable($aTempFrom, $aFields, $aDelTables, $aSetValues, $aSelectedIdFields, $this->m_sTableAlias, $aJoinData);
$sJoinTableAlias = $oRightSelect->privRenderSingleTable($aTempFrom, $aFields, $aGroupBy, $aDelTables, $aSetValues, $aSelectedIdFields, $this->m_sTableAlias, $aJoinData);
}
$aFrom[$this->m_sTableAlias]['subfrom'] = $aTempFrom;