expresiones ejemplos condicionales campos campo calculados calculado php orm symfony1 propel

php - ejemplos - formulas en campos calculados excel



Aplicación Symfony: ¿cómo agregar campos calculados a los objetos Propel? (5)

Agregue un atributo "orders_count" a un Cliente y luego escriba algo como esto:

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

Puede utilizar no solo el método "guardar", sino que la idea permanece igual. Desafortunadamente, Propel no admite ninguna "magia" para dichos campos.

¿Cuál es la mejor manera de trabajar con los campos calculados de los objetos Propel?

Digamos que tengo un objeto "Cliente" que tiene una tabla correspondiente "clientes" y cada columna corresponde a un atributo de mi objeto. Lo que me gustaría hacer es agregar un atributo calculado "Número de órdenes completadas" a mi objeto al usarlo en la Vista A, pero no en las Vistas B y C.

El atributo calculado es un COUNT () de objetos "Orden" vinculados a mi objeto "Cliente" a través de ID.

Lo que puedo hacer ahora es seleccionar primero todos los objetos del Cliente, luego contar iterativamente los Pedidos para todos ellos, pero creo que hacerlo en una sola consulta mejoraría el rendimiento. Pero no puedo "hidratar" adecuadamente mi objeto Propel ya que no contiene la definición de campo (s) calculado (s).

¿Cómo lo abordarías?


Propel realmente construye una función automática basada en el nombre del campo vinculado. Digamos que tienes un esquema como este:

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

Cuando construya su modelo, su objeto Cliente tendrá un método getOrders () que recuperará todos los pedidos asociados con ese cliente. Luego puede simplemente usar count ($ customer-> getOrders ()) para obtener el número de pedidos para ese cliente.

La desventaja es que esto también traerá e hidratará esos objetos Order. En la mayoría de RDBMS, la única diferencia de rendimiento entre tirar de los registros o usar COUNT () es el ancho de banda utilizado para devolver el conjunto de resultados. Si ese ancho de banda sería significativo para su aplicación, es posible que desee crear un método en el objeto Cliente que genere la consulta COUNT () manualmente usando Créole:

// 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''); } ... }


Estoy haciendo esto en un proyecto ahora anulando hydrate () y Peer :: addSelectColumns () para acceder a los campos de 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; }

Hay algo ridículo con AddAsColumn () pero no lo recuerdo en este momento, pero funciona. Puede leer más sobre los problemas de AddAsColumn () .


Esto es lo que hice para resolver esto sin ninguna consulta adicional:

Problema

Necesario para agregar un campo COUNT personalizado a un conjunto de resultados típico utilizado con el Buscapersonas Symfony. Sin embargo, como sabemos, Propel no es compatible con esto. Entonces, la solución fácil es hacer algo como esto en la plantilla:

foreach ($pager->getResults() as $project): echo $project->getName() . '' and '' . $project->getNumMembers() endforeach;

Donde getNumMembers() ejecuta una consulta COUNT por separado para cada objeto $project . Por supuesto, sabemos que esto es extremadamente ineficiente porque puede hacer el COUNT sobre la marcha al agregarlo como una columna a la consulta SELECT original, guardando una consulta para cada resultado mostrado.

Tenía varias páginas diferentes que mostraban este conjunto de resultados, todas con diferentes criterios. ¡Así que escribir directamente mi propia cadena de consulta SQL con PDO sería demasiado complicado ya que tendría que entrar en el objeto Criteria y perder el tiempo tratando de formar una cadena de consulta basada en lo que haya en ella!

Entonces, lo que hice al final evita todo eso, permitiendo que el código nativo de Propel trabaje con los Criterios y cree el SQL como de costumbre.

1 - Primero cree los métodos [get / set] NumMembers () equivalentes de acceso / mutador en el objeto modelo que regresa con doSelect (). Recuerde, el programa de acceso ya no hace la consulta COUNT, simplemente mantiene su valor.

2 - Vaya a la clase de pares y anule el método padre doSelect () y copie todo el código exactamente como está

3 - Elimine este bit porque getMixerPreSelectHook es un método privado del par base (o cópielo en su par si lo necesita):

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

4 - Ahora agrega tu campo COUNT personalizado al método doSelect en tu clase de peer:

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

Eso es. Ahora tiene el campo COUNT adicional agregado a su objeto sin hacer una consulta por separado para obtenerlo a medida que escupe los resultados. El único inconveniente de esta solución es que ha tenido que copiar todo el código principal porque necesita agregar bits justo en el medio. Pero en mi situación, esto parecía un pequeño compromiso para guardar todas esas consultas y no escribir mi propia cadena de consulta SQL.


Hay varias opciones. Primero, es crear una vista en su base de datos que haga los recuentos por usted, similar a mi respuesta aquí . Hago esto para un proyecto actual de Symfony en el que trabajo, donde los atributos de solo lectura para una tabla dada son en realidad mucho, mucho más amplios que la tabla misma. Esta es mi recomendación ya que las columnas de agrupación (max (), count (), etc.) son de solo lectura de todos modos.

Las otras opciones son construir esta funcionalidad en su modelo. Usted PUEDE hacer esta hidratación usted mismo, pero es un poco complicado. Aquí están los pasos ásperos

  1. Agregue las columnas a su clase Table como miembros de datos protegidos.
  2. Escriba los getters y setters apropiados para estas columnas
  3. Anula el método de hidratación y, dentro, rellena tus nuevas columnas con los datos de otras consultas. Asegúrese de llamar a parent :: hydrate () como la primera línea

Sin embargo, esto no es mucho mejor de lo que estás hablando. Aún necesitará consultas N + 1 para recuperar un único conjunto de registros. Sin embargo, puede ser creativo en el paso n. ° 3, de modo que N es el número de columnas calculadas, no el número de filas devuelto.

Otra opción es crear un método de selección personalizado en su clase Table Peer.

  1. Realice los pasos 1 y 2 desde arriba.
  2. Escriba SQL personalizado que consultará manualmente a través del proceso Propel :: getConnection ().
  3. Cree el conjunto de datos de forma manual iterando sobre el conjunto de resultados, y maneje la hidratación personalizada en este punto para no romper la hidratación cuando se usa en los procesos de doSelect.

Aquí hay un ejemplo de este enfoque

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

Puede haber otras soluciones a su problema, pero están más allá de mi conocimiento. ¡La mejor de las suertes!