Pergunta

O que é a melhor maneira de trabalhar com campos calculados de Propel objetos?

dizer que tenho uma "Cliente" objeto que tem uma tabela correspondente "clientes" e cada um corresponde coluna para um atributo do meu objeto. O que eu gostaria de fazer é:. Adicionar um atributo calculado "Número de pedidos concluídos" ao meu objeto quando usá-lo em View A mas não em Visualizações B e C

O atributo calculado é um COUNT () da "Ordem" objetos vinculados ao meu objeto "Cliente" via ID.

O que posso fazer agora é primeiro selecionar todos os objetos de clientes, em seguida, de forma iterativa contar ordens para todos eles, mas eu acho que fazendo isso em uma única consulta iria melhorar o desempenho. Mas eu não posso corretamente "hidratar" meu objeto Propel, uma vez que não contém a definição do campo calculado (s).

Como você abordá-lo?

Foi útil?

Solução

Existem várias opções. Em primeiro lugar, é criar uma exibição em seu banco de dados que irá fazer a contagem para você, semelhante a minha resposta aqui . Eu faço isso por uma corrente Symfony projeto que eu trabalho sobre onde o atributos somente leitura para uma determinada tabela são realmente muito, muito maior do que a própria tabela. Esta é a minha recomendação desde colunas de agrupamento (max (), count (), etc) são somente leitura de qualquer maneira.

As outras opções são realmente construir essa funcionalidade em seu modelo. É absolutamente possível fazer isso hidratação mesmo, mas é um pouco complicado. Aqui está os passos irregulares

  1. Adicione as colunas à sua Table classe como membros de dados protegidos.
  2. Escrever os getters e setters apropriadas para essas colunas
  3. Substitua o método hidrato e dentro, preencher suas novas colunas com os dados de outras consultas. Certifique-se de pai chamada :: hidratado () como a primeira linha

No entanto, isso não é muito melhor do que o que você está falando já. Você ainda precisará N + 1 consultas para recuperar um único conjunto de registros. No entanto, você pode obter criativo na etapa # 3 para que N é o número de colunas calculadas, e não o número de linhas retornadas.

Outra opção é criar um método de seleção personalizada em seu Table classe de pares.

  1. Execute os passos 1 e 2 de cima.
  2. Write costume SQL que você irá consultar manualmente através do processo Propel :: getConnection ().
  3. Criar o conjunto de dados manualmente por iteração sobre o conjunto de resultados, e hidratação personalizado pega neste ponto para não quebrar hidratação quando o uso pelos processos doSelect.

Aqui está um exemplo desta abordagem

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

Pode haver outras soluções para o problema, mas eles estão além do meu conhecimento. Melhor da sorte!

Outras dicas

Estou fazendo isso em um projeto agora, substituindo hidratado () e Peer :: addSelectColumns () para aceder aos campos 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;
}   

está lá pateta algo com AddAsColumn (), mas eu não me lembro no momento, mas isso não funciona. Você pode ler mais sobre o AddAsColumn () questões .

Aqui está o que eu fiz para resolver isso sem quaisquer consultas adicionais:

Problema

necessário para adicionar um campo COUNT personalizado a um conjunto de resultados típico usado com o Symfony Pager. No entanto, como sabemos, Propel não suporta esta fora da caixa. Portanto, a solução mais fácil é simplesmente fazer algo parecido com isso no template:

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

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

endforeach;

Onde getNumMembers() executa uma consulta contagem separada para cada objeto $project. Claro, sabemos que isso é extremamente ineficiente, porque você pode fazer o COUNT on the fly, adicionando-o como uma coluna para a consulta SELECT original, salvando uma consulta para cada resultado apresentado.

Eu tive várias páginas diferentes exibindo este conjunto de resultados, todos usando diferentes critérios. Então escrever minha própria seqüência de consulta SQL com DOP diretamente seria a maneira demasiado incómodo como eu teria que entrar no objeto Criteria e mexer tentando formar uma cadeia de consulta com base em tudo o que estava nele!

Então, o que eu fiz no final evita tudo isso, deixando o trabalho de código nativo do Propel com os critérios e criar o SQL como de costume.

1 - Primeiro criar o [set get /] NumMembers () acessor equivalente / métodos modificadores no objeto modelo que fica retornando pela doSelect (). Lembre-se, o acessor não faz a consulta COUNT mais, ele apenas mantém o seu valor.

2 - Ir para a classe de pares e substituir o método pai doSelect () e copie todo o código dele exatamente como ela é

3 - Remover esta pouco porque getMixerPreSelectHook é um método particular do peer base (ou copie-o para o seu ponto se você precisar dele):

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

4 - Agora adicione o seu campo de contagem personalizada para o método doSelect em sua classe de pares:

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

É isso. Agora você tem o campo de contagem adicional adicionado ao seu objeto sem fazer uma consulta separada para obtê-lo como você cuspir os resultados. A única desvantagem para esta solução é que você tinha que copiar todo o código do pai, porque você precisa adicionar pedaços bem no meio dela. Mas, na minha situação, este parecia ser um pequeno compromisso para salvar todas as consultas e não escrever a minha própria seqüência de consulta SQL.

Adicionar um atributo "orders_count" a um cliente, e algo em seguida, escrever assim:

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

Você pode usar não só a "salvar" método, mas as estadias ideia o mesmo. Infelizmente, Propel não suporta nenhuma "mágica" para esses campos.

Propel realmente constrói uma função automática com base no nome do campo vinculado. Vamos dizer que você tem um esquema parecido com isto:

customer:
  id:
  name:
  ...

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

Quando você construir o seu modelo, o objeto Cliente terá um método GetOrders () que irá recuperar todos os pedidos associados a esse cliente. Você pode, então, basta usar count ($ cliente-> GetOrders ()) para obter o número de pedidos para esse cliente.

A desvantagem é isso também vai buscar e hidratar os objetos Order. Na maioria dos RDBMS, a única diferença de desempenho entre puxando os registros ou usar COUNT () é a largura de banda utilizada para retornar o conjunto de resultados. Se a largura de banda seria significativo para a sua aplicação, você pode querer criar um método no objeto Cliente que constrói a COUNT () consulta manualmente usando crioula:

  // 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');
    }
    ...
  }
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top