Frage

Was ist der beste Weg, um mit berechneten Feldern von Propel Objekten zu arbeiten?

Sagen wir, ich habe ein Objekt „Kunde“, dass eine entsprechende Tabelle „Kunden“ hat und jede Spalte entspricht einem Attribut meines Objekts. Was würde Ich mag zu tun ist: a. Berechnet Attribut „Anzahl der abgeschlossenen Aufträge“ zu meinem Objekt hinzufügen, wenn es auf Modell A, aber nicht auf Ansichten B und C mit

Das berechnete Attribut ist ein COUNT () von „Order“ Objekten im Zusammenhang mit meinem „Kunden“ Objekt über ID.

Was kann ich tun, ist nun zunächst alle Kunden Objekte auswählen, dann iterativ zählen Aufträge für alle von ihnen, aber ich denke, es wäre in einer einzigen Abfrage zu tun würde die Leistung verbessern. Aber ich kann nicht richtig „Hydrat“ mein Propel Objekt, da es nicht enthält die Definition des berechneten Feldes (s).

Wie würden Sie es angehen?

War es hilfreich?

Lösung

Es gibt mehrere Möglichkeiten. Erstens ist eine Ansicht in Ihrer DB zu erstellen, die die Zählungen für Sie, ähnlich wie meine Antwort hier tun werden. Ich tue dies für ein aktuelles Symfony Projekt arbeite ich an, wo die Lese-nur für eine bestimmte Tabelle Attribute sind eigentlich viel, viel breiter als die Tabelle selbst. Das ist meine Empfehlung, da Gruppierungsspalten (max (), count (), usw.) sind schreibgeschützt sowieso.

Die anderen Optionen sind, um tatsächlich diese Funktionalität in Ihr Modell zu bauen. Sie können absolut tun, um diese Feuchtigkeit selbst, aber es ist ein wenig kompliziert. Hier sind die groben Schritte

  1. Fügen Sie die Spalten der Table Klasse als geschützte Datenelemente.
  2. Schreiben Sie die entsprechenden Getter und Setter für diese Spalten
  3. die Hydrat-Methode außer Kraft setzen und innerhalb Ihrer neue Spalten mit den Daten aus anderen Abfragen füllen. Stellen Sie sicher, parent :: Hydrat () als die erste Zeile nennen

Dies ist jedoch nicht viel besser als das, was Sie schon sprechen. Sie werden immer noch brauchen N + 1 Abfragen einen einzelnen Datensatz abzurufen. Sie können jedoch 3 so in Schritt # kreativ, dass N ist die Anzahl der berechneten Spalten, nicht die Anzahl der zurückgegebenen Zeilen.

Eine andere Möglichkeit ist es, eine individuelle Auswahlmethode auf Table Peer-Klasse zu erstellen.

  1. Führen Sie die Schritte 1 und 2 von oben.
  2. Schreiben benutzerdefinierte SQL, die Sie manuell über das Propel :: getConnection () Prozess abfragt.
  3. Erstellen Sie den Datensatz manuell über die Ergebnismenge iteriert und benutzerdefinierte Trink an dieser Stelle behandeln, um nicht Feuchtigkeit zu brechen, wenn die Nutzung durch die doSelect Prozesse.

Hier ist ein Beispiel für diesen Ansatz

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

Es können auch andere Lösungen sein, um Ihr Problem, aber sie sind entzieht sich meiner Kenntnis. Viel Glück!

Andere Tipps

ich das jetzt in einem Projekt mache von Hydrat überschreiben () und Peer :: addSelectColumns () für den Zugriff auf postgis Felder:

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

Es ist etwas doof mit AddAsColumn (), aber ich kann im Moment nicht erinnern, aber dies funktioniert. Sie können mehr über die AddAsColumn lesen () Fragen .

Hier ist, was ich tue dies zu lösen, ohne zusätzliche Abfragen:

Problem

Wir brauchten eine benutzerdefinierte COUNT Feld zu einem typischen Ergebnismenge mit dem Symfony Pager verwendet hinzuzufügen. Aber wie wir wissen, ist Propel dies die Box nicht unterstützt werden. So ist die einfache Lösung ist, einfach in der Vorlage so etwas zu tun:

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

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

endforeach;

Wo getNumMembers() läuft eine separate COUNT Abfrage für jedes $project Objekt. Natürlich wissen wir das höchst ineffizient ist, weil Sie es als eine Spalte in die ursprünglichen SELECT-Abfrage durch Hinzufügen, Speicher eine Abfrage für jedes Ergebnis angezeigt, um die COUNT on the fly tun.

Ich hatte mehrere verschiedene Seiten dieses Ergebnismenge anzeigt, die alle unterschiedliche Kriterien. Also mit PDO meine eigene SQL-Query-String schreiben würde direkt viel zu viel Aufwand sein, wie ich in den Criteria Objekt erhalten müssten und mess around versuchen, eine Abfrage-String zu bilden, basierend auf, was war drin!

Also, was ich am Ende tat vermeidet, dass alle mit den Kriterien nativen Code Arbeit des Propel zu lassen und die SQL wie gewohnt erstellen.

1 - Erstellen Sie zunächst die [get / set] NumMembers () äquivalent Accessor / Mutator-Methoden im Modellobjekt, das durch die doSelect () wird wiederkommen. Bedenken Sie, dass der Accessor nicht die COUNT Abfrage mehr tun, es hält nur seinen Wert.

2 - Gehen Sie in die Peer-Klasse und überschreiben die Eltern doSelect () -Methode, und kopieren Sie den gesamten Code aus es genau so, wie es ist

3 - dieses Bit entfernen, weil getMixerPreSelectHook eine private Methode der Basis Peer (oder es in Ihrem Peer kopieren, wenn Sie es brauchen):

// symfony_behaviors behavior
foreach (sfMixer::getCallables(self::getMixerPreSelectHook(__FUNCTION__)) as $sf_hook)
{
  call_user_func($sf_hook, 'BaseTsProjectPeer', $criteria, $con);
}

4 - Jetzt ist Ihr individuelles COUNT Feld an die doSelect Methode in der Peer-Klasse hinzufügen:

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

Das ist es. Jetzt haben Sie das zusätzliche COUNT Feld zu Ihrem Objekt hinzugefügt, ohne eine separate Abfrage zu tun, es zu erhalten, wie Sie die Ergebnisse ausspucken. Der einzige Nachteil dieser Lösung ist, dass Sie alle übergeordneten Code kopieren habe, weil Sie Bits nach rechts in der Mitte der es hinzufügen müssen. Aber in meiner Situation, schien dies wie ein kleiner Kompromiss all diese Abfragen zu speichern und nicht meinen eigenen SQL-Abfrage-String schreiben.

Fügen Sie ein Attribut „ORDERS_COUNT“ an einen Kunden, und dann so etwas schreiben:

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

Sie können mit nicht nur die „save“ Methode, aber die Idee bleibt gleich. Leider hat Propel keine „magische“ Unterstützung für solche Felder aus.

Propel baut tatsächlich eine automatische Funktion auf den Namen des verknüpften Feldes basiert. Angenommen, Sie haben ein Schema wie folgt aus:

customer:
  id:
  name:
  ...

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

Wenn Sie Ihr Modell bauen, Ihr Customer-Objekt wird ein Verfahren GetOrders () haben, die alle Aufträge mit diesem Kunden zugeordnet abruft. Sie können dann einfach verwenden count ($ kunden-> GetOrders ()) die Anzahl der Aufträge für diese Kunden zu erhalten.

Der Nachteil ist, wird dies auch die Auftrag Objekte holen und Hydrat. Auf den meisten RDBMS, ist der einzige Unterschied in der Leistung zwischen den Datensatz zu ziehen oder mit COUNT () die verwendeten Bandbreite eingestellt, die Ergebnisse zurückzukehren. Wenn die Bandbreite für Ihre Anwendung von Bedeutung sein würde, mögen Sie vielleicht eine Methode in dem Customer-Objekt erstellen, das die COUNT () Abfrage manuell mit Creole baut:

  // 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');
    }
    ...
  }
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top