2016-02-15 1 views
11

मान लें कि मेरे पास Product इकाइयां और Review उत्पादों से जुड़ी संस्थाएं हैं। SQL क्वेरी द्वारा लौटाए गए कुछ परिणामों के आधार पर Product इकाई पर फ़ील्ड संलग्न करना संभव है? ReviewsCount फ़ील्ड को COUNT(Reviews.ID) as ReviewsCount के बराबर जोड़ने की तरह।क्या डॉक्ट्रिन में किसी फ़ील्ड के रूप में SQL फ़ंक्शन के परिणाम का उपयोग करना संभव है?

मैं जानता हूँ कि यह कैसा

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

करने के लिए है कि एक समारोह में संभव है लेकिन मैं डेटाबेस प्रश्नों की संख्या को कम करने और प्रदर्शन को बढ़ाने के लिए, के रूप में सामान्य रूप से मैं के सैकड़ों लोड करने के लिए की आवश्यकता नहीं होती एसक्यूएल के साथ ऐसा करने चाहते हैं समीक्षा, लेकिन अभी भी संख्या जानने की जरूरत है। मुझे लगता है कि एसक्यूएल के COUNT चल रहे हैं 100 उत्पादों के माध्यम से जाने और प्रत्येक के लिए 100 समीक्षा की तुलना में बहुत तेज़ होगा। इसके अलावा, यह सिर्फ उदाहरण है, अभ्यास पर मुझे अधिक जटिल कार्यों की आवश्यकता है, जो मुझे लगता है कि MySQL तेजी से संसाधित करेगा। यदि मैं गलत हूं तो मुझे सही करों।

+0

मुझे ऐसा नहीं लगता है। क्या आपने नामित प्रश्नों का उपयोग करने पर विचार किया है [उदाहरण] (http://stackoverflow.com/questions/12016378/doctrine2-named-queries)? –

+0

हां। मैंने टॉमसज़ मेडिसकी के जवाब पर मेरी टिप्पणी में मूल प्रश्नों की समस्या देखी। समस्या यह है कि वे इकाइयों के साथ स्केलर्स को एक साथ लौटते हैं, जबकि मैं चाहता हूं कि वे गंदगी से बचने के लिए संस्थाओं के खेतों में रहें। – Multis

उत्तर

1

हाँ, यह संभव है, आप 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

समस्या यह है कि उस मामले में मुझे एक इकाई से अलग एक स्केलर मिलता है। यह कोई समस्या नहीं हो सकती है यदि ये सभी स्केलर एक ही इकाई से जुड़े हुए हैं, लेकिन क्या होगा यदि उन्हें विभिन्न संस्थाओं से जोड़ा जाना चाहिए? जैसे कि मैं उत्पाद और समीक्षा चुनना चाहता हूं, और उत्पादों के लिए मैं समीक्षाओं की संख्या चाहता हूं, जबकि समीक्षाओं के लिए मुझे पसंद और नापसंदों की संख्या चाहिए। – Multis

1

2.1+, EXTRA_LAZY associations का उपयोग करने पर विचार करें:

वे आपको अपनी इकाई में अपनी तरह की विधि को लागू करने की अनुमति देते हैं, सभी एंटी को पुनः प्राप्त करने के बजाय एसोसिएशन पर सीधी गिनती करते हैं उस में संबंधों:

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

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

अच्छा लगता है, लेकिन फिर भी मुझे जो चाहिए वह नहीं। जैसा कि मैंने उल्लेख किया है, अभ्यास गणनाओं पर थोड़ा अधिक जटिल है। जैसे मुझे सकारात्मक समीक्षाओं और नकारात्मक समीक्षाओं की संख्या जानने की आवश्यकता है, जो सभी संग्रह लोड किए बिना और इसके माध्यम से असंभव प्रतीत होता है। – Multis

3

आप मैप कर सकते हैं एक single column result to an entity field - native queries और ResultSetMapping इस लक्ष्य को हासिल करने के लिए देखो। एक साधारण उदाहरण के रूप में:

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

यह वही है Aggregate Fields सिद्धांत प्रलेखन ने सुझाव दिया जाता है। समस्या यह है कि आप अनिवार्य रूप से सिद्धांत बना रहे हैं कि आपके डेटाबेस में reviews_count नामक एक और कॉलम है, जो आप नहीं चाहते हैं। इसलिए, यह अभी भी उस कॉलम को भौतिक रूप से जोड़ने के बिना काम करेगा, लेकिन यदि आपने कभी भी doctrine:schema:update चलाया है तो यह आपके लिए उस कॉलम को जोड़ने जा रहा है। दुर्भाग्य से सिद्धांत वास्तव में आभासी गुणों की अनुमति नहीं देता है, इसलिए एक और समाधान आपके स्वयं के कस्टम हाइड्रेटर को लिखना होगा, या शायद loadClassMetadata ईवेंट की सदस्यता लें और मैन्युअल रूप से अपनी विशेष इकाई (या संस्थाओं) लोड के बाद मैन्युअल रूप से मैपिंग जोड़ें।

ध्यान दें कि अगर आप COUNT(r.id) AS reviewsCount की तरह कुछ करना तो तुम अब COUNT(id) अपने addFieldResult() समारोह में उपयोग कर सकते हैं, और बदले कि दूसरा पैरामीटर के लिए उर्फ ​​reviewsCount उपयोग करना चाहिए।

आप परिणाम सेट मैपिंग का उपयोग शुरू करने के रूप में ResultSetMappingBuilder का उपयोग भी कर सकते हैं।

मेरा वास्तविक सुझाव यह है कि वह अतिरिक्त सामानों के माध्यम से इसे मैन्युअल रूप से करने के बजाय करें।अनिवार्य रूप से एक सामान्य क्वेरी बनाएं जो आपकी इकाई और स्केलर परिणामों को एक सरणी में लौटाती है, फिर स्केलर परिणाम को अपनी इकाई पर एक संबंधित, अप्रयुक्त फ़ील्ड पर सेट करें, और इकाई को वापस करें।

+0

यह एक आदर्श समाधान हो सकता है, लेकिन मुझे एक "अपरिभाषित अनुक्रमणिका: समीक्षा गणना" अपवाद मिलता है यदि मैं किसी संपत्ति को परिणाम निर्दिष्ट करता हूं, जो कॉलम से लिंक नहीं होता है। भले ही वह संपत्ति मौजूद है और सार्वजनिक है। क्या डेटाबेस कॉलम को असाइन किए बिना सिद्धांत द्वारा संपत्ति को सुलभ बनाने का कोई तरीका है? – Multis

+0

मामला मिलान करना है, इसलिए इसे $ समीक्षा के बजाय $ समीक्षा काउंटर –

+0

की समझदारी होगी। मेल खा रहा है। फिर भी यह केवल तभी काम करता है जब मैं उस क्षेत्र में डेटाबेस कॉलम असाइन करता हूं (भले ही मैं सिद्धांत नहीं करता: स्कीमा: अद्यतन)। लेकिन यहां तक ​​कि उस मामले में, यदि मुझे एक ही क्वेरी के साथ एक ही इकाई मिलती है - यह इस क्षेत्र को रीसेट करने में परिवर्तनों को ओवरराइट कर देगी, हालांकि मैं इन परिणामों को अलग से रखता हूं। यह सिद्धांत कैश के परिणाम की तरह दिखता है और मुझे एक ही संस्था देता है, लेकिन खेतों को फिर से लोड और समीक्षा के साथ फ़ील्ड रीसेट करें। – Multis

2

विस्तृत जांच के बाद मुझे पता चला कि मैं जो कुछ चाहता था उसके करीब कुछ करने के कई तरीके हैं, लेकिन उनमें से सभी में कुछ minuses हैं। अंत में मैंने CustomHydrators का उपयोग करने का निर्णय लिया है। ऐसा लगता है कि ओआरएम के साथ प्रबंधित नहीं किए गए गुणों को ResultSetMapping के साथ फ़ील्ड के रूप में मैप नहीं किया जा सकता है, लेकिन स्केलर के रूप में प्राप्त किया जा सकता है और मैन्युअल रूप से एक इकाई से जुड़ा जा सकता है (क्योंकि PHP फ्लाई पर ऑब्जेक्ट गुणों को संलग्न करने की अनुमति देता है)। हालांकि, परिणामस्वरूप आप सिद्धांत से प्राप्त होते हैं कैश में रहता है। इसका मतलब है कि उस तरीके से सेट की गई संपत्ति रीसेट हो सकती है यदि आप कोई अन्य क्वेरी करते हैं जिसमें इन इकाइयों को भी शामिल किया जाएगा।

ऐसा करने का एक और तरीका यह क्षेत्र सीधे सिद्धांत के मेटाडेटा कैश में जोड़ रहा था। मैं एक CustomHydrator में कर की कोशिश की है कि:

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'); 

आशा है कि किसी की मदद कर सकता है :)

संबंधित मुद्दे

 संबंधित मुद्दे