Приложение Symfony - как добавить вычисляемые поля для Продвижения объектов?

Можно подумать, что нативные средства JSON в SQL Server были бы здесь более полезными, но не совсем - требуется некоторая неуклюжая конкатенация строк, и единственное, что нам действительно приносит пользу, - это нативное экранирование JSON. Это потому, что на самом деле нет удобного способа сопоставить столбцы в пары ключ / значение; только простые сопоставления столбца как ключа.

SELECT [UserID], JsonValue = 
    '{' + STRING_AGG(
        '"' + STRING_ESCAPE([key], 'json') + '"' + ':' + 
        '"' + STRING_ESCAPE([value], 'json') + '"',
        ','
    ) + '}'
FROM UserProperty
GROUP BY UserID

Для этого требуется SQL Server 2017+; он также должен работать на Azure, так как это впереди кривой.

5
задан Tomas Kohl 29 October 2008 в 10:56
поделиться

5 ответов

Существует несколько вариантов. Во-первых, должен создать представление в Вашем DB, который сделает счета для Вас, подобный моему ответу здесь. Я делаю это для текущего проекта Symfony, я продолжаю работать, где атрибуты "только для чтения" для данной таблицы на самом деле очень, намного шире, чем сама таблица. Это - моя рекомендация, так как группирующиеся столбцы (макс. (), количество (), и т.д.) только для чтения так или иначе.

Другие опции состоят в том, чтобы на самом деле встроить эту функциональность в Вашу модель. Вы абсолютно CAN делает эту гидратацию сами, но это немного сложно. Вот грубые шаги

  1. Добавьте столбцы к своему классу Таблицы как защищенные элементы данных.
  2. Запишите соответствующие методы считывания и методы set для этих столбцов
  3. Переопределите метод гидрата и в, заполните Ваши новые столбцы с данными из других запросов. Удостоверьтесь, что назвали родителя:: гидрат () как первая строка

Однако это не намного лучше, чем, о чем Вы уже говорите. Вам все еще будет нужен N + 1 запрос для получения единственного официального набора документов. Однако можно стать творческими на шаге № 3 так, чтобы N был количеством вычисляемых столбцов, не количеством возвращенных строк.

Другая опция состоит в том, чтобы создать пользовательский метод выбора для Вашего класса TablePeer.

  1. Сделайте шаги 1 и 2 сверху.
  2. Запишите пользовательский SQL, который Вы запросите вручную через Продвижение:: getConnection () процесс.
  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;
    }
}

Могут быть другие решения Вашей проблемы, но они вне моего знания. Всего наилучшего

3
ответ дан 14 December 2019 в 19:30
поделиться

Я делаю это в проекте теперь переопределяющим гидратом () и 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 () проблемы.

1
ответ дан 14 December 2019 в 19:30
поделиться

Добавьте атрибут "orders_count" к Клиенту и затем запишите что-то вроде этого:

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

Можно использовать не, только метод "сохранения", но и идея остаются такими же. К сожалению, Продвиньте, не поддерживает "волшебства" для таких полей.

0
ответ дан 14 December 2019 в 19:30
поделиться

Продвиньте на самом деле создает автоматическую функцию на основе названия связанного поля. Скажем, у Вас есть схема как это:

customer:
  id:
  name:
  ...

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

При создании модели Клиентский объект будет иметь метод getOrders (), который получит все заказы, связанные с тем клиентом. Можно затем просто использовать количество ($customer-> getOrders ()) для получения количества заказов на того клиента.

Оборотная сторона, это будет также выбирать и гидратировать те объекты Порядка. На большей части RDBMS, единственном различии в производительности между получением по запросу записей или использованием КОЛИЧЕСТВА () пропускная способность, используемая для возврата набора результатов. Если та пропускная способность была бы значительной для Вашего приложения, Вы могли бы хотеть создать метод в Клиентском объекте, который создает КОЛИЧЕСТВО () запрос вручную использование креольского языка:

  // 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');
    }
    ...
  }
0
ответ дан 14 December 2019 в 19:30
поделиться

Вот что я сделал, чтобы решить эту проблему без дополнительных запросов:

Проблема

Требуется добавить настраиваемое поле COUNT к типичному набору результатов, используемому с Symfony Pager. Однако, как мы знаем, Propel не поддерживает это из коробки. Поэтому простое решение - просто сделать что-то вроде этого в шаблоне:

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

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

endforeach;

Где getNumMembers () запускает отдельный запрос COUNT для каждого объекта $ project . Конечно, мы знаем, что это крайне неэффективно, потому что вы можете выполнять COUNT на лету, добавляя его в качестве столбца к исходному запросу SELECT, сохраняя запрос для каждого отображаемого результата.

У меня было несколько разных страниц, отображающих этот набор результатов, на всех с использованием разных критериев. Так что написать мою собственную строку запроса SQL с PDO напрямую было бы слишком хлопотно, так как мне пришлось бы залезть в объект Criteria и возиться, пытаясь сформировать строку запроса на основе того, что в ней было!

Итак, то, что я сделал в итоге, позволило избежать всего этого, позволив собственному коду Propel работать с критериями и создавать 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.

1
ответ дан 14 December 2019 в 19:30
поделиться
Другие вопросы по тегам:

Похожие вопросы: