5

使用 Propel 对象的计算字段的最佳方法是什么?

假设我有一个对象“客户”,它有一个对应的表“客户”,每一列对应于我的对象的一个​​属性。我想做的是:在我的对象上添加一个计算属性“已完成订单数”,而不是在视图 B 和 C 上使用它。

计算的属性是通过 ID 链接到我的“客户”对象的“订单”对象的 COUNT()。

我现在可以做的是首先选择所有客户对象,然后迭代地计算所有这些对象的订单,但我认为在单个查询中这样做会提高性能。但我无法正确“水合”我的 Propel 对象,因为它不包含计算字段的定义。

你会如何处理它?

4

5 回答 5

3

有几种选择。首先,是在您的数据库中创建一个可以为您计算的视图,类似于我在此处的回答。我为我正在处理的当前 Symfony 项目执行此操作,其中给定表的只读属性实际上比表本身宽得多。这是我的建议,因为分组列(max()、count() 等)无论如何都是只读的。

其他选项是将此功能实际构建到您的模型中。你绝对可以自己做这种补水,但这有点复杂。这是粗略的步骤

  1. 将列添加到Table类中作为受保护的数据成员。
  2. 为这些列编写适当的 getter 和 setter
  3. 覆盖 hydrate 方法并在其中使用来自其他查询的数据填充新列。确保调用 parent::hydrate() 作为第一行

但是,这并不比您已经在谈论的要好得多。您仍然需要N + 1 个查询来检索单个记录集。但是,您可以在第 3 步中发挥创意,使N是计算的列数,而不是返回的行数。

另一种选择是在您的Table Peer 类上创建自定义选择方法。

  1. 从上面执行步骤 1 和 2。
  2. 编写您将通过 Propel::getConnection() 过程手动查询的自定义 SQL。
  3. 通过迭代结果集手动创建数据集,并在此时处理自定义水合,以免在 doSelect 进程使用时破坏水合。

这是这种方法的一个示例

<?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;
    }
}

您的问题可能还有其他解决方案,但它们超出了我的知识范围。祝你好运!

于 2008-10-29T15:30:53.237 回答
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() 有一些愚蠢的地方,但我现在不记得了,但这确实有效。您可以阅读有关 AddAsColumn() 问题的更多信息

于 2009-03-23T21:16:10.447 回答
1

这是我在没有任何其他查询的情况下解决此问题的方法:

问题

需要将自定义 COUNT 字段添加到与 Symfony Pager 一起使用的典型结果集中。但是,正如我们所知,Propel 不支持此功能。所以简单的解决方案是在模板中做这样的事情:

foreach ($pager->getResults() as $project):

 echo $project->getName() . ' and ' . $project->getNumMembers()

endforeach;

Where为每个对象getNumMembers()运行一个单独的 COUNT 查询。$project当然,我们知道这是非常低效的,因为您可以通过将其作为列添加到原始 SELECT 查询中来动态执行 COUNT,为显示的每个结果保存一个查询。

我有几个不同的页面显示这个结果集,都使用不同的标准。因此,直接用 PDO 编写我自己的 SQL 查询字符串会太麻烦,因为我必须进入 Criteria 对象并试图根据其中的任何内容来形成查询字符串!

所以,我最终所做的一切都避免了,让 Propel 的本机代码与 Criteria 一起工作并像往常一样创建 SQL。

1 - 首先在由 doSelect() 返回的模型对象中创建 [get/set]NumMembers() 等效访问器/修改器方法。请记住,访问器不再执行 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 查询字符串的一个小妥协。

于 2010-03-26T13:21:22.423 回答
0

将属性“orders_count”添加到客户,然后编写如下内容:

class Order {
...
  public function save($conn = null) {
    $customer = $this->getCustomer();
    $customer->setOrdersCount($customer->getOrdersCount() + 1);
    $custoner->save();
    parent::save();
  }
...
}

您不仅可以使用“保存”方法,而且想法保持不变。不幸的是,Propel 不支持这些领域的任何“魔法”。

于 2008-10-29T14:00:11.780 回答
0

Propel 实际上是根据链接字段的名称构建了一个自动函数。假设您有这样的架构:

customer:
  id:
  name:
  ...

order:
  id:
  customer_id: # links to customer table automagically
  completed: { type: boolean, default false }
  ...

当您构建模型时,您的 Customer 对象将具有一个 getOrders() 方法,该方法将检索与该客户关联的所有订单。然后,您可以简单地使用 count($customer->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');
    }
    ...
  }
于 2008-10-30T16:58:48.177 回答