Warning: file_get_contents(/data/phpspider/zhask/data//catemap/1/php/248.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181

Warning: file_get_contents(/data/phpspider/zhask/data//catemap/8/mysql/63.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Php 是否可以将SQL函数的结果用作条令中的字段?_Php_Mysql_Sql_Symfony_Doctrine Orm - Fatal编程技术网

Php 是否可以将SQL函数的结果用作条令中的字段?

Php 是否可以将SQL函数的结果用作条令中的字段?,php,mysql,sql,symfony,doctrine-orm,Php,Mysql,Sql,Symfony,Doctrine Orm,假设我有产品实体和审查实体附加到产品。是否可以根据SQL查询返回的某个结果将字段附加到产品实体?类似于将等于COUNT(Reviews.ID)的reviewCount字段附加为ReviewCount 我知道在函数中这样做是可能的 public function getReviewsCount() { return count($this->Reviews); } 但我希望使用SQL来减少数据库查询的数量并提高性能,因为通常我可能不需要加载数百条评论,但仍然需要知道它们的数量。我认

假设我有
产品
实体和
审查
实体附加到产品。是否可以根据SQL查询返回的某个结果将字段附加到
产品
实体?类似于将等于
COUNT(Reviews.ID)的
reviewCount
字段附加为ReviewCount

我知道在函数中这样做是可能的

public function getReviewsCount() {
    return count($this->Reviews);
}

但我希望使用SQL来减少数据库查询的数量并提高性能,因为通常我可能不需要加载数百条评论,但仍然需要知道它们的数量。我认为运行SQL的
COUNT
要比遍历100个产品并为每个产品计算100个评论快得多。此外,这只是一个例子,在实践中,我需要更复杂的函数,我认为MySQL会处理得更快。如果我错了,请纠正我。

是的,这是可能的,您需要使用
QueryBuilder
来实现这一点:

$result = $em->getRepository('AppBundle:Product')
    ->createQueryBuilder('p')
    ->select('p, count(r.id) as countResult')
    ->leftJoin('p.Review', 'r')
    ->groupBy('r.id')
    ->getQuery()
    ->getArrayResult();
现在,您可以执行以下操作:

foreach ($result as $row) {
    echo $row['countResult'];
    echo $row['anyOtherProductField'];
}

如果你在学说2.1 +,考虑使用:

它们允许您在实体中实现类似您的方法,直接对关联进行计数,而不是检索其中的所有实体:

/**
* @ORM\OneToMany(targetEntity="Review", mappedBy="Product" fetch="EXTRA_LAZY")
*/
private $Reviews;

public function getReviewsCount() {
    return $this->Reviews->count();
}
您可以映射-查看和结果映射来实现这一点。举个简单的例子:

use Doctrine\ORM\Query\ResultSetMapping;

$sql = '
    SELECT p.*, COUNT(r.id)
    FROM products p
    LEFT JOIN reviews r ON p.id = r.product_id
';

$rsm = new ResultSetMapping;
$rsm->addEntityResult('AppBundle\Entity\Product', 'p');
$rsm->addFieldResult('p', 'COUNT(id)', 'reviewsCount');

$query   = $this->getEntityManager()->createNativeQuery($sql, $rsm);
$results = $query->getResult();
然后,在产品实体中,您将有一个
$reviewsCount
字段,并且计数将映射到该字段。请注意,只有在条令元数据中定义了一列时,这才有效,如下所示:

/**
 * @ORM\Column(type="integer")
 */
private $reviewsCount;

public function getReviewsCount()
{
    return $this->reviewsCount;
}
这是条令文件所建议的。这里的问题是,您实际上是在让我们认为您的数据库中还有另一列名为
reviews\u count
,这是您不想要的。因此,这在不实际添加该列的情况下仍然有效,但是如果您运行
原则:schema:update
它将为您添加该列。不幸的是,原则并不真正允许虚拟属性,因此另一种解决方案是编写您自己的自定义属性,或者订阅
loadClassMetadata
事件,并在加载特定实体后手动添加映射

请注意,如果执行类似于ReviewCount的
COUNT(r.id)
操作,则不能再在
addFieldResult()
函数中使用
COUNT(id)
,而必须为第二个参数使用别名
ReviewCount

您还可以使用作为使用结果集映射的开始


我的实际建议是手动操作,而不是检查所有额外的东西。基本上创建一个普通查询,将实体和标量结果返回到一个数组中,然后将标量结果设置为实体上对应的未映射字段,然后返回实体。

经过详细调查,我发现有几种方法可以实现我想要的功能,包括其他答案中列出的功能,但它们都有一些缺点。最后我决定使用。似乎不能使用ResultsMapping作为字段映射未使用ORM管理的属性,但可以作为标量获取并手动附加到实体(因为PHP允许动态附加对象属性)。但是,从条令中获得的结果仍保留在缓存中。这意味着,如果您进行一些其他查询,其中也包含这些实体,则以这种方式设置的属性可能会被重置

另一种方法是将这些字段直接添加到条令的元数据缓存中。我试着在一个定制器中这样做:

protected function getClassMetadata($className)
{
    if ( ! isset($this->_metadataCache[$className])) {
        $this->_metadataCache[$className] = $this->_em->getClassMetadata($className);

        if ($className === "SomeBundle\Entity\Product") {
            $this->insertField($className, "ReviewsCount");
        }
    }

    return $this->_metadataCache[$className];
}

protected function insertField($className, $fieldName) {
    $this->_metadataCache[$className]->fieldMappings[$fieldName] = ["fieldName" => $fieldName, "type" => "text", "scale" => 0, "length" => null, "unique" => false, "nullable" => true, "precision" => 0];
    $this->_metadataCache[$className]->reflFields[$fieldName] = new \ReflectionProperty($className, $fieldName);

    return $this->_metadataCache[$className];
}
但是,该方法也存在实体属性重置问题。因此,我的最终解决方案只是使用stdClass来获得相同的结构,但不受条令管理:

namespace SomeBundle;

use PDO;
use Doctrine\ORM\Query\ResultSetMapping;

class CustomHydrator extends \Doctrine\ORM\Internal\Hydration\ObjectHydrator {
    public function hydrateAll($stmt, $resultSetMapping, array $hints = array()) {
        $data = $stmt->fetchAll(PDO::FETCH_ASSOC);

        $result = [];

        foreach($resultSetMapping->entityMappings as $root => $something) {
            $rootIDField = $this->getIDFieldName($root, $resultSetMapping);

            foreach($data as $row) {
                $key = $this->findEntityByID($result, $row[$rootIDField]);

                if ($key === null) {
                    $result[] = new \stdClass();
                    end($result);
                    $key = key($result);
                }

                foreach ($row as $column => $field)
                    if (isset($resultSetMapping->columnOwnerMap[$column]))
                        $this->attach($result[$key], $field, $this->getPath($root, $resultSetMapping, $column));
            }
        }


        return $result;
    }

    private function getIDFieldName($entityAlias, ResultSetMapping $rsm) {
        foreach ($rsm->fieldMappings as $key => $field)
            if ($field === 'ID' && $rsm->columnOwnerMap[$key] === $entityAlias) return $key;

            return null;
    }

    private function findEntityByID($array, $ID) {
        foreach($array as $index => $entity)
            if (isset($entity->ID) && $entity->ID === $ID) return $index;

        return null;
    }

    private function getPath($root, ResultSetMapping $rsm, $column) {
        $path = [$rsm->fieldMappings[$column]];
        if ($rsm->columnOwnerMap[$column] !== $root) 
            array_splice($path, 0, 0, $this->getParent($root, $rsm, $rsm->columnOwnerMap[$column]));

        return $path;
    }

    private function getParent($root, ResultSetMapping $rsm, $entityAlias) {
        $path = [];
        if (isset($rsm->parentAliasMap[$entityAlias])) {
            $path[] = $rsm->relationMap[$entityAlias];
            array_splice($path, 0, 0, $this->getParent($root, $rsm, array_search($rsm->parentAliasMap[$entityAlias], $rsm->relationMap)));
        }

        return $path;
    }

    private function attach($object, $field, $place) {
        if (count($place) > 1) {
            $prop = $place[0];
            array_splice($place, 0, 1);
            if (!isset($object->{$prop})) $object->{$prop} = new \stdClass();
            $this->attach($object->{$prop}, $field, $place);
        } else {
            $prop = $place[0];
            $object->{$prop} = $field;
        }
    }
}
使用该类,您可以获得任意结构并附着任意实体,但您可以选择:

$sql = '
    SELECT p.*, COUNT(r.id)
    FROM products p
    LEFT JOIN reviews r ON p.id = r.product_id
';

$em = $this->getDoctrine()->getManager();

$rsm = new ResultSetMapping();
$rsm->addEntityResult('SomeBundle\Entity\Product', 'p');
$rsm->addFieldResult('p', 'COUNT(id)', 'reviewsCount');

$query = $em->createNativeQuery($sql, $rsm);

$em->getConfiguration()->addCustomHydrationMode('CustomHydrator', 'SomeBundle\CustomHydrator');
$results = $query->getResult('CustomHydrator');

希望这可能会对某人有所帮助:)

前面的答案对我没有帮助,但我找到了一个解决方案,可以做到以下几点:

我的用例不同,所以代码是模拟的。但关键是使用
addScalarResult
,然后在实体上设置聚合时清除结果

使用条令\ORM\Query\resultstMappingBuilder;
// ...
$sql=”
选择p.*,将(r.id)计数为reviewCount
来自产品p
p.id=r.product\u id上的左连接评论
";
$em=$this->getEntityManager();
$rsm=新的ResultSetMappingBuilder($em,ResultSetMappingBuilder::COLUMN\u RENAMING\u CUSTOM);
$rsm->addRootEntityFromClassMetadata('App\Entity\Product','p');
$rsm->addScalarResult('reviewCount','reviewCount');
$query=$em->createNativeQuery($sql,$rsm);
$result=$query->getResult();
//将聚合字段附加到实体
$aggregatedResult=[];
foreach($ResultAs$resultItem){
$product=$resultItem[0];
$product->setReviewCount($resultItem[“reviewCount”]);
阵列推送($aggregatedResult,$product);
}
返回$aggregatedResult;

问题是,在这种情况下,我得到一个标量,与实体分开。如果所有这些标量都附加到同一个实体,那么这可能不是问题,但是如果它们应该附加到不同的实体呢?比如我想要选择产品和评论,对于产品我想要评论的数量,而对于评论我想要喜欢和不喜欢的数量。看起来不错,但仍然不是我需要的。正如我所提到的,实际计算要复杂一些。就像我需要知道正面评论和负面评论的数量,如果不加载所有的收藏并仔细检查,这似乎是不可能的。我不这么认为。您是否考虑过使用命名查询?是的。在我对托马斯马德斯基的回答的评论中,我注意到了本机查询的问题。问题是它们与实体一起返回标量,而我希望它们是实体的字段以避免混乱。这可能是一个理想的解决方案,但如果