Можно подумать, что нативные средства 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, так как это впереди кривой.
Существует несколько вариантов. Во-первых, должен создать представление в Вашем DB, который сделает счета для Вас, подобный моему ответу здесь. Я делаю это для текущего проекта Symfony, я продолжаю работать, где атрибуты "только для чтения" для данной таблицы на самом деле очень, намного шире, чем сама таблица. Это - моя рекомендация, так как группирующиеся столбцы (макс. (), количество (), и т.д.) только для чтения так или иначе.
Другие опции состоят в том, чтобы на самом деле встроить эту функциональность в Вашу модель. Вы абсолютно CAN делает эту гидратацию сами, но это немного сложно. Вот грубые шаги
Однако это не намного лучше, чем, о чем Вы уже говорите. Вам все еще будет нужен N + 1 запрос для получения единственного официального набора документов. Однако можно стать творческими на шаге № 3 так, чтобы N был количеством вычисляемых столбцов, не количеством возвращенных строк.
Другая опция состоит в том, чтобы создать пользовательский метод выбора для Вашего класса TablePeer.
Вот пример этого подхода
<?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;
}
}
Могут быть другие решения Вашей проблемы, но они вне моего знания. Всего наилучшего
Я делаю это в проекте теперь переопределяющим гидратом () и 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 () проблемы.
Добавьте атрибут "orders_count" к Клиенту и затем запишите что-то вроде этого:
class Order {
...
public function save($conn = null) {
$customer = $this->getCustomer();
$customer->setOrdersCount($customer->getOrdersCount() + 1);
$custoner->save();
parent::save();
}
...
}
Можно использовать не, только метод "сохранения", но и идея остаются такими же. К сожалению, Продвиньте, не поддерживает "волшебства" для таких полей.
Продвиньте на самом деле создает автоматическую функцию на основе названия связанного поля. Скажем, у Вас есть схема как это:
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');
}
...
}
Вот что я сделал, чтобы решить эту проблему без дополнительных запросов:
Проблема
Требуется добавить настраиваемое поле 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.