使用Propel对象的计算字段的最佳方法是什么?
假设我有一个对象“Customer”,它有一个对应的表“customers”,每列对应一个我对象的属性。我想要做的是:在View A上使用它时,在我的对象上添加一个计算属性“已完成订单的数量”,但不在视图B和C上。
计算的属性是通过ID链接到我的“客户”对象的“订单”对象的COUNT()。
我现在可以做的是首先选择所有客户对象,然后迭代计算所有客户对象的订单,但我认为在单个查询中执行此操作可以提高性能。但是我无法正确地“保湿”我的Propel对象,因为它不包含计算字段的定义。
你会怎么做?
答案 0 :(得分:3)
有几种选择。首先,在您的数据库中创建一个视图,为您完成计数,类似于我的回答here。我为当前的Symfony项目执行此操作,其中给定表的只读属性实际上比表本身宽得多。这是我的建议,因为分组列(max(),count()等)无论如何都是只读的。
其他选项是在模型中实际构建此功能。你自己绝对可以做这种水合作用,但它有点复杂。这是粗略的步骤
但是,这并不比你刚才谈到的好多少。您仍然需要 N + 1个查询来检索单个记录集。但是,您可以在步骤3中获得创意,以便 N 是计算列的数量,而不是返回的行数。
另一种选择是在 Table Peer类上创建自定义选择方法。
以下是此方法的一个示例
<?php
class TablePeer extends BaseTablePeer
{
public static function selectWithCalculatedColumns()
{
// Do our custom selection, still using propel's column data constants
$sql = "
SELECT " . implode( ', ', self::getFieldNames( BasePeer::TYPE_COLNAME ) ) . "
, count(" . JoinedTablePeer::ID . ") AS calc_col
FROM " . self::TABLE_NAME . "
LEFT JOIN " . JoinedTablePeer::TABLE_NAME . "
ON " . JoinedTablePeer::ID . " = " . self::FKEY_COLUMN
;
// Get the result set
$conn = Propel::getConnection();
$stmt = $conn->prepareStatement( $sql );
$rs = $stmt->executeQuery( array(), ResultSet::FETCHMODE_NUM );
// Create an empty rowset
$rowset = array();
// Iterate over the result set
while ( $rs->next() )
{
// Create each row individually
$row = new Table();
$startcol = $row->hydrate( $rs );
// Use our custom setter to populate the new column
$row->setCalcCol( $row->get( $startcol ) );
$rowset[] = $row;
}
return $rowset;
}
}
您的问题可能还有其他解决方案,但我们知之甚少。祝你好运!
答案 1 :(得分:1)
我现在正在一个项目中通过覆盖hydrate()和Peer :: addSelectColumns()来访问postgis字段:
// in peer
public static function locationAsEWKTColumnIndex()
{
return GeographyPeer::NUM_COLUMNS - GeographyPeer::NUM_LAZY_LOAD_COLUMNS;
}
public static function polygonAsEWKTColumnIndex()
{
return GeographyPeer::NUM_COLUMNS - GeographyPeer::NUM_LAZY_LOAD_COLUMNS + 1;
}
public static function addSelectColumns(Criteria $criteria)
{
parent::addSelectColumns($criteria);
$criteria->addAsColumn("locationAsEWKT", "AsEWKT(" . GeographyPeer::LOCATION . ")");
$criteria->addAsColumn("polygonAsEWKT", "AsEWKT(" . GeographyPeer::POLYGON . ")");
}
// in object
public function hydrate($row, $startcol = 0, $rehydrate = false)
{
$r = parent::hydrate($row, $startcol, $rehydrate);
if ($row[GeographyPeer::locationAsEWKTColumnIndex()]) // load GIS info from DB IFF the location field is populated. NOTE: These fields are either both NULL or both NOT NULL, so this IF is OK
{
$this->location_ = GeoPoint::PointFromEWKT($row[GeographyPeer::locationAsEWKTColumnIndex()]); // load gis data from extra select columns See GeographyPeer::addSelectColumns().
$this->polygon_ = GeoMultiPolygon::MultiPolygonFromEWKT($row[GeographyPeer::polygonAsEWKTColumnIndex()]); // load gis data from extra select columns See GeographyPeer::addSelectColumns().
}
return $r;
}
AddAsColumn()有一些愚蠢的东西,但我现在不记得了,但这确实有效。你可以read more about the AddAsColumn() issues。
答案 2 :(得分:1)
以下是我在没有任何其他问题的情况下解决此问题的方法:
<强>问题强>
需要将自定义COUNT字段添加到与Symfony寻呼机一起使用的典型结果集中。但是,正如我们所知,Propel并不支持这一点。因此,简单的解决方案是在模板中执行以下操作:
foreach ($pager->getResults() as $project):
echo $project->getName() . ' and ' . $project->getNumMembers()
endforeach;
getNumMembers()
为每个$project
对象运行单独的COUNT个查询。当然,我们知道这是非常低效的,因为您可以通过将其作为列添加到原始SELECT查询来动态执行COUNT,从而为显示的每个结果保存查询。
我有几个显示此结果集的不同页面,全部使用不同的Criteria。因此,直接使用PDO编写我自己的SQL查询字符串会非常麻烦,因为我必须进入Criteria对象并且根据其中的内容尝试形成查询字符串!
所以,我最终做的就是避免这一切,让Propel的本机代码与Criteria协同工作并像往常一样创建SQL。
1 - 首先在模型对象中创建[get / set] NumMembers()等效的accessor / mutator方法,由doSelect()返回。请记住,访问者不再执行COUNT查询,它只保留其值。
2 - 进入对等类并覆盖父doSelect()方法并完全按原样复制所有代码
3 - 删除此位,因为getMixerPreSelectHook是基础对等方的私有方法(如果需要,可以将其复制到对等方中):
// symfony_behaviors behavior
foreach (sfMixer::getCallables(self::getMixerPreSelectHook(__FUNCTION__)) as $sf_hook)
{
call_user_func($sf_hook, 'BaseTsProjectPeer', $criteria, $con);
}
4 - 现在将自定义COUNT字段添加到对等类中的doSelect方法:
// copied into ProjectPeer - overrides BaseProjectPeer::doSelectJoinUser()
public static function doSelectJoinUser(Criteria $criteria, ...)
{
// copied from parent method, along with everything else
ProjectPeer::addSelectColumns($criteria);
$startcol = (ProjectPeer::NUM_COLUMNS - ProjectPeer::NUM_LAZY_LOAD_COLUMNS);
UserPeer::addSelectColumns($criteria);
// now add our custom COUNT column after all other columns have been added
// so as to not screw up Propel's position matching system when hydrating
// the Project and User objects.
$criteria->addSelectColumn('COUNT(' . ProjectMemberPeer::ID . ')');
// now add the GROUP BY clause to count members by project
$criteria->addGroupByColumn(self::ID);
// more parent code
...
// until we get to this bit inside the hydrating loop:
$obj1 = new $cls();
$obj1->hydrate($row);
// AND...hydrate our custom COUNT property (the last column)
$obj1->setNumMembers($row[count($row) - 1]);
// more code copied from parent
...
return $results;
}
就是这样。现在,您可以在对象中添加额外的COUNT字段,而无需执行单独的查询来获取结果。此解决方案的唯一缺点是您必须复制所有父代码,因为您需要在其中间添加位。但在我的情况下,这似乎是一个小小的妥协,以保存所有这些查询,而不是编写我自己的SQL查询字符串。
答案 3 :(得分:0)
向Customer添加属性“orders_count”,然后编写如下内容:
class Order {
...
public function save($conn = null) {
$customer = $this->getCustomer();
$customer->setOrdersCount($customer->getOrdersCount() + 1);
$custoner->save();
parent::save();
}
...
}
您不仅可以使用“保存”方法,还可以保持相同的想法。不幸的是,Propel并不支持这些领域的任何“魔力”。
答案 4 :(得分:0)
Propel实际上是根据链接字段的名称构建自动功能。假设您有这样的架构:
customer:
id:
name:
...
order:
id:
customer_id: # links to customer table automagically
completed: { type: boolean, default false }
...
构建模型时,Customer对象将有一个方法getOrders(),它将检索与该客户关联的所有订单。然后,您只需使用count($ customer-&gt; getOrders())来获取该客户的订单数量。
缺点是它还会获取并保湿那些Order对象。在大多数RDBMS上,拉动记录或使用COUNT()之间的唯一性能差异是用于返回结果集的带宽。如果该带宽对您的应用程序很重要,您可能希望在Customer对象中创建一个方法,该方法使用Creole手动构建COUNT()查询:
// in lib/model/Customer.php
class Customer extends BaseCustomer
{
public function CountOrders()
{
$connection = Propel::getConnection();
$query = "SELECT COUNT(*) AS count FROM %s WHERE customer_id='%s'";
$statement = $connection->prepareStatement(sprintf($query, CustomerPeer::TABLE_NAME, $this->getId());
$resultset = $statement->executeQuery();
$resultset->next();
return $resultset->getInt('count');
}
...
}