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

假设我有一个对象“客户”具有相应表“客户”的表并且每列对应于我的对象的属性。我想要做的是:添加一个计算属性“已完成订单数量”;在View A上使用它而不是在视图B和C上使用它时。

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

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

你会怎么做?

有帮助吗?

解决方案

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

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

  1. 将列添加到 Table 类作为受保护数据成员。
  2. 为这些列编写适当的getter和setter
  3. 覆盖水合物方法,在其中,使用其他查询中的数据填充新列。确保将parent :: hydrate()作为第一行调用
  4. 但是,这并不比你刚才谈到的好多少。您仍然需要 N + 1个查询来检索单个记录集。但是,您可以在步骤#3中获得创意,以便 N 是计算列的数量,而不是返回的行数。

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

    1. 从上面做第1步和第2步。
    2. 编写将通过Propel :: getConnection()过程手动查询的自定义SQL。
    3. 通过迭代结果集手动创建数据集,并在此时处理自定义水合作用,以便在doSelect过程使用时不会破坏水合作用。
    4. 以下是此方法的一个示例

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

      您的问题可能还有其他解决方案,但我们知之甚少。祝你好运!

其他提示

我现在正在一个项目中通过覆盖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()问题的更多信息

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

<强>问题

需要将自定义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查询字符串。

添加属性“orders_count”给客户,然后写这样的东西:

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

您不仅可以使用“保存”功能,还可以使用“保存”功能。方法,但想法保持不变。不幸的是,Propel并不支持任何“魔术”。对于这样的领域。

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');
    }
    ...
  }
许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top