5

Propel オブジェクトの計算フィールドを操作する最良の方法は何ですか?

対応するテーブル「customers」を持つオブジェクト「Customer」があり、各列がオブジェクトの属性に対応しているとします。私がやりたいことは、計算された属性「完了した注文の数」をビュー A で使用するときにオブジェクトに追加し、ビュー B と C では使用しないことです。

計算された属性は、ID を介して「Customer」オブジェクトにリンクされた「Order」オブジェクトの COUNT() です。

今できることは、最初にすべての Customer オブジェクトを選択し、次にそれらすべての Orders を繰り返しカウントすることですが、1 つのクエリでそれを行うとパフォーマンスが向上すると思います。しかし、計算フィールドの定義が含まれていないため、Propel オブジェクトを適切に「水和」できません。

どのようにアプローチしますか?

4

5 に答える 5

3

いくつかの選択肢があります。まず、ここでの私の答えと同様に、DB にカウントを行うビューを作成することです。私が取り組んでいる現在の Symfony プロジェクトでは、特定のテーブルの読み取り専用属性が実際にはテーブル自体よりもはるかに広い場合にこれを行います。グループ化列 (max()、count() など) はとにかく読み取り専用であるため、これは私の推奨事項です。

他のオプションは、実際にこの機能をモデルに組み込むことです。この水分補給は絶対に自分で行うことができますが、少し複雑です. 大まかな手順はこちら

  1. 列を保護されたデータ メンバーとしてTableクラスに追加します。
  2. これらの列に適切なゲッターとセッターを記述します
  3. hydrate メソッドをオーバーライドし、その中で、新しい列に他のクエリからのデータを入力します。親::水和物()を最初の行として呼び出すようにしてください

ただし、これはあなたがすでに話していることよりもはるかに優れているわけではありません。1 つのレコード セットを取得するには、N + 1 個のクエリが必要です。ただし、ステップ 3 で工夫を凝らして、Nが返される行の数ではなく、計算された列の数になるようにすることができます。

もう 1 つのオプションは、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

追加のクエリなしでこれを解決するために私がしたことは次のとおりです。

問題

Symfony Pager で使用される一般的な結果セットにカスタム COUNT フィールドを追加する必要がありました。しかし、私たちが知っているように、Propel はこれを最初からサポートしていません。したがって、簡単な解決策は、テンプレートで次のようにすることです。

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

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

endforeach;

WheregetNumMembers()は、オブジェクトごとに個別の COUNT クエリを実行し$projectます。もちろん、これが非常に非効率であることはわかっています。なぜなら、表示される結果ごとにクエリを保存して、元の SELECT クエリに列として追加することで、その場で COUNT を実行できるからです。

この結果セットを表示するいくつかの異なるページがあり、すべて異なる基準を使用していました。そのため、PDO を使用して独自の SQL クエリ文字列を直接記述するのは、Criteria オブジェクトにアクセスして、そこにあるものに基づいてクエリ文字列を作成しようと試行錯誤する必要があるため、面倒です!

したがって、私が最終的に行ったことは、Propel のネイティブ コードが Criteria で動作し、通常どおり SQL を作成できるようにすることで、それをすべて回避することです。

1 - 最初に、[get/set]NumMembers() と同等のアクセサー/ミューテーター メソッドを、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 クエリ文字列を作成しないための小さな妥協のように思えました。

于 2010-03-26T13:21:22.423 に答える
1

postgis フィールドにアクセスするために hydrate() と Peer::addSelectColumns() をオーバーライドすることで、プロジェクトでこれを行っています。

// 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 に答える
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() の使用のパフォーマンスの唯一の違いは、結果セットを返すために使用される帯域幅です。その帯域幅がアプリケーションにとって重要な場合は、Creole を使用して手動で COUNT() クエリを作成するメソッドを Customer オブジェクトに作成することをお勧めします。

  // 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 に答える